Array + List = 数组 + 列表 = ArrayList = 数组列表
四种初始化方式
方式一:普通方式(这个方式很简单也是我们最常用的方式)
ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
方式二:内部类方式 (这种方式也比较常用,而且省去了多余的代码量)
ArrayList<String> list = new ArrayList<String>(){
list.add("aaa");
list.add("bbb");
list.add("ccc");
};
方式三:Arrays.asList
ArrayList<String> list = new ArrayList<String>(Arrays.asList("aaa", "bbb", "ccc"));
构造函数源码分析
public ArrayList(Collection <? extends E > c) {
elementData = c.toArray();
if((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if(elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
通过构造函数可以看到,只要实现 Collection 类的都可以作为入参。
在通过转为数组以及拷贝Arrays.copyOf 到 Object[] 集合中在赋值给属性elementData 。
注意:c.toArray might (incorrectly) not return Object[] (see 6260652)
public Object[] toArray() 返回的类型不一定就是Object[] ,其类型取决于其返回的实际类型,毕竟Object 是父类,它可以其他任意型。(子类实现和父同名的方法,仅仅返回值不一致时,默认调用是子类的实现方法)
举例
@Test
public void t() {
List < Integer > list1 = Arrays.asList(1, 2, 3);
System.out.println("通过数组转换:" + (list1.toArray().getClass() == Object[].class));
ArrayList < Integer > list2 = new ArrayList < Integer > (Arrays.asList(1, 2, 3));
System.out.println("通过集合转换:" + (list2.toArray().getClass() == Object[].class));
}
测试结果:
通过数组转换:false
通过集合转换:true
造成这个结果的原因,如下:
1. Arrays.asList使用的是:Arrays.copyOf(this.a, size,(Class<? Arrays.copyOf(this.a, size,(Class<? extends T[]>) a .getClass());
2. ArrayList 构造函数使用的是:Arrays.copyOf(elementData, size, Arrays.copyOf(elementData, size, Object[].class);
Arrays.asList构建出来的List与new ArrayList得到的List,压根就不是一个List!类关系图如下:
区别:Arrays.asList 构建的集合,不能赋值给ArrayList
Arrays.asList 构建的集合,不能再添加元素
Arrays.asList 构建的集合,不能再删除元素
方式四:Collections.ncopies
//这会初始化一个由10个0组成的集合。
ArrayList<Integer> list = new ArrayList<Integer>(Collections.nCopies(10, 0));
插入
普通插入
/**
* Appends the specified element to the end of this list.
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList默认初始化时会申请10个长度的空间,如果超过这个长度则需要进行扩容,那么它是怎么扩容的呢?
从根本上分析来说,数组是定长的,如果超过原来定长长度,扩容则需要申请新的数组长度,并把原数组元素拷贝到新数组中,如下图;
1. 判断长度充足;ensureCapacityInternal(size + 1);
2. 当判断长度不足时,则通过扩大函数,进行扩容; grow(int minCapacity);
3. 扩容的长度计算;int newCapacity = oldCapacity + (oldCapacity >> 1); 旧容量右移1位,这相当于扩容了原来容量的 (int)3/2 。例如:4或10扩容时:1010 + 1010 >> 1 = 1010 + 0101 = 10 + 5 = 15;2或7扩容时:0111 + 0111 >> 1 = 0111 + 0011 >> 7 + 3 = 10;
4. 当扩容完以后,就需要进行把数组中的数据拷贝到新数组中,这个过程会用到Arrays.copyOf(elementData, newCapacity); 但他的底层用到的是System. arraycopy
指定位置插入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
1. 判断size是否可以插入。
2. 判断插入后是否需要扩容; ensureCapacityInternal(size + 1);
3. 数据元素迁移,把从待插入位置后的元素,顺序往后迁移。
4. 给数组的指定位置赋值,也就是把待插入元素插入进来。
删除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if(numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
删除的过程主要包括;
1. 校验是否越界; rangeCheck(index);
2. 计算删除元素的移动长度numMoved,并通过System.arraycopy自己把元素复制给自己。
3. 把结尾元素清空,null。
扩展
如果给你一组元素;a、b、c、d、e、f、g,需要你放到ArrayList中,但是要求获取一个元素的时间复杂度都是O(1),你怎么处理?
想解决这个问题,就需要知道元素添加到集合中后知道它的位置,而这个位置呢,其实可以通过哈希值与集合长度与运算,得出存放数据的下标,如下图;
public static void main(String[] args) {
List <String> list = new ArrayList <String> (Collections. <String> nCopies(8, "0"));
list.set("a".hashCode() & 8 - 1, "a");
list.set("b".hashCode() & 8 - 1, "b");
list.set("c".hashCode() & 8 - 1, "c");
list.set("d".hashCode() & 8 - 1, "d");
list.set("e".hashCode() & 8 - 1, "e");
list.set("f".hashCode() & 8 - 1, "f");
list.set("g".hashCode() & 8 - 1, "g");
System.out.println("元素集合:" + list);
System.out.println("获取元素f [\"f\".hashCode() & 8 - 1)] Idx:" + ("f".hashCode() & (8 - 1)) + " 元素:" + list.get("f".hashCode() & 8 - 1));
System.out.println("获取元素e [\"e\".hashCode() & 8 - 1)] Idx:" + ("e".hashCode() & (8 - 1)) + " 元素:" + list.get("e".hashCode() & 8 - 1));
System.out.println("获取元素d [\"d\".hashCode() & 8 - 1)] Idx:" + ("d".hashCode() & (8 - 1)) + " 元素:" + list.get("d".hashCode() & 8 - 1));
}