Java中集合类学习笔记一---Vector

时间:2022-05-22 05:03:58

一、学习目的

由于最近在项目中发现,一个项目中用到的java中最多也是最重要的知识基本上就是集合类的使用和多线程的开发,而集合类基本都会使用像List、ArrayList或者HashMa,有些场景可能会用到HashTable或者HashSet等集合。总的来说,java中的对于集合的操作给我们编程带来了很大的便利,但是作为一名合格的程序员,我觉得不仅要会使用,而且应该知道实现细节。同时阅读技术牛人写出来的代码对于自己的开发水平也会有很大的帮助。

二、学习目标

了解Vector的继承关系以及其内部实现,能够更加灵活的使用

三、学习笔记

在java的代码实现中,如果你去认真观察的话,他的继承关系有些复杂,也可以说有些冗余,但是到目前为止我还不知道为什么会有这种冗余出现,或者是代码开发者不小心造成的,也有可能是代码开发者故意这么写,让我们在看代码的时候不用那么麻烦吧.具体的也说不清,下面看一些java中关于vector实现的类图:

Java中集合类学习笔记一---Vector

在上面的类图中,基本上所有的集合类都会实现Collection接口,并且每一个集合类中都会实现自己的迭代器函数,用于循环遍历,必要时候可以使用迭代器进行删除某个迭代器指向的元素。我并没有将所有的类方法写到里面,这个类图主要是想让读者能够看到清晰的继承关系。从图中可以看到Vector继承了AbstractList<E>这个抽象类,但同时AbstractList又实现了List<E>接口,但Vector也实现了List<E>接口,这个地方总感觉有些多余.如果有知道的还请指导。通过接口的继承关系以及Vector的实现关系,可以理解为Vector类实现了以下接口:Iterable、Collection、List、RandomAccess、Cloneable、Serializable。上述接口在以后的学习过程中还会学到,这篇文章中就不深入了解其细节了,只要大概知道是干什么的就行。

下面就是关于代码层面的学习:

在java.util包中打开Vector.java这个类,首先类名以及继承代码

public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

下一行
protected Object[] elementData;
通过这一行代码相信大家都明白了Vector底层是通过数组的方式进行数据存储的,所以对于Vector的增删改查都可以使用跟数组相似的逻辑操作进行。

下面看一下该类中主要的方法都有哪些:

1. size 方法的实现

public synchronized int size() {
return elementCount;
}

返回Vector中元素个数,这边的元素个数与数组大小不是一个概念。vector中元素个数是通过

<pre name="code" class="java">protected int elementCount;
 
2. isEmpty 函数的实现 

<pre name="code" class="java">public synchronized boolean isEmpty() {
return elementCount == 0;
}
 
判断该Vector是否为空,这些都比较简单,也很容易看懂的,以后就不罗列了。

3. boolean contains(Object o);//是否包含某个对象

public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}

通过indexOf来查找该元素存在哪个位置上,如果该元素不存在,则返回-1。contains方法使用了indexOf方法,后面也会遇到Index方法的。

4. index方法的实现

<pre name="code" class="java">public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
 
该方法表示从index个位置开始数起,如果存在于object相同的对象,那么就返回该对象所在位置处的坐标,否则返回-1;我觉得让我考虑的话,应该会把object为null的情况给忽略,这样就导致了算法的不普遍性。而循环遍历数组后通过对象的equals方法,这同时用到了java的多态性,object只是一个对象的引用而已,如果该对象的实际类型的类有重载过equals方法,那么按照重载后的方法判断是否相同;如果没有重载,那么必须两个对象的地址相同才会判定为相同对象。所以,以后我们写程序的时候如果要依据对象中的字段来判断对象是否相同的话,则必须重载Object超父类中的equals方法才行。

5.lastIndexOf方法的实现

<pre name="code" class="java">public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount);

if (o == null) {
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
 
跟indexOf方法实现相同,只是从后向前遍历,确实有时候知道某个元素在靠后的位置,使用向前遍历能够节约系统时间。
6.elementAt,firstElement,lastElement方法

<pre name="code" class="java">public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}

return elementData(index);
}
 
6中的方法全部调用elementData方法,就没必要说了,直接看elementData方法吧

7.elementData方法的实现

@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

这不太合适吧?我还期待着有什么精妙的实现呢.原来就是对数组的操作.

8.removeElementAt方法的实现

在寻找第7个方法的时候我无意间看到了一个很好的实现方法。确实感觉到了在java中无处不在的代码重用。不废话了,看代码

<pre name="code" class="java">public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}


 
在system类中实现的arraycopy方法直接把这个删除方法搞定,这个方法中的参数代表什么意思呢?第一个参数表示源数组,第二个参数源数组的开始位置,第三个参数表示目的数组,第四个参数表示目的数组的开始位置,第五个参数表示复制多少个数据过去。认真思考后发现,这样拷贝的话,那么源数组中的最后一个元素并没有替换,还是存在原来的位置,且与倒数第二个位置上的元素相同。这样的话删除的不完全啊。对,确实这样操作删除的不完全,但是后面的两句话将arraycopy的缺点给弥补了.好巧妙。 

9.clone方法的实现

<pre name="code" class="java">public synchronized Object clone() {
try {
@SuppressWarnings("unchecked")
Vector<E> v = (Vector<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, elementCount);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
 
clone方法一直都是很神秘的一个方法,学过c++的一定都见过深拷贝和浅拷贝这两个名词,但这是什么意思呢?在java中是否也存在相同的问题呢?答案是肯定的。在java中确实存在相同的问题,这两个名词都只会在类中有指向某个非基本类型对象的时候才会存在的问题,这也就存在了java中的另外两个名词,值传递和引用传递的概念了。不过相信能够读到这边的读者都对这些概念有一个非常深刻的了解。回归正题。如果该函数中只使用super.clone()来进行对象的拷贝的话,那么在返回的新的对象中关于elementData还是指向了原Vector中elementData所指向的对象。这样的话,相当于对另一个对象的操作会影响到原vector对象中elementData中的值,这显然是不合理的.所以通过Arrays中的copyOf方法,将源数组中的值拷贝到新对象中,这样在内存中就会存在两个相同的副本,对对象操作之后不会影响彼此的数据。copyOf也是一个比较强大的函数,而Arrays也是一个经常使用到的工具类,可以大大的简化工作,有兴趣的可以去了解一下其细节,在以后的博文中应该也会学到。 

10. addAll函数的实现

<pre name="code" class="java">public synchronized boolean addAll(Collection<? extends E> c) {
modCount++;
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
System.arraycopy(a, 0, elementData, elementCount, numNew);
elementCount += numNew;
return numNew != 0;
}
 
通过使用集合类中都存在的toArray()方法先将集合变为相关对象的数组,然后对原始对象进行扩容调用 
<pre name="code" class="java">ensureCapacityHelper(elementCount + numNew);
 
使用系统拷贝,将源数组中的从0开始的numNew个元素拷贝到elementData中,从elementCount位置开始插入。 

而集合中全部都有toArray方法,下面就直接看一下Vector中的toArray方法吧

11 toArray方法的实现

<span style="font-family:KaiTi_GB2312;font-size:14px;"></span><pre name="code" class="java">public synchronized Object[] toArray() {
return Arrays.copyOf(elementData, elementCount);
}
 
同样是对于对象中数组的拷贝。
12 扩容函数ensureCapacityHelper的实现
<span style="font-family:KaiTi_GB2312;font-size:14px;"></span><pre name="code" class="java">private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
 
<pre name="code" class="java"> private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}


 
这部分代码也没有什么好解释的了,每一个集合类都有自己不同的扩容方法,根据集合的性能不同,使用了不同的方法,从这部分代码能够看到扩容的主要因子是capacityIncrement,容量增量。同时该扩容算法的容器增长率是呈线性增长的。 

13.唯一一个实现的是Serilizable接口中的方法

<pre name="code" class="java">private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
final java.io.ObjectOutputStream.PutField fields = s.putFields();
final Object[] data;
synchronized (this) {
fields.put("capacityIncrement", capacityIncrement);
fields.put("elementCount", elementCount);
data = elementData.clone();
}
fields.put("elementData", data);
s.writeFields();
}


 
将对象中的字段以及值放入到流中,进行数据的传输。这里也不在说明了,以后肯定还会遇到序列化接口的实现的。

14.listIterator的实现

上面就曾经说过,每一个集合类中都会存在一个迭代器,方便对集合中元素进行操作

<pre name="code" class="java">public synchronized Iterator<E> iterator() {
return new Itr();
}

/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}

public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}

public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

/**
* An optimized version of AbstractList.ListItr
*/
final class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}

public boolean hasPrevious() {
return cursor != 0;
}

public int nextIndex() {
return cursor;
}

public int previousIndex() {
return cursor - 1;
}

public E previous() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
cursor = i;
return elementData(lastRet = i);
}
}

public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.set(lastRet, e);
}
}

public void add(E e) {
int i = cursor;
synchronized (Vector.this) {
checkForComodification();
Vector.this.add(i, e);
expectedModCount = modCount;
}
cursor = i + 1;
lastRet = -1;
}
}


 
一堆代码以及包含了两个内部实现类一个是Itr 正向迭代器,只能够向下查询。ListItr 双向迭代器。通过两个类似指针的标志位来进行数据的查询以及修改。 

以上就是关于Vector的全部核心和有趣的方法。欢迎大家指正批评。

Java中集合类学习笔记一---Vector