Java集合之ArrayList源码解析

下面我们来看看ArrayList的底层实现,

ArrayList继承了AbstractList,实现Cloneable、Serializable、RandomAccess接口,

它的成员属性有Object[]  elementData 和 int size,

显然底层是以可扩展的数组来存储元素,

新增元素

有如下这段代码,

public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
}

我们进到add(E e)方法,下如图,

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

size因为是成员属性,并且是基本数据类型,所以它的初始值为0,

第3行elementData[size++] = e;等价于第一步先elementData[0] = e ,第二步size自增,

第2行的 ensureCapacityInternal(size + 1),上面我们也提到了,Object[]  elementData是一个可动态扩展的一个数组,

因此我们需要校验当前的的容量是否满足元素的存储,如果不满足,又是采取怎样的方式进行扩容呢?

我们来看 ensureCapacityInternal(int minCapacity),

1 private void ensureCapacityInternal(int minCapacity) {
2         if (elementData == EMPTY_ELEMENTDATA) {
3             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
4         }
5
6         ensureExplicitCapacity(minCapacity);
7 }

DEFAULT_CAPACITY = 10 是默认的数组容量,

下面来看ensureExplicitCapacity(int minCapacity),

1 private void ensureExplicitCapacity(int minCapacity) {
2         modCount++;
3
4         // overflow-conscious code
5         if (minCapacity - elementData.length > 0)
6             grow(minCapacity);
7 }

如果需要的长度大于数组当前的长度,则调用grow(int minCapacity),

 1 private void grow(int minCapacity) {
 2         // overflow-conscious code
 3         int oldCapacity = elementData.length;
 4         int newCapacity = oldCapacity + (oldCapacity >> 1);
 5         if (newCapacity - minCapacity < 0)
 6             newCapacity = minCapacity;
 7         if (newCapacity - MAX_ARRAY_SIZE > 0)
 8             newCapacity = hugeCapacity(minCapacity);
 9         // minCapacity is usually close to size, so this is a win:
10         elementData = Arrays.copyOf(elementData, newCapacity);
11 }

扩容的规则是,当前数组的长度乘以1.5,结果带小数则取整数,

最后调用Arrays.copyOf(T[] original, int newLength),直接看到最内层的实现,

1 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2         T[] copy = ((Object)newType == (Object)Object[].class)
3             ? (T[]) new Object[newLength]
4             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
5         System.arraycopy(original, 0, copy, 0,
6                          Math.min(original.length, newLength));
7         return copy;
8 }

会创建一个新数组,同时将原数组的内容复制到新数组中,

扩容是的操作是int newCapacity = oldCapacity + (oldCapacity >> 1),我们可以这么认为

1.扩容一次性太多,势必会造成对内存空间的过多占用,

2.扩容太少,会造成次数太多,下次的扩容很快到来,同时,将原数组的元素复制到新的数组中,频繁的数组拷贝需要消耗一定的性能,

因此也许这是一种比较折中的处理方式,

删除元素 

如下一段代码

1 public static void main(String[] args) {
2         List<Integer> list = new ArrayList<Integer>();
3         list.add(111);
4         list.add(222);
5         list.remove(1);
6         list.remove(222);
7 }

元素的删除分为两种,一种是按照下标来删除,一种是按照元素来删除

按照下标来删除元素,先看一下代码,

 1 public E remove(int index) {
 2         rangeCheck(index);
 3
 4         modCount++;
 5         E oldValue = elementData(index);
 6
 7         int numMoved = size - index - 1;
 8         if (numMoved > 0)
 9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12
13         return oldValue;
14 }

第二行,校验是否越界,第九行将删除的数据之后的元素往前移动一位,第十一行将size自减,同时将最后一位引用指向null

clear to let GC do its work!

按照元素来删除元素,先看一下代码,

public boolean remove(Object o) {
        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;
}

遍历找出下标之后调用 fastRemove(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
}

两种实现方式差不多,最后都是调用上面的数组部分元素的移动,以及最后一位引用让虚拟机自己释放元素的引用

插入元素

如下一段代码,

1 public static void main(String[] args) {
2         List<Integer> list = new ArrayList<Integer>();
3         list.add(111);
4         list.add(222);
5         list.add(1,22222);
6 }

第5行表示,在第一个元素之后插入22222,

我们进到add(int index, E element),

1 public void add(int index, E element) {
2         rangeCheckForAdd(index);
3
4         ensureCapacityInternal(size + 1);  // Increments modCount!!
5         System.arraycopy(elementData, index, elementData, index + 1,
6                          size - index);
7         elementData[index] = element;
8         size++;
9 }

第2行,校验插入的位置是否在数组的大小范围内,否则跑出异常,

第4行,判断是否有必要扩容,和新增元素的扩容是一样方法,

第5行,插入的位置到最后的所有元素向后移动一位,

第6行,插入的位置的引用指向新元素,

下面我们ArrayList实现了Random接口,然鹅RandomAccess是一个空接口,javadoc中是这么描述的,

Marker interface used by <tt>List</tt> implementations to indicate that
they support fast (generally constant time) random access.

翻译过来的意思是 这是一个标记接口,仅仅是一个标记,arrayList实现标记,表明它能快速的查询数据,

从上面几个方面,我们总结一下ArrayList的优缺点,

优点

1.新增一个元素,顺序新增,增加数组元素的一个引用而已,

2.数组查询非常快捷,

缺点

1.删除元素,会造成部分元素的移动,势必会造成性能的一定影响,

2.插入元素,会造成部分元素的移动,势必会造成性能的一定影响,

所以,在业务开发中,涉及到查询较多的,考虑ArrayList。

同样,想借鉴大神对于集合的四个关注点

1.是否允许为空,

2.元素是否允许重复,

3.元素的存储顺序与查找顺序是否一致,

4.是否线程安全,

ArrayList允许元素为空,允许重复,有序,非线程安全,

我们再回头看数组的定义

private transient Object[] elementData;

被transient关键字修饰,表示该数组不会被序列化,而是提供了writeObject(java.io.ObjectOutputStream s),javadoc有这么一句话,

Save the state of the <tt>ArrayList</tt> instance to a stream (that
is, serialize it)

我们来看一下writeObject(java.io.ObjectOutputStream s),

 1 private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException{
 3         // Write out element count, and any hidden stuff
 4         int expectedModCount = modCount;
 5         s.defaultWriteObject();
 6
 7         // Write out size as capacity for behavioural compatibility with clone()
 8         s.writeInt(size);
 9
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18 }

第5行,对非transient的成员属性序列化,

第11~13行,对有数组中有值得元素逐个序列化。

这么做的好处,

1.大大缩短序列化的时间,

2.减少序列化后文件的大小。

原文地址:https://www.cnblogs.com/sunshine798798/p/9053733.html

时间: 2024-10-29 19:08:24

Java集合之ArrayList源码解析的相关文章

Java集合类库 ArrayList 源码解析

集合类库是Java的一个重大突破,方便了我们对大数据的操作.其中 Arrays 和 Collections 工具类可以帮助我们快速操作集合类库.下面对Java集合类库的源码分析是基于jdk1.7的.今天我们来看看ArrayList的底层实现原理. ArrayList的继承结构图 继承自 AbstractList 抽象类,在上层是 AbstractCollection 抽象类,直接去 AbstractCollection 类去看看. AbstractCollection 类主要实现了 Collec

Java集合---Array类源码解析

Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对Object对象数组采用归并排序.对这一区别,sun在

Java 1.8 ArrayList源码解析

1 // 非线程安全 2 // 继承了AbstractList类 3 // 实现了List.RandomAccess.Cloneable.java.io.Serializable接口 4 // 后面3个接口是标记接口,没有抽象方法. 5 // 表示ArrayList可以随机访问.浅复制.序列化和反序列化. 6 public class ArrayList<E> extends AbstractList<E> 7 implements List<E>, RandomAcc

【Java集合】-- LinkedList源码解析

目录 继承体系 数据结构 源码解析 1.属性 2.构造方法 LinkedList() LinkedList(Collection<? extends E> c) 3.添加元素 add(E e) addFirst(E e) addLast(E e) add(int index, E element) offer(E e) offerFirst(E e) offerLast(E e) 总结 4.获取元素 get(int index) getFirst() getLast() peek() 5.删除

Java集合之ArrayList源码分析

1.简介 List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList.Java中分别提供了这两种结构的实现,这一篇文章是要熟悉下ArrayList的源码实现.使用示例如下: package com.test.collections; import java.util.ArrayList; public class ArrayListTest { /** * @param args */ public

Java集合类库 LinkedList 源码解析

基于JDK 1.7,和ArrayList进行比较分析 Java已经有了ArrayList,用来存放元素,对元素的操作都很方便.为什么还会有LinkedList呢?我们都知道ArrayList获取元素很快,但是插入一个元素很慢,因为ArrayList底层维护的是一个数组,往数组中的某个位置插入一个元素,是很消耗资源的. 而LinkedList插入元素很快,获取任意位置的元素却很慢.这是为什么呢?底层又是怎样实现的呢? 1.继承关系 LinkedList的继承关系图: LinkedList继承的是A

Java集合---Arrays类源码解析

一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对Object对象数组采用归并排序.对这一区别,sun在<<The Java Tutorial>>中做出的解释如下: The sort

Java集合(3)--ArrayList源码分析

默认初始容量为10,底层用的是对象数组实现的. public void ensureCapacity(int minCapacity).确保数组最小容量,用于添加元素的时候. 它的父类AbstractList只有一个抽象方法abstract public E get(int index); modCount:记录list结构被改变的次数,其实是改变大小时会自增,主要是add和remove,set并不会使modCount自增. 会影响modCount的操作:add,remove.ensureCap

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA