`
iamxi
  • 浏览: 191896 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java源码解读——ArrayList(二)

阅读更多

看了下ArrayList的源码,记录下自己的解读心得与体会。

 

ArrayList的自动变长机制

都知道ArrayList不像数组那样是定长的,然而ArrayList也使用了数组来保存数据,所以么,自然很关心是怎么实现变长的。

 

ArrayList通过ensureCapacityInternal(int minCapacity)方法实现自身容量的增加,在add()和addAll()方法里面都调用了改方法。

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

 方法名的英文大致意思是“确保内部容量”,这里要说明,size表示的是现有的元素个数,并非ArrayList的容量,容量应该是数组elementData的长度。参数minCapacity是需要检查的最小容量,即方法的功能就是确保elementData的长度不小于minCapacity,如果不够,则调用grow增加容量。容量的增长也算结构性变动,所以modCount需要加1。

 

grow代码:

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

 先对容量扩大1.5倍,这里oldCapacity >> 1是二进制操作右移,相当于除以2,如果不知道这个面壁去吧(刚看到时候也没反应过来这个是什么操作,毕竟平时java中很少用到左移、右移这种运算,没事谢个测试用例测试下计算效果就知道是干什么的了)。接再来把新的临时容量(还没正式改变容量,应该叫预期容量)和实际需要的最小容量比较,如果还不满足,则把临时容量改成需要的最小容量值。在判断容量是否超过MAX_ARRAY_SIZE的值,MAX_ARRAY_SIZE值为Integer.MAX_VALUE - 8,比int的最大值小8,不知道为什设计,可能方便判断吧。如果已经超过,调用hugeCapacity方法检查容量的int值是不是已经溢出。一般很少用到int最大值的情况,那么多数据也不会用ArrayList来做容器了,估计这辈子没机会见到hugeCapacity运行一次了。最后确定了新的容量,就使用Arrays.copyOf方法来生成新的数组,copyOf也已经完成了将就的数据拷贝到新数组的工作。

当然有增就有减,trimToSize()就是用来将elementData的长度变成size。

    public void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

 这样可以解决平凡新增、删除元素后elementData过大的问题。

 

从上面的操作来看,ArrayList的容量增长的开始还是有的,就算那些数字的计算忽略不计,Arrays.copyOf的操作的消耗也不会小。所以在初始化ArrayList的时候尽量预算下大致的容量需求,降低平凡调整容量的开销。

 

序列化

ArrayList实现了Serializable接口,所以ArrayList可以进行序列化。但是elementData的定义

private transient Object[] elementData;

 transient修饰符让elementData无法自动序列化,这样的原因是,数组内存储的的元素其实只是一个引用,单单序列化一个引用没有任何意义,反序列化后这些引用都无法在指向原来的对象。ArrayList使用writeObject()实现手工序列化数组内的元素。

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        int expectedModCount = modCount;
        s.defaultWriteObject();

        s.writeInt(elementData.length);

        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
 

迭代器实现

一般ArrayList需要遍历时,可以调用他的iterator()方法返回一个迭代器,然后用迭代器进行遍历。

    public Iterator<E> iterator() {
        return new Itr();
    }

 iterator()方法放回了一个Itr类实例,这个Itr类是ArrayList的一个内部类,实现了Iterator接口。

        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

上面是Itr类的三个成员变量。 cursor类似一个游标,指向迭代器下一个值的位置。lastRet是迭代器最后一次取出的元素的位置。expectedModCount的值为Itr初始化时候ArrayList的modCount的值,前面将过modCount用于记录ArrayList内发生结构性改变的次数,而Itr每次进行next或remove的时候都会去检查expectedModCount值是否还和现在的modCount值一直,从而保证了迭代器和ArrayList内数据的一致性。实现代码如下:

        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

 Itr作为ArrayList的内部类,可以访问所有ArrayList的成员。理解了这一点,itr类的实现就容易理解了。

 

还有一个listIterator的迭代器,实现方式类似。

 

 

java的集合在平时开发中很常用,能够了解它们的内部实现,对开发带来很大的便利,也减少了不必要的BUG。

 

分享到:
评论
4 楼 15201493793 2016-05-11  
大神arraylist完了,linkedList呢,set,map 啥的呢?继续啊
3 楼 unbachone 2015-10-24  
ensureCapacityInternal不是“确保内部容量”的意思,是为了区别于ensureCapacity方法,jdk1.6时是用ensureCapacity方法来确保容量的,1.7有了一些改进,而ensureCapacity变成了只供外部调用,而ArrayList类内部确保容量就使用了ensureCapacityInternal方法,所以在方法后面加了Internal。
2 楼 jssyjam 2015-09-22  
/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
     //这边减去8的意思是一些vms会在数组头加一些信息,如果不预留空间可能会发生内存溢出的错误
1 楼 jiangyeqt 2014-12-29  
transient修饰符让elementData无法自动序列化,这样的原因是,数组内存储的的元素其实只是一个引用,单单序列化一个引用没有任何意义,反序列化后这些引用都无法在指向原来的对象。ArrayList使用writeObject()实现手工序列化数组内的元素。
-----------------------------------------------------------------
这一段不敢苟同,亲自敲一敲就知道了。
集合中存储javaBean,序列化的是什么,是引用吗?其实是有状态的javaBean对象吧。反序列话回来会生成一个状态完全相同的javaBean实例。不知道我说的是否有纰漏,请指教。

相关推荐

    Java源码解析——看优秀源码最能使人进步

    Java源码解析——看优秀源码最能使人进步 Java源码解析是一种深入了解JDK源码的方式,适合那些想深入了解JDK源码的人。通过对JDK源码的解析,可以让开发者更好地理解Java语言的底层逻辑,从而写出更加高效、稳定的...

    java——ArrayList-源码解析.docx

    ArrayList 是 Java 中常用的集合类,它是 List 接口的一个实现,基于数组进行操作。ArrayList 的设计使得元素可以动态地添加和删除,同时提供了对数组大小的灵活调整。它实现了 ICollection 和 IList 接口,提供了...

    Java源码篇之容器类——ArrayList

    在Java编程语言中,ArrayList是集合框架中一个重要的容器类,属于List接口的实现,它提供了动态数组的功能。本文将深入解析ArrayList的源码,帮助开发者理解其内部工作原理。 1、前言 在日常开发中,ArrayList是...

    清华妹子的Java仓库(进阶学习路线)

    Java集合框架源码解读(1)——ArrayList、LinkedList和Vector Java集合框架源码解读(2)——HashMap Java集合框架源码解读(3)——LinkedHashMap Java集合框架源码解读(4)——WeakHashMap Java集合框架源码解读

    java基础——————试题库

    这份“java基础——————试题库”资源旨在帮助学习者系统地复习和深入理解Java的基础知识,确保他们能够全方位地掌握这一强大的编程工具。下面将详细阐述Java的基础知识点。 1. **Java简介** - Java是由Sun ...

    java源码解读-JavaSource:Java源码解读

    在Java编程语言的世界里,源码解读是提升技术深度、理解内部机制的关键步骤。"JavaSource:Java源码解读"项目旨在帮助开发者深入探索Java的内部工作原理,从而更好地运用和优化代码。在这个项目中,我们可以看到一...

    JAVA学习笔记————————

    5. **集合框架**:JAVA集合框架是存放和操作对象的容器,包括List、Set、Map等接口以及ArrayList、HashSet、HashMap等实现类。学习笔记会详细介绍它们的使用场景和操作方法。 6. **IO流**:JAVA的输入/输出流系统...

    java源码解读-java-src:java源码解读

    Java源码解读是Java开发人员深入理解平台工作原理和编程模型的重要途径。在这个"java-src:java源码解读"项目中,我们可以探索Java的核心库,包括JVM(Java虚拟机)、集合框架、并发机制、I/O流、网络编程等多个关键...

    java源码文档src

    `java.text`和`java.util.Locale`包提供了国际化和本地化的支持,源码解读能帮助开发者为不同地区和语言的用户提供定制服务。 总之,Java源码文档src是Java开发者不可或缺的学习资源,它揭示了Java平台的内在工作...

    尚硅谷Java基础实战——Bank项目下载地址

    5. 集合框架:ArrayList、LinkedList、HashMap等是Java集合框架的一部分,它们用于存储和管理对象。在Bank项目中,可以使用集合来存储多个账户或客户信息,便于管理和查找。 6. IO流:Java的IO流用于读写数据。在...

    ArrayList源码分析

    ArrayList是Java集合框架中的一员,它是List接口的一个实现,提供了动态数组的功能。ArrayList的主要特点是它在内存中以数组的形式存储元素,因此对于随机访问元素,它的性能非常高效。本篇文章将深入探讨ArrayList...

    Java日程管理——万年行事历源代码

    3. **集合框架**:Java集合框架如`ArrayList`, `HashSet`, `HashMap`等在日程管理中发挥着核心作用,用于存储和管理多个日程项。例如,`HashMap`可以用来存储事件ID与事件详情的映射关系,便于快速查找和更新。 4. ...

    java大学教程——课件

    文件名"Java 大学实用教程"暗示了课程可能包含实用的编程示例,如文件I/O操作、多线程编程、集合框架的使用(如ArrayList、LinkedList、HashMap等)以及网络编程。理解如何读写文件、如何实现多线程并行执行以及如何...

    Java学习笔记——良葛格

    "Java学习笔记——良葛格"是一份专为初学者设计的教程资料,由良葛格精心编写,旨在帮助读者掌握JDK5.0版本的Java基础知识。JDK(Java Development Kit)是Java开发的核心工具集,包含了编译器、调试器和运行环境等...

    java毕业设计之ArrayList,LinkList链表接口实现源码.zip

    在本Java毕业设计项目中,主要探讨了两种重要的数据结构——ArrayList和LinkedList,它们都是Java集合框架中的核心组件,属于List接口的实现类。ArrayList和LinkedList各有特点,适合不同的应用场景,理解并掌握这两...

    Java源码大全

    Java源码大全是一个集合了各种Java编程基础知识和经典算法的资源包,对于Java初学者以及有一定经验的开发者来说,都是一个宝贵的参考资料。这个压缩包涵盖了Java语言的不同方面,旨在帮助学习者深入理解Java编程的...

    java源码下载,java

    在学习Java源码时,还可以研究其标准库(JDK)中的类,如`java.lang.String`、`java.util.ArrayList`等。这些类是Java语言的基础,它们的实现往往体现了设计模式和最佳实践。 为了更好地理解和学习Java源码,可以...

    Java中的ArrayList的底层源码解读、LinkedList、Vector的区别介绍

    阅读建议:结合代码进行阅读,在阅读的时候进行代码的比对,以及做笔记,结合自己不懂的地方反复观看,源码解读较为枯燥,文档中的步骤给的都很详细,需要大家结合源码一步一步的跟着步骤进行阅读,

    Java毕业设计——学校管理系统设计与实现(源码+数据库).zip

    《Java毕业设计——学校管理系统设计与实现》是一个典型的软件工程实践项目,主要涵盖了Java编程语言、数据库管理和Web应用开发等多个领域的知识。以下是该项目可能涉及的关键技术点和知识点的详细解析: 1. **Java...

Global site tag (gtag.js) - Google Analytics