博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JDK源码分析(3)之 ArrayList 相关
阅读量:4973 次
发布时间:2019-06-12

本文共 7082 字,大约阅读时间需要 23 分钟。

ArrayList的源码其实比较简单,所以我并没有跟着源码对照翻译,文本只是抽取了一些我觉得有意思或一些有疑惑的地方分析的。

一、成员变量

private static final int DEFAULT_CAPACITY = 10;                        // 默认容量private static final Object[] EMPTY_ELEMENTDATA = {};                  // 空实例的空数组对象private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  // 也是空数组对象,用于计算添加第一个元素时要膨胀多少transient Object[] elementData;                                        // 存储内容的数组private int size;                                                      // 存储的数量

其中elementData被声明为了transient,那么ArrayList是如何实现序列化的呢?

查看writeObjectreadObject的源码如下:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {  // Write out element count, and any hidden stuff  int expectedModCount = modCount;  s.defaultWriteObject();    // Write out size as capacity for behavioural compatibility with clone()  s.writeInt(size);    // Write out all elements in the proper order.  for (int i=0; i

可以看到在序列化的时候是把elementData里面的元素逐个取出来放到ObjectOutputStream里面的;而在反序列化的时候也是把元素逐个拿出来放回到elementData里面的;

这样繁琐的操作,其中最重要的一个好处就是节省空间,因为elementData的大小是大于ArrayList中实际元素个数的。所以没必要将elementData整个序列化。

二、构造函数

ArrayList的构造函数主要就是要初始化elementDatasize,但是其中有一个还有点意思

public ArrayList(Collection
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.toArray()之后又判断了他的Class类型是不是Object[].class,这个也注释了是一个bug,那么在什么情况下会产生这种情况呢?

// test 6260652private static void test04() {  List
list = Arrays.asList("111", "222", "333"); System.out.println(list.getClass()); Object[] objects = list.toArray(); System.out.println(objects.getClass());}打印:class java.util.Arrays$ArrayListclass [Ljava.lang.String;

这里可以看到objectsclass居然是[Ljava.lang.String,同时这里的ArrayListjava.util.Arrays.ArrayList

// java.util.Arrays.ArrayListpublic static 
List
asList(T... a) { return new ArrayList<>(a);}private static class ArrayList
extends AbstractList
implements RandomAccess, java.io.Serializable { private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } ...}

从以上例子可以看到,由于直接将外部数组的引用直接赋值给了List内部的数组,所以List所持有的数组类型是未知的。之前讲过数组是协变的,不支持泛型,所以只有值运行时再知道数组的具体类型,所以导致了以上的bug;难怪《Effective Java》里面讲数组的协变设计,不是那么完美。

三、常用方法

由于ArrayList是基于数组的,所以他的api基本都是基于System.arraycopy()实现的;

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

可以看到这是一个native方法,并且JVM有对这个方法做特殊的优化处理,

private static void test05() {  int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};  int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9};    System.arraycopy(s1, 3, s2, 6, 2);  System.out.println(Arrays.toString(s1));  System.out.println(Arrays.toString(s2));}打印:[1, 2, 3, 4, 5, 6, 7, 8, 9][1, 2, 3, 4, 5, 6, 4, 5, 9]private static void test06() {  int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};  int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9};    System.arraycopy(s1, 3, s2, 6, 5);  System.out.println(Arrays.toString(s1));  System.out.println(Arrays.toString(s2));}// 抛出异常`java.lang.ArrayIndexOutOfBoundsException`private static void test07() {  int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};  int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9};    System.arraycopy(s1, 8, s2, 5, 3);  System.out.println(Arrays.toString(s1));  System.out.println(Arrays.toString(s2));}// 抛出异常`java.lang.ArrayIndexOutOfBoundsException`

从上面的测试可以了解到:

  • System.arraycopy()就是将源数组的元素复制到目标数组中,
  • 如果源数组和目标数组是同一个数组,就可以实现数组内元素的移动,
  • 源数组和目标数组的下标越界,都会抛出ArrayIndexOutOfBoundsException

同时在ArrayList进行数组操作的时候都会进行安全检查,包括下标检查和容量检查

// 容量检查public void ensureCapacity(int minCapacity) {  int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)  // any size if not default element table ? 0  // larger than default for default empty table. It's already  // supposed to be at default size.  : DEFAULT_CAPACITY;    if (minCapacity > minExpand) {    ensureExplicitCapacity(minCapacity);  }}

