阅读ArrayList源码的一些记录
ArrayList的底层是基于数组实现的,但是我们知道数组的长度一旦确定就不能够再次变化,ArrayList的长度是可以变化的,其实就是在需要扩容的时候,重新生成一个数组,并把原数组中的元素放到新的数组中,用新的数组替代就得数组,就完成了ArrayList的扩容。
本文是基于JDK1.8的源码,同时也会提到一些和JDK1.6的一些差别
一、构造方法
1、给定初始大小的构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {// 如果大于0,就按照给定的大小来初始化数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {// 如果等于0,则初始化为一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {// 如果小于0,则直接抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
2、无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此处的无参构造方法是初始化一个空的数组,相比于1.6来说,无参构造方法有一点变化,在1.6中,如果调用无参构造方法,会把elementData数组的长度初始化为10
3、以传入的Collection为基础构建ArrayList
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;
}
}
这个构造方法没什么说的,就是把传入的集合转化为数组,然后放到elementData中
下面按照增删改查依次说明
二、增
1、在数组尾部插入
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保长度够用,否则就扩容
elementData[size++] = e;// 在数组尾部添加元素
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//用无参构造方法生成对象的时候,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 取其中比较大的值
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 是否需要扩容,如果需要就调用grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;// 原来的容量
// 正常的扩容原则 原长度 + 原长度的一半(只取整数部分)(eg:1、原长度是10则正常扩容长度为10 + (10 >> 1)=15 2.原长度为11,则正常扩容以后的长度为11 + (11 >> 1) = 16)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果按照正常的扩容算法扩容后的长度还不能达到要求,则按照传进来的长度进行扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)// 这种情况一般不会发生,除非你往List中添加了很多很多数据
newCapacity = hugeCapacity(minCapacity);
// 把原来的数据放到新的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 如果小于0就表示溢出了
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
JDK1.6的扩容原则和这个有一定的差别,没有那么复杂,可以看一下1.6的扩容原则
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;// 原长度
if (minCapacity > oldCapacity) {// 是否需要扩容
Object oldData[] = elementData;
// 正常的扩容算法,原长度的3/2 + 1
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)// 不满足要求,则把传入的长度作为扩容后的长度
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
2、在指定位置插入
public void add(int index, E element) {
rangeCheckForAdd(index);// 检测index是否合法(0 < index < size)
ensureCapacityInternal(size + 1); // 是否要扩容
// 把index以及其后的元素往后移动,由此可以看出在特定位置插入数据的效率并不高
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
3、把一个集合的元素插入到数组的尾部
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();// 把集合转为数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 是否需要扩容
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
4、把一个集合插入到数组的特定位置
和插入一个元素的套路一样,此处只贴一下代码
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
三、删
1、按照索引删除
public E remove(int index) {
rangeCheck(index);// 检查index是否大于等于size,如果是会抛出异常
modCount++;
E oldValue = elementData(index);// 获取指定索引的上的值
int numMoved = size - index - 1;
if (numMoved > 0)
// 把index后面的元素前移,所以移除的效率不高
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2、按照值来删除
如果有此值,删除成功返回true,否则返回false
// 此方法是从头开始循环查找,找到以后就删除并返回,后面相同的值不会被删除
public boolean remove(Object o) {
// 分是否为null两种情况,然后循环查找,如果找到就删除
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;
}
// 方法相比于remove(int)来说,减少了index的合法性判断,以及旧值的获取
private void fastRemove(int index) {
modCount++;
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
}
3、清空
// 循环把数组的每个元素都置为null
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
四、改
public E set(int index, E element) {
// 校验index的合法性
rangeCheck(index);
// 获取旧值,返回时用
E oldValue = elementData(index);
// 修改值
elementData[index] = element;
return oldValue;
}
五、查
public E get(int index) {
rangeCheck(index);// 校验index的合法性
return elementData(index);// 获取指定索引上的值
}
六、其他的一些方法
1、获取List的长度
因为ArrayList维护了一个size成员变量来表示其长度,直接获取size的长度即可
public int size() {
return size;
}
2、集合是否为空集合
长度为空就是空集合
public boolean isEmpty() {
return size == 0;
}
3、是否包含指定的元素
遍历去比较
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
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;
}
七、ArrayList的遍历
以下都是个人的测试代码
三种遍历方式
public void testArrayList(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// 第一种方法
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
// 第二种
for(String str : list){
System.out.println(str);
}
// 第三种,迭代器
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
下面我们看一种情形,就是删除List中的所有与指定值相同的值
第一种(并不能达到我们预想的结果)
public void testFor(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
for(int i = 0; i < list.size(); i ++){
if("a".equals(list.get(i))){
list.remove(i);
}
}
// 经过删除以后,list中的元素为a,b,c,d,a显然没有达到我们要删除所有的a的目的
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
第二种(迭代器方法 推荐使用)
public void testiter(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
Iterator<String> iterator2 = list.iterator();
while(iterator2.hasNext()){
String val = iterator2.next();
if("a".equals(val)){
iterator2.remove();
}
}
// 经过删除后 list 中的元素为b,c,d 得到了我们所期望的结果
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
第三种方法
public void testFor2(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
for(int i = 0; i < list.size(); i ++){
if("a".equals(list.get(i))){
list.remove(i);
i --;
}
}
// 经过删除以后,list中的元素为b,c,d 同样达到了我们的要求,不过不建议用这种方法,推荐用迭代器去删除
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
由于比较好奇foreach的实现就写了两个测试方法,代码如下
public void testForEach(){
List<String> list = new ArrayList<>();
for(String str : list){
System.out.println(str);
}
}
public void testIter(){
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
通过idea反编译后生成的内容如下
public void testForEach() {
List<String> list = new ArrayList();
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
System.out.println(str);
}
}
public void testIter() {
List<String> list = new ArrayList();
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
String s = (String)iterator.next();
System.out.println(s);
}
}
然后顺带用javap去看了一下字节码文件,发现两个方法的字节码几乎是一样的,如下图
由此可见foreach是依赖于迭代器实现的。