目录
ArrayList JDK1.7 版本底层扩容机制 实现源码解读:
ps:JDK1.7的ArrayList 的 ArrayList(Collection c) 构造器 源码解读:
ArrayList JDK1.8 版本底层扩容机制 实现源码解读:
ps:JDK1.8的ArrayList 的 ArrayList(Collection c) 构造器 源码解读:
JDK1.7 、JDK1.8 的ArrayList 和 Vector 区别总结:
说明:
- ArrayList 、Vectory 的底层是通过 Object的 数组 来 实现的。
- 数组:在Java中是一种引用类型。在数据结构中,数组就是一种 紧密结构 之一的 一种数据结构!
- ArrayList 、Vector 底层的算法,就是 通过数组的 copy 来实现的。巧妙的结合了 很多编程思想在里面。
- 数组的表现形式:如图所示:
数组的优缺点:
数组的优点:
(1)相对于变量来说,数组可以存放大量的数据。
(2)按照索引查询元素 速度快。
(3)数组存储是:是有序的,可重复的。
(4)按照索引遍历数组方便。数组定义简单。并且访问也方便。可以随机访问其中的元素。
数据的缺点:
(1)一旦指定了长度,那么数组长度就被确定了,不可以更改,不适合动态存储。
(2)只能存储一种数据类型的数据。
(3)增加、删除元素效率慢。
(4)官方 只提供了一个 length 属性能够获取数组中应该存储多少个元素,
而 数组中实际元素的数量 是没有办法获取的,没有提供对应的方法 或者是属性 来获取。
(5)数组的空间必须是连续的。造成数组在内存中分配空间时 必须找到一块连续的内存空间。
所以说数组不可能定义得太大。因为内存中不可能有那么多 大的连续的内存空间来供数组使用。
解决这个问题的办法 就是使用链表。
ArrayList JDK1.7 版本底层扩容机制 实现源码解读:
(源码和StringBuilder 以及 StringBuffer 及其相似)
(以JDK1.7.0_21为例:)
(JDK1.7.0_79 这是JDK1.7的最后一个小版本,源码和JDK1.8差不多,所以直接读 1.8)
无参构造器创建ArrayList对象:
ArrayList中两个重要的属性:
跟进:
step 1:
step 2:
调用add方法,查看ArrayList集合中的 扩容机制:此时 ArrayList 中 elementDate 数组的初始化长度 为 10:
跟进:
step 1 :
step 2:
step 3:
再次添加执行触发数组的扩容:
step 1:
step 2:
step 3:
step 4:
step 5:
step 6: (debug时不会进来)
step 7 :一直 return,return到没有执行的代码位置:
step 8:一路 结束方法弹栈消失 , 回到 add方法中:
step 9: 添加元素完毕,add方法弹栈。回到调用 add 方法的地方。
ps:JDK1.7的ArrayList 的 ArrayList(Collection<? extends E> c)
构造器 源码解读:
public ArrayList(Collection<? extends E> c) {
//step 1: 把入参集合转成 Object数组,让当前 初始化的集合对象的 elementData数组指向
elementData = c.toArray();
// step 2: 拿到 入参集合的长度
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// step 3:
// 如果 elementData数组的类型不是Object数组的类型
// 就进行 数组的拷贝,再让elementData指向
if (elementData.getClass() != Object[].class)
// 发生这个 数组拷贝的动作 除非调用的 toArray 的返回值不是 Object 类型的。
// 继承ArrayList 或父类、或实现父接口 把它的 toArray重写就 可以执行这个代码。
// 比如看以下 测试 代码:👇
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
test:
private static class TestList<E> extends ArrayList<E>{
@Override
public String[] toArray() {
return this.toArray(new String[0]);
}
}
TestList<String> testList = new TestList<>();
testList.add("第一");
testList.add("第二");
testList.add("第三");
testList.add("第四");
// 通过这种方式创建集合,如果正在实例化的集合 给定的 泛型数据类型
// 不是传入的集合 给定的 泛型类型的本类型的话。
// 是它的父类的话,那么在实例化集合的时候,
// 调用构造器的代码 new ArrayList<CharSequence> 泛型 也要指定才行!不然
// public ArrayList(Collection<? extends E> c) 的 Collection<? extends E>
// 的 泛型上限通配,不好检查是否编译通过(也可以理解为这里的自动推断类型失效)
ArrayList<CharSequence> arrayList = new ArrayList<CharSequence>(testList);
System.out.println("arrayList = " + arrayList);
debug 构造器 --->
ArrayList JDK1.8 版本底层扩容机制 实现源码解读:
说明:JDK1.8 版本后的 源码和 JDK1.7 的源码只有细微的差别。
通过ArrayList 无参构造器 看底层扩容机制:
step 1:JDK1.8版本后 源码中几个关键的属性:
跟进:
step 1:
使用add方法,跟进:
step 1:
step 2:
step 3:
注:calculateCapatity只会在第一次添加元素的时候进入if分支。初始化长度为 10
step 4:
进入到 ensureExplicitCapacity(确保明确的容量方法)
step 5:
step 6:
step 7:
System.arraycopy 方法debug时不会进入:
step 8:一路return 到grow方法
step 9:grow执行结束弹栈消失,一路的方法弹栈消失,回到 add 方法
step 10:add 方法结束,回到调用 add 方法的地方。
step 11:,这和JDK1.7的机制是一样的。所以不再跟进。
在没有调用ArrayList的,传入一个初始化容量的构造器和 当添加元素时 手动去 指定最小容量
的时候,调用 ArrayList的无参构造器的初始化的方式 调用add方法时当数组容量不够时 扩容递增顺序就是: 10 -> 15 -> 22 - > 33 -> 49 -> 73 -> 109 -> 163 -> 244 -> 366 -> ……,每次新数组的长度扩容的倍数,都是 旧数组长度的 1.5 倍
ps:JDK1.8的ArrayList 的 ArrayList(Collection<? extends E> c)
构造器 源码解读:
public ArrayList(Collection<? extends E> c) {
//step 1: 把当前传入建立的集合转成 Object 数组
Object[] a = c.toArray();
//step 2: 把数组的长度赋值给 当前正在实例化的集合对象。并且 size 不等于 0
if ((size = a.length) != 0) {
// step 2.1:如果传入进当前正在实例化集合的集合的运行时类型是 ArrayList 类型
if (c.getClass() == ArrayList.class) {
// step 2.1.1: 就让当前实例化的 集合对象的 elementData数组 指向
// 传入进来的ArrayList集合 的elementData 数组
elementData = a;
} else {
// step 2.2: 如果不是,则进行数组的拷贝,再让elementData 指向
// 在 Arrays.copyOf方法中创建的新数组!
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
// step 3: 如果传入进来的集合是没有元素的,
// 即传入进来的集合的elementData的长度为0时,则当前实例化的集合对象的
// elementData 数组 指向 空数组,(长度为0)
elementData = EMPTY_ELEMENTDATA;
}
}
Vector :
说明:
Vector 的使用和 ArrayList 的使用是一样的,且底层也是通过数组来实现的。只是相同的API 由ArrayList 实现的 换成了 Vector 来实现了而已。相同功能的方法名不一样。当然,Vector 也有自己有 且 ArrayList 没有的API。相同的就不做赘述了。不同的使用一下。不过 Vector 在我实际开发中,没用到过。但我被面试问到过。
Vector 有且ArrayList没有的API基本使用:
public static void vectorExclusivelyMethodUse() {
Vector<String> vector = new Vector<>();
vector.add("埃及胡夫金字塔");
vector.add("巴比伦空中花园");
vector.add("阿尔忒弥斯神庙");
vector.add("奥林匹亚宙斯神像");
vector.add("摩索拉斯陵墓");
vector.add("罗德岛太阳神巨像");
vector.add("亚历山大灯塔");
vector.add("秦陵兵马俑");
//void copyInto(Object[] anArray)
//将此向量的组件复制到指定的数组中
// 如果数组的类型不是 Vector 的泛型给定的数组类型或父类类型,
// 则抛出 java.lang.ArrayStoreException
CharSequence[] str = new String[vector.size()];
vector.copyInto(str);
System.out.println(Arrays.toString(str));
// (和ArrayList 的 public E get(int index) 方法功能是一样的)
// E elementAt(int index)
//返回指定索引处的组件。
//Enumeration<E> elements()
//返回此向量的组件的枚举。
// (此枚举非彼枚举,只是接口名的意思是:枚举)
// 可用这种方法 遍历 Vector
Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()) {
String element = elements.nextElement();
System.out.println("element = " + element);
}
//void insertElementAt(E obj, int index)
// 此方法和 ArrayList 的
// public void add(int index, E element) 方法功能是一样的
//在指定的index插入指定对象作为该向量中的一个 index 。
vector.insertElementAt("万里长城", vector.size());
System.out.println("vector = " + vector);
//E lastElement()
// 等同于 ArrayList的get方法的
// list.get(list.size() -1) 方式调用
//返回向量的最后一个组件
// void setElementAt(E obj, int index)
// E set(int index, E element) , index 不能大于 最大索引。
// 否则 抛出 java.lang.ArrayIndexOutOfBoundsException:
// 用指定的元素替换此Vector中指定位置的元素。
// 和 ArrayList的 public E set(int index, E element) 功能是一样的
//设置在指定的组件 index此向量的要指定的对象。
vector.setElementAt("吴哥窟", vector.size() - 2);
System.out.println("vector = " + vector);
vector.set(vector.size() - 1, "罗马斗兽场");
System.out.println("vector = " + vector);
//void setSize(int newSize)
//设置此向量的大小。(如果长度小于 Vector的 size,长度后的元素将删除)
vector.setSize(6);
System.out.println("vector = " + vector);
// int capacity()
//返回此向量的当前容量。
System.out.println("vector.capacity() = " + vector.capacity());
}
result:
Vector 扩容机制 实现源码解读:
使用无参构造器跟进:
Vector中三个重要的属性:
step 1:
step 2:
step 3:
实例化完成:
往Vector集合中添加 11个元素,看第11个元素的扩容机制:
step 1:
step 2:
step 3:
step 4:方法弹栈消失。回到 add 方法中:
Vector 其他两个 构造器:
public Vector(Collection<? extends E> c) {
Object[] a = c.toArray();
elementCount = a.length;
// 1: 如果是 ArrayList 集合
if (c.getClass() == ArrayList.class) {
// 2: 则把 ArrayList集合的 elementData 赋值给 当前 Vectory实例的elementData指向。
elementData = a;
} else {
// 3: 如果不是则 先数组拷贝。再赋值指向。
elementData = Arrays.copyOf(a, elementCount, Object[].class);
}
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
// 如果用户给的 初始化长度是 小于 0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 初始化的elementData的数组的长度为 用户指定的长度。
this.elementData = new Object[initialCapacity];
// 当前Vector实例的 容量增量是用户给的为准。
this.capacityIncrement = capacityIncrement;
}
// 如果使用此构造器实例化 Vector对象时。当集合中的元素的数量超过。elementData数组的容量时。则
// Vector 底层的扩容的新数组的长度每次都是 : 旧数组的容量 + 用户给定的容量增量。
JDK1.7 、JDK1.8 的ArrayList 和 Vector 区别总结:
- JDK1.7的ArrayList的 扩容机制源码。JDK1.7的源码是 ,在实例化这个 ArrayList 集合对象的时候。不管 调用者初始化完ArrayList对象后 继不继续 使不使用,在初始化对象的时候,都会创建一个长度为 10 的 Object 的数组(elementData),当调用者继续使用时 添加元素 到集合中的容量 达到 elementData的当前最大容量时。 就会进行 对 elementData 数组的 扩容。扩容的新数组的长度 为 就旧数组长度的 1.5 倍。
- JDK1.8的ArrayList的 扩容机制源码。JDK1.8的源码是,在实例化这个 ArrayList 集合对象的时候。不会去创建Object 的数组。底层是长度为0的Object 数组。只有在调用者第一次往集合中添加元素的时候,才会对 elementData 数组扩容 至长度为 10 的新数组。当调用者 添加的元素 到集合中的容量 达到 elementData的当前最大容量时。 就会进行 对 elementData 数组的 扩容。扩容的新数组的长度 为 就旧数组长度的 1.5 倍。(这和1.7的扩容机制是一样的)
- 只是它们两个版本 的源码最大的不同点是。 JDK1.7 的源码在 实例化 ArrayList 集合对象的时候,就会去创建 一个长度为 10 的Object 数组(elementData)。不管调用者实例化这个对象后要不要继续往集合中添加元素。在内存中都有个 长度为 10 的Object 数组。在一定的程度上,有点造成了资源的浪费。
- 而 JDK1.8 的源码在实例化 ArrayList 集合对象的时候,不会去创建 新数组,此时elementData数组的长度为0。 只有当 调用者确定往集合中添加元素时。第一次才会去对 elementData数组进行扩容(也就是创建一个长度为 10 的新数组让 elementData 指向)。JDK1.8 源码 改良后的 体现思想是为开发者 节省了内存。在调用 add 方法后才会去创建新数组。
-
ArrayList 与 Vector 的相同点:
- 它们的底层都是通过 数组 来实现存储的。且 数组的类型都是Object。且数组的 变量名 是 elementData 。
- 它们 的查询元素效率高。增、删元素效率低。
- 它们的元素都是有序、可重复的。
- 如果是使用无参构造器初始化ArrayList、Vector的实例的话:JDK1.7的 ArrayList 的 elementData 数组在是实例化 ArrayList对象的时候就已经创建好了,长度为 10。JDK1.8 的ArrayList 的 elementData 数组在实例化的时候没有创建。长度为0,在用户 第一次添加元素的时候,才会先创建 elmentData 数组。长度为 10。Vector 的elementData数组也是在实例化的时候就已经创建好了。长度为10。(Vector实例化和JDK1.7的ArrayList实例化的机制是一样的。)。它们在初始化elementData数组的共同点是。第一次初始化 elementData 数组的长度 都为 10!
-
ArrayList 与 Vector 的不同点:
- 底层扩容机制不一样,在是否多线程的环境下。效率也不一样。
- 当使用ArrayList时 添加的元素的数量大于 当前elementData数组的最大容量时,ArrayList 底层扩容 elementData数组的容量是 原数组容量 的1.5倍。 ArrayList 是线程不安全的、但是效率高。
- 当使用Vector时,如果不是使用 Vector 的 有个有参构造器 可以 自定义初始化容量、和自定义初始化容量增量的构造器实例化 Vector的实例的 话。那么当 添加 的元素的数量大于 当前elementData数组的最大容量时。Vectory 底层扩容elementData数组的容量是 原数组的 2 倍。Vector 的 增加、修改、查询、删除 API都被 synchronized 关键字所修饰。说明 Vector 是线程安全的。但是 它的效率低。所以使用频率没有 ArrayList 高。
- 底层扩容机制不一样,在是否多线程的环境下。效率也不一样。
下一篇:Collection集合接口:List 集合子接口下的LinkedList的基本使用、模拟双向链表存储元素、LinkedList 源码阅读理解总结: