老规矩也是先画个类图了解一下他的结构:
画了三个类图之后我陷入了沉思,为啥类图和HashSet这么像?翻阅资料,了解集合框架采用了策略模式的设计模式,设计模式等后面再认真整理一份分析一下,这里就不多说了,留着这些问题,后面设计模式版本慢慢揭秘,下面说说几个重要的方法
构造方法:这里了解到如果我们不设置初始值的话默认是10,和hashMap的16有差别,初始化方法就初始化了一个10长度的Object数组
public ArrayList() {
this(10);
}
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
boolean add(E e)
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
每次添加元素的时候会检查一下容量够不,如果不够,进行扩容,而且他的扩容算法和hashMap不一样,他的计算方法:(oldCapacity * 3)/2 + 1,自己可以算一下,10-->16-->40-->61,看完这里,注意Arrays.copyOf这个方法,这个就是我在String里面强调的很重要的方法,看来万变不离其中,一般集合框架中用到复制的地方肯定会调用这个方法。
int indexOf(Object o)
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
这个方法相比String.indexOf(String str)较简单,因为o是一个整体,这样比较就较为简单。
E remove(int index),重点看一下删除方法,这个是按照下标来删除的,看一下他的实现
public E remove(int index) {
RangeCheck(index);//异常索引坚持
modCount++;
E oldValue = (E) elementData[index];
//这里计算需要移动的步数,下面居然采用了复制
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
看了上面的源码,我做了一个实验,
public static void testList(){
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
list.add(i+"asdjflasjdkflasjdlkfjaslkdfj");
}
long a = System.currentTimeMillis();
list.remove(5000);
long b = System.currentTimeMillis();
System.out.println(b-a);
}
我中途换了很多次删除的索引下标,执行结果都是0ms。。。之前我还以为remove需要进行复制,会比较慢,谁知道这么快?不是说数组对插入和删除操作比较耗时吗,于是我加大了长度再来,我加到了长度为100w,删除下标为50w的数据,结果用了1ms,无法理解!!!那我表示以后LinkedList再见。
boolean remove(Object o)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
这里他会遍历数组,然后调用equals方法比较相等,最终还是通过下标来进行删除操作,我也对他做了性能测试,
public static void testList(){
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000000; i++) {
list.add(i+"asdjflasjdkflasjdlkfjaslkdfj");
}
String str = "500000asdjflasjdkflasjdlkfjaslkdfj";
long a = System.currentTimeMillis();
list.remove(500000);
long b = System.currentTimeMillis();
list.remove(str);
long c = System.currentTimeMillis();
System.out.println(b-a);
System.out.println(c-b);
}
结果是用索引删除花了1ms,用对象删除花了36ms,36ms还是比较久了相对于大并发来说,所以以后删除还是尽量用索引来删除,玩到这里,我又测试了一下map的性能,
public static void testList() {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000000; i++) {
list.add(i + "asdjflasjdkflasjdlkfjaslkdfj");
}
String str = "500000asdjflasjdkflasjdlkfjaslkdfj";
Map<String, Integer> map = new HashMap<String, Integer>();
for (int i = 0; i < 1000000; i++) {
map.put(i + "asdjflasjdkflasjdlkfjaslkdfj", 0);
}
long a = System.currentTimeMillis();
list.remove(500000);
long b = System.currentTimeMillis();
list.remove(str);
long c = System.currentTimeMillis();
map.remove(500000);
long d = System.currentTimeMillis();
System.out.println(b - a);
System.out.println(c - b);
System.out.println(d - c);
}
运行结果:
之前看过hashMap的删除流程是通过key得到hash,然后通过hash得到index,再定位到数组的entry,再对这个entry链表做遍历,这么快应该也要归功于hashMap的hash算法。