四、迭代方式

1. 随机访问

由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。

for (int i=0, len = list.size(); i < len; i++) {    String s = list.get(i);}

2. 迭代器遍历

这其实就是迭代器模式

Iterator iter = list.iterator();while (iter.hasNext()) {    String s = (String)iter.next();}

3. 增强for循环遍历

这其实是一个语法糖

for (String s : list) {    ...}

4. 增强for循环遍历的实现

  • 对于ArrayList
public void test_List() {  List
list = new ArrayList<>(); list.add("a"); list.add("b"); for (String s : list) { System.out.println(s); }}

使用javap -v 反编译

Code:  stack=2, locals=4, args_size=1...    27: invokeinterface #7,  1    // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;    32: astore_2    33: aload_2    34: invokeinterface #8,  1    // InterfaceMethod java/util/Iterator.hasNext:()Z...

这里可以很清楚的看到,其实是通过Iterator迭代器实现的

  • 对于Array
public void test_array() {  String[] ss = {"a", "b"};  for (String s : ss) {    System.out.println(s);  }}

使用javap -v 反编译

Code:  stack=4, locals=6, args_size=1     0: iconst_2     1: anewarray     #2    // class java/lang/String     4: dup     5: iconst_0                 6: ldc           #3    // String a     8: aastore     9: dup    10: iconst_1    11: ldc           #4    // String b    13: aastore    14: astore_1    15: aload_1    16: astore_2    17: aload_2    18: arraylength    19: istore_3    20: iconst_0    21: istore        4    // 将一个数值从操作数栈存储到局部变量表    23: iload         4    // 将局部变量加载到操作栈    25: iload_3    26: if_icmpge     49    29: aload_2    30: iload         4    32: aaload    33: astore        5    35: getstatic     #5    // Field java/lang/System.out:Ljava/io/PrintStream;    38: aload         5    40: invokevirtual #6    // Method java/io/PrintStream.println:(Ljava/lang/String;)V    43: iinc          4, 1    46: goto          23    49: return

这里只能到导致看到是在做一些存取操作,但也不是很清楚,所以可以直接反编译成java代码

public void test_array() {  String[] ss = new String[]{"a", "b"};  String[] var2 = ss;  int var3 = ss.length;    for(int var4 = 0; var4 < var3; ++var4) {    String s = var2[var4];    System.out.println(s);  }}

现在就能很清楚的看到其实是通过随机存储(下标访问)的方式实现的;

五、fail-fast机制

fail-fast是说当并发的对容器内容进行操作时,快速的抛出ConcurrentModificationException;但是这种快速失败操作无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常。所以我们程序的正确性不能完全依赖这个异常,只应用于bug检测。

protected transient int modCount = 0;private void checkForComodification() {  if (ArrayList.this.modCount != this.modCount)    throw new ConcurrentModificationException();}

ArrayListIteratorSubList中,每当进行内存操作时,都会先使用checkForComodification来检测内容是否已修改。

总结

ArrayList整体来看就是一个更加安全和方便的数组,但是他的插入和删除操作也实在是蛋疼,对于这一点其实可以通过树或者跳表来解决。

转载于:https://www.cnblogs.com/sanzao/p/10156117.html

你可能感兴趣的文章
综合练习:词频统计
查看>>
BZOJ1026: [SCOI2009]windy数
查看>>
样板操作数
查看>>
64位UBUNTU下安装adobe reader后无法启动
查看>>
组件:slot插槽
查看>>
Nginx配置文件nginx.conf中文详解(转)
查看>>
POJ 1308 Is It A Tree?(并查集)
查看>>
N进制到M进制的转换问题
查看>>
利用sed把一行的文本文件改成每句一行
查看>>
Android应用开发:核心技术解析与最佳实践pdf
查看>>
python——爬虫
查看>>
孤荷凌寒自学python第五十八天成功使用python来连接上远端MongoDb数据库
查看>>
求一个字符串中最长回文子串的长度(承接上一个题目)
查看>>
简单权限管理系统原理浅析
查看>>
springIOC第一个课堂案例的实现
查看>>
求输入成绩的平均分
查看>>
php PDO (转载)
查看>>
wordpress自动截取文章摘要代码
查看>>
[置顶] 一名优秀的程序设计师是如何管理知识的?
查看>>
scanf和gets
查看>>