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_ELEMENTDATAminCapacity = 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 + 1int 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 modCountint 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 workreturn 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 workfor (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是依赖于迭代器实现的。

阅读ArrayList源码的一些记录相关推荐

  1. 阅读ConcurrentHashMap源码的一些记录

    在多线程的环境下,HashMap不能满足我们的要求,而Hahstable效率太低,一般会推荐大家用ConcurrentHashMap,ConcurrentHashMap使用分段锁,能够让锁的粒度更小, ...

  2. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  3. ArrayList源码详细解析(一)

    Java ArrayList源码解析(基于JDK 12,对比JDK 8) 自从接触到ArrayList以来,一直觉得很方便,但是从来没有系统.全面的学习了解过ArraryList的实现原理.最近学习了 ...

  4. java1.8 indexes_java1.8源码之ArrayList源码解读

    文章目录 一.ArrayList概述1.1 ArrayList简介1.2 ArrayList数据结构 二.ArrayList源码分析2.1 ArrayList继承结构和层次关系2.2 类的属性2.3 ...

  5. JDK12下的ArrayList源码解读 与 Vector的对比

    ArrayList源码阅读. //测试代码实现如下 private static void arrayList() {ArrayList<String> list = new ArrayL ...

  6. 扩容是元素还是数组_02 数组(附ArrayList源码分析)

    定义 用一组连续的内存空间存储一组具有相同类型的数据的线性表数据结构. 优势 支持通过下标快速的随机访问数据,时间复杂度为O(1). 劣势 通常情况下,插入和删除效率低下,每次操作后,需要进行后续元素 ...

  7. 增加数组下标_数组以及ArrayList源码解析

    点击上方"码之初"关注,···选择"设为星标" 与精品技术文章不期而遇 前言 前一篇我们对数据结构有了个整体的概念上的了解,没看过的小伙伴们可以看我的上篇文章: ...

  8. 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates

    阅读react-redux源码 - 零 阅读react-redux源码 - 一 阅读react-redux源码(二) - createConnect.match函数的实现 阅读react-redux源 ...

  9. 面试官系统精讲Java源码及大厂真题 - 05 ArrayList 源码解析和设计思路

    05 ArrayList 源码解析和设计思路 耐心和恒心总会得到报酬的. --爱因斯坦 引导语 ArrayList 我们几乎每天都会使用到,但真正面试的时候,发现还是有不少人对源码细节说不清楚,给面试 ...

  10. 手把手带你阅读Mybatis源码(三)缓存篇

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 前言 大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读M ...

最新文章

  1. .NET操作注册表的封装类
  2. 一步一个脚印学习WCF系列之WCF概要—WCF出现的目的(一)
  3. 解决VMware虚拟机时间同步问题
  4. 03-JDBC学习手册:JDBC中几个重要接口和异常处理
  5. 问题:二进制数中1的个数。
  6. android获取详细地址,Android获取当前子网掩码地址(亲测可用)
  7. python 3d绘图模块_使用python和mayavi创建3D streamplot
  8. CSDN认证博客专家申请通过,感动,欣慰,分享一下我的经验!
  9. 书籍翻译 - Fundamentals of Computer Graphics, Fourth Edition 虎书第四版中文翻译
  10. Leetcode总结之Union Find
  11. 如何实现复制文本到剪贴板?
  12. 东瓜小知识之《简单查询网卡的厂商信息》
  13. [CTF]使用浏览器firefox插件伪装IP地址
  14. 微信服务商分账功能 PHP
  15. 特色图像尺寸css,Wordpress 3.2.1特色图像尺寸和裁剪(Wordpress 3.2.1 Featured Image Size and Crop)...
  16. 运算符、||运算符、?.可选链运算符、? ?空位合并运算符
  17. 别再羡慕马斯克的脑机接口了!中国强大的脑机接口在这里
  18. DCA1000EVM使用说明
  19. 各种常见的博弈论 + 简单例题
  20. [渝粤教育] 西南科技大学 土力学基础工程 在线考试复习资料(1)

热门文章

  1. u8服务器配置维护,u8客户端连接服务器配置
  2. 批量调取接口_调用API接口批量查手机归属地
  3. 使用Excel办公,你必须学会的文件加密、单元格锁定、复制可见单元格
  4. 【微服务】使用yml格式进行nacos拓展配置
  5. SM2258XT+B17A测试(焊接+开卡+测速+跑圈)
  6. 你在杠杆另外一端的位置,决定你是否能够撬动地球
  7. JAVASE复习计划
  8. java nutch 爬虫_Java分布式爬虫Nutch教程——导入Nutch工程,执行完整爬取
  9. html留言页面设计,html的留言板制作(js)
  10. Gps开发实战——卫星数量获取