集合详解(二)----ArrayList源代码剖析(JDK1.7)

简介: ArrayList私有属性构造方法ArrayList的动态扩容核心ArrayList    ArrayList是List类的一个典型的实现,是基于数组实现的List类,因此,ArrayList封装了一个动态的、可变长度的Object[]数组。

ArrayList


    ArrayList是List类的一个典型的实现,是基于数组实现的List类,因此,ArrayList封装了一个动态的、可变长度的Object[]数组。ArrayList是通过initialCapacity参数来设置数组长度的,当向ArrayList添加的数据超出了ArrayList的长度之后,initialCapacity会自动增加。

    

私有属性

    ArrayList定义了两个私有属性:

//elementData存储ArrayList内的元素,size表示它包含的元素的数量。
private transient Object[] elementData;
private int size;

    其中有一个关键字:transient:Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

public class UserInfo implements Serializable {  
     private static final long serialVersionUID = 996890129747019948L;  
     private String name;  
     private transient String psw;  

     public UserInfo(String name, String psw) {  
         this.name = name;  
         this.psw = psw;  
     }  

     public String toString() {  
         return "name=" + name + ", psw=" + psw;  
     }  
 }  

 public class TestTransient {  
     public static void main(String[] args) {  
         UserInfo userInfo = new UserInfo("张三", "123456");  
         System.out.println(userInfo);  
         try {  
             // 序列化,被设置为transient的属性没有被序列化  
             ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(  
                     "UserInfo.out"));  
             o.writeObject(userInfo);  
             o.close();  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
         try {  
             // 重新读取内容  
             ObjectInputStream in = new ObjectInputStream(new FileInputStream(  
                     "UserInfo.out"));  
             UserInfo readUserInfo = (UserInfo) in.readObject();  
             //读取后psw的内容为null  
             System.out.println(readUserInfo.toString());  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
     }  
 }

    被标记为transient的属性在对象被序列化的时候不会被保存。

构造方法

    ArrayList提供了三种方式的构造器。

 // ArrayList带容量大小的构造函数。
 public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    //ArrayList无参数构造参数,默认容量10
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

     // 创建一个包含collection的ArrayList   
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray(); //调用toArray()方法把collection转换成数组 
        size = elementData.length; //把数组的长度赋值给ArrayList的size属性
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

    在这有一个地方需要注意下,就是在JDK1.6中无参数的构造方法是这么写的:

  // ArrayList无参构造函数。默认容量是10。    
    public ArrayList() {    
        this(10);    
    }   

    在1.7前,会默认在内存中直接分配10个空间,但是在1.7有了改变,会先在内存中分配一个对象的内存空间,但是这个对象是没有长度的。但是在你进行添加的时候,默认的会去拿对象的默认大小来作比较。

    

ArrayList的动态扩容(核心)

    当ArrayList进行add操作的时候,如果添加的元素超出了数组的长度,怎么办?

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    add方法会去调用下面的方法,根据传入的最小需要容量minCapacity来和数组的容量长度对比,若minCapactity大于或等于数组容量,则需要进行扩容。

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //超出了数组可容纳的长度,需要进行动态扩展
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    扩容的时候会去调用grow()方法来进行动态扩容,在grow中采用了位运算,我们知道位运算的速度远远快于整除运算:


private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//这才是动态扩展的精髓,看到这个方法,ArrayList瞬间被打回原形
private void grow(int minCapacity) {
     int oldCapacity = elementData.length;
     //首先得到数组的旧容量,然后进行oldCapacity + (oldCapacity >> 1),将oldCapacity 右移一位,其效果相当于oldCapacity /2,整句的结果就是设置新数组的容量扩展为原来数组的1.5倍
     int newCapacity = oldCapacity + (oldCapacity >> 1);
     //再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组, 
     //不够就将数组长度设置为需要的长度
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     //判断有没超过最大限制,如果超出限制则调用hugeCapacity
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     //将原来数组的值copy新数组中去, ArrayList的引用指向新数组
     //这儿会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影响效率,
     //因此鼓励在合适的时候通过构造方法指定默认的capaticy大小
     elementData = Arrays.copyOf(elementData, newCapacity);
 }
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    有一点需要注意的是,容量拓展,是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消耗,所以在我们使用ArrayList时,最好能预计数据的大小,在第一次创建时就申请够内存。

    看一下JDK1.6的动态扩容的实现原理:

public void ensureCapacity(int minCapacity) {
      modCount++;
     int oldCapacity = elementData.length;
     if (minCapacity > oldCapacity) {
         Object oldData[] = elementData;
         int newCapacity = (oldCapacity * 3)/2 + 1;
             if (newCapacity < minCapacity)
         newCapacity = minCapacity;
             // minCapacity is usually close to size, so this is a win:
             elementData = Arrays.copyOf(elementData, newCapacity);
     }
     }

    从代码上,我们可以看出区别:
    第一:在容量进行扩展的时候,其实例如整除运算将容量扩展为原来的1.5倍加1,而jdk1.7是利用位运算,从效率上,jdk1.7就要快于jdk1.6。
    第二:在算出newCapacity时,其没有和ArrayList所定义的MAX_ARRAY_SIZE作比较,为什么没有进行比较呢,原因是jdk1.6没有定义这个MAX_ARRAY_SIZE最大容量,也就是说,其没有最大容量限制的,但是jdk1.7做了一个改进,进行了容量限制。

相关文章
|
13天前
|
Java 数据处理 API
JDK 21中的序列集合:有序数据处理的新篇章
JDK 21引入了序列集合(Sequenced Collections),这是一种维护元素插入顺序的新型集合。本文介绍了序列集合的概念、特性及其应用场景,如事件日志记录、任务调度和数据处理。通过保持插入顺序和高效的遍历方法,序列集合为开发者提供了更直观和易用的API。
|
6月前
|
Java
Java jdk1.8 lambda 遍历集合的时候到底需不需判空
Java jdk1.8 lambda 遍历集合的时候到底需不需判空
|
6月前
|
Java API 数据处理
JDK 21中的序列集合:有序数据的新篇章
本文将深入探讨JDK 21中新增的序列集合(Sequenced Collections)的概念、特性以及其在现代软件开发中的应用。序列集合为有序数据的处理提供了更高效、更直观的方式,使得开发者能够更轻松地管理集合中元素的顺序。本文将通过示例代码展示序列集合的使用,并分析其与传统集合的区别与优势。
|
6月前
|
安全 Java 开发者
JDK 9:不可变集合类工厂方法的探索与实践
JDK 9引入了一系列新的不可变集合类工厂方法,这些方法为Java开发者提供了更加便捷和安全的方式来创建不可变集合。本文将深入探讨这些新方法的原理、优势以及如何在实际开发中应用它们。
|
6月前
|
编解码 Java API
集合在JDK9中的新特性
集合在JDK9中的新特性
jdk8 Stream流中将集合转成map,重复key处理,统计最大值,获取某个属性集合等10种最常用方法
jdk8 Stream流中将集合转成map,重复key处理,统计最大值,获取某个属性集合等10种最常用方法
177 5
|
存储 安全 Java
高频面试题-JDK集合源码篇(String,ArrayList)
都是List的子集合,LinkedList继承与Dqueue双端队列,看名字就能看出来前者是基于数组实现,底层采用Object[]存储元素,数组中的元素要求内存分配连续,可以使用索引进行访问,它的优势是随机访问快,但是由于要保证内存的连续性,如果删除了元素,或者从中间位置增加了元素,会设计到元素移位的操作,所以增删比较慢。
77 0
java8的JDK文档--Tutorial - Concurrency Lesson-并发集合(Concurrent Collections)
java8的JDK文档--Tutorial - Concurrency Lesson-并发集合(Concurrent Collections)
java8的JDK文档--Tutorial - Concurrency Lesson-并发集合(Concurrent Collections)
|
SQL Java 程序员
JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来
本文就Stream的基础使用层面进行了全面的介绍、实战,告诉你该怎么用每种操作符,只有掌握了这些基本的操作,在面对实际复杂处理逻辑时,需要进一步配合使用,就会知道它的妙处了。这也让你对集合的操作更上一步,为你省去了不少麻烦。关于Stream更深入的说明,如:并行处理、是否高效等,将会在之后的章节进行详尽的阐述、验证,以消除使用中的疑惑与担忧。
296 0
JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来
|
存储 安全 Java
Java集合简单了解——基于JDK1.8中LinkedHashMap、TreeMap、Hashtable、Properties的实现原理
Java集合简单了解——基于JDK1.8中LinkedHashMap、TreeMap、Hashtable、Properties的实现原理
Java集合简单了解——基于JDK1.8中LinkedHashMap、TreeMap、Hashtable、Properties的实现原理