ArrayList 原理(1)
ArrayList是Java List类型的集合类中最常使用的,本文基于Java1.8,对于ArrayList的实现原理做一下详细讲解。
(Java1.8源码:http://docs.oracle.com/javase/8/docs/api/)
一、ArrayList实现原理总结
ArrayList的实现原理总结如下:
①数据存储是基于数组实现的,默认初始容量为10;
②添加数据时,首先需要检查元素个数是否超过数组容量,如果超过了则需要对数组进行扩容;插入数据时,需要将插入点k开始到数组末尾的数据全部向后移动一位。
③数组的扩容是新建一个大容量(原始数组大小+扩充容量)的数组,然后将原始数组数据拷贝到新数组,然后将新数组作为扩容之后的数组。数组扩容的操作代价很高,我们应该尽量减少这种操作。
④删除数据时,需要将删除点+1位置开始到数组末尾的数据全部向前移动一位。
⑤获取数据很快,根据数组下表可以直接获取。
二、ArrayList的实现原理详解
(转自:http://zhangshixi.iteye.com/blog/674856)
1. ArrayList概述:
ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
2. ArrayList的实现:
对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:
1) 底层使用数组实现:
- private transient Object[] elementData;
2) 构造方法:
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
- public ArrayList() {
- this(10);
- }
- public ArrayList(int initialCapacity) {
- super();
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
- this.elementData = new Object[initialCapacity];
- }
- public ArrayList(Collection<? extends E> c) {
- elementData = c.toArray();
- size = elementData.length;
- // c.toArray might (incorrectly) not return Object[] (see 6260652)
- if (elementData.getClass() != Object[].class)
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- }
3) 存储:
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。下面我们一一讲解:
- // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
- public E set(int index, E element) {
- RangeCheck(index);
- E oldValue = (E) elementData[index];
- elementData[index] = element;
- return oldValue;
- }
- // 将指定的元素添加到此列表的尾部。
- public boolean add(E e) {
- ensureCapacity(size + 1);
- elementData[size++] = e;
- return true;
- }
- // 将指定的元素插入此列表中的指定位置。
- // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
- public void add(int index, E element) {
- if (index > size || index < 0)
- throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
- // 如果数组长度不足,将进行扩容。
- ensureCapacity(size+1); // Increments modCount!!
- // 将 elementData中从Index位置开始、长度为size-index的元素,
- // 拷贝到从下标为index+1位置开始的新的elementData数组中。
- // 即将当前位于该位置的元素以及所有后续元素右移一个位置。
- System.arraycopy(elementData, index, elementData, index + 1, size - index);
- elementData[index] = element;
- size++;
- }
- // 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。
- public boolean addAll(Collection<? extends E> c) {
- Object[] a = c.toArray();
- int numNew = a.length;
- ensureCapacity(size + numNew); // Increments modCount
- System.arraycopy(a, 0, elementData, size, numNew);
- size += numNew;
- return numNew != 0;
- }
- // 从指定的位置开始,将指定collection中的所有元素插入到此列表中。
- public boolean addAll(int index, Collection<? extends E> c) {
- if (index > size || index < 0)
- throw new IndexOutOfBoundsException(
- "Index: " + index + ", Size: " + size);
- Object[] a = c.toArray();
- int numNew = a.length;
- ensureCapacity(size + numNew); // Increments modCount
- int 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;
- }
4) 读取:
- // 返回此列表中指定位置上的元素。
- public E get(int index) {
- RangeCheck(index);
- return (E) elementData[index];
- }
5) 删除:
ArrayList提供了根据下标或者指定对象两种方式的删除功能。如下:
- // 移除此列表中指定位置上的元素。
- public E remove(int index) {
- RangeCheck(index);
- modCount++;
- E oldValue = (E) elementData[index];
- int numMoved = size - index - 1;
- if (numMoved > 0)
- System.arraycopy(elementData, index+1, elementData, index, numMoved);
- elementData[--size] = null; // Let gc do its work
- return oldValue;
- }
- // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
- public boolean remove(Object o) {
- // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
- if (o == null) {
- for (int index = 0; index < size; index++)
- if (elementData[index] == null) {
- // 类似remove(int index),移除列表中指定位置上的元素。
- fastRemove(index);
- return true;
- }
- } else {
- for (int index = 0; index < size; index++)
- if (o.equals(elementData[index])) {
- fastRemove(index);
- return true;
- }
- }
- return false;
- }
注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。
6) 调整数组容量:
从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
- 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倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:
- public void trimToSize() {
- modCount++;
- int oldCapacity = elementData.length;
- if (size < oldCapacity) {
- elementData = Arrays.copyOf(elementData, size);
- }
- }
7) Fail-Fast机制:
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体介绍请参考我之前的文章深入Java集合学习系列:HashMap的实现原理中的Fail-Fast机制。
8) 关于其他的一些方法的实现都很简单易懂,读者可参照API文档和源代码,一看便知,这里就不再多说。
/*****************************************************************************************************************************/
ArrayList是List里面使用率最高的。
package collection.lession7;
import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Iterator;import java.util.List; public class Lession7 { public static void main(String[] args) { testNormal(); testSpecial(); // 一个最常见的错误 testForProblem(); } public static void testNormal() { // ------------------------------------------------------- // 声明一个列表 // 允许放入任何数据 // ------------------------------------------------------- ArrayList list = new ArrayList(); // 放入整数 // 当然你用 new Integer(1)也可以 list.add(1); // 放入字符串 list.add("abc"); // 放入浮点数 list.add(new Float(1.11)); // add会将数据保存到列表的尾部 showList(list); // 1, abc, 1.11] // 下面我们在列表的头部增加数据 list.add(0, 2); list.add(0, "bcd"); list.add(0, new Double(2.34)); // 列表可以指定插入的位置 // 0 是头部第一个位置,所以数据都逐个放到最前面了 showList(list); // [2.34, bcd, 2, 1, abc, 1.11] // 下面我们插入到我们希望的任何位置 // 当然不能越界,(0 到 list.size()-1)范围内才可以 list.add(1, 3); list.add(4, "xyz"); // 数据被放到了正确的位置 showList(list); // [2.34, 3, bcd, 2, xyz, 1, abc, 1.11] // ------------------------------------------------------- // 我们有了数据,我们来测试读取数据 // ------------------------------------------------------- // 我们可以通过指定索引的位置,来拿到我们希望的数据 System.out.println(list.get(0)); // 2.34 System.out.println(list.get(4)); // xyz // ------------------------------------------------------- // 测试是否存在某个数据 // ------------------------------------------------------- System.out.println(list.contains("xyz")); // true // 测试是否包含一组数据 Collection c = new ArrayList(); c.add(1); c.add(2); System.out.println(list.containsAll(c)); // true c.add(3); c.add(4); // containsAll_1234=false System.out.println(list.containsAll(c)); // false // ------------------------------------------------------- // 查找某个数据所在的索引位置 // 如果不存在,返回-1 // ------------------------------------------------------- System.out.println(list.indexOf(3)); // 1 System.out.println(list.indexOf("xyz")); // 4 System.out.println(list.indexOf("abcd")); // -1 // ------------------------------------------------------- // 测试删除数据 // 请注意, // 如果你使用整数(int)数字,则默认调用的是remove(int index); // 如果你用 long,则会调用 remove(Object obj); // 所以如果你要删除整数,请使用 remove(new Integer(int)); // ------------------------------------------------------- // 删除索引为1的数据 list.remove(1); // 索引为1的数据被干掉了 showList(list); // [2.34, bcd, 2, xyz, 1, abc, 1.11] // 删除数字1 和字符串 abc list.remove(new Integer(1)); list.remove("xyz"); showList(list); // [2.34, bcd, 2, abc, 1.11] // ------------------------------------------------------- // 迭代器的使用 // ------------------------------------------------------- Iterator it = list.iterator(); while (it.hasNext()) { System.out.print(it.next() + " "); // 2.34 bcd 2 abc 1.11 } System.out.println(); // ------------------------------------------------------- // 转化为数组 // ------------------------------------------------------- Object[] objs = list.toArray(); for (Object obj : objs) { System.out.print(obj + " "); // 2.34 bcd 2 abc 1.11 } System.out.println(); } public static void testSpecial() { // ------------------------------------------------------- // 测试重复和null // ------------------------------------------------------- // List<Integer> list = new ArrayList<Integer>(); list.add(123); list.add(456); list.add(123); list.add(456); // 数据允许重复 showList(list); // [123, 456, 123, 456] list.add(null); list.add(789); list.add(null); list.add(999); // 允许放入多个null showList(list); // [123, 456, 123, 456, null, 789, null, 999] // ------------------------------------------------------- // 测试一下查找最后一次出现的位置 // ------------------------------------------------------- System.out.println(list.indexOf(123)); // 0 System.out.println(list.lastIndexOf(123)); // 2 // ------------------------------------------------------- // 转化为数组 // 记得要转化为Inerger. // ------------------------------------------------------- Integer[] nums = (Integer[]) list.toArray(new Integer[0]); // 注意数据里面有null,所以循环变量不要用int 要用Integer for (Integer num : nums) { System.out.print(num + " "); // 123 456 123 456 null 789 null 999 } System.out.println(); } public static void testForProblem() { // 一些朋友在向循环里向列表增加对象的时候 // 经常忘记初始化,造成最终加入的都是同一个对象 List<MyObject> list = new ArrayList<MyObject>(); MyObject obj = new MyObject(); for (int i = 1; i <= 5; i++) { obj.setName("Name" + i); list.add(obj); } // 里面的数据都是最后一个 showList(list); // [Name5, Name5, Name5, Name5, Name5] // 正确的做法 List<MyObject> list2 = new ArrayList<MyObject>(); MyObject obj2 = null; for (int i = 1; i <= 5; i++) { obj2 = new MyObject(); obj2.setName("Name" + i); list2.add(obj2); } // 里面的数据都是最后一个 showList(list2); // [Name1, Name2, Name3, Name4, Name5] } /** * 显示List里面的数据。 * * @param list */ private static void showList(List list) { System.out.println(Arrays.toString(list.toArray())); }} class MyObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 重写toString方法,输出name */ public String toString() { return name; }} |
输出结果
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[1, abc, 1.11][2.34, bcd, 2, 1, abc, 1.11][2.34, 3, bcd, 2, xyz, 1, abc, 1.11]2.34xyztruetruefalse14-1[2.34, bcd, 2, xyz, 1, abc, 1.11][2.34, bcd, 2, abc, 1.11]2.34 bcd 2 abc 1.112.34 bcd 2 abc 1.11[123, 456, 123, 456][123, 456, 123, 456, null, 789, null, 999]02123 456 123 456 null 789 null 999[Name5, Name5, Name5, Name5, Name5][Name1, Name2, Name3, Name4, Name5]
|
ArrayList 原理(1)的更多相关文章
- Java集合 ArrayList原理及使用
ArrayList是集合的一种实现,实现了接口List,List接口继承了Collection接口.Collection是所有集合类的父类.ArrayList使用非常广泛,不论是数据库表查询,exce ...
- ArrayList原理解析
简介 ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了ICollection和IList接口,灵活的设置数组的大小等好处 有图有码 图 ...
- 「必知必会」最细致的 ArrayList 原理分析
从今天开始也正式开 JDK 原理分析的坑了,其实写源码分析的目的不再是像以前一样搞懂原理,更重要的是看看他们编码风格更进一步体会到他们的设计思想.看源码前先自己实现一个再比对也许会有不一样的收获! ...
- 一.ArrayList原理及实现学习总结
一.ArrayList介绍 ArrayList是一种线性数据结构,它的底层是用数组实现的,相当于动态数组.与Java中的数组相比,它的容量能动态增长.类似于C语言中的动态申请内存,动态增长内存. 当创 ...
- ArrayList 原理(2)
1. 概述 关于Java集合的小抄中是这样描述的: 以数组实现.节约空间,但数组有容量限制.超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预 ...
- ArrayList原理(一)
需要使用到动态数组的时候用的最多的就是ArrayList了,底层其实是Object数组,以下demo基于JDK1.8: List<Integer> list = new ArrayLis ...
- ArrayList原理分析(重点在于扩容)
首先,ArrayList定义只定义类两个私有属性: /** * The array buffer into which the elements of the ArrayList are stored ...
- 容器ArrayList原理(学习)
一.概述 动态数组,容量能动态增长,元素可以为null,用数组存储,非线程同步(vector线程同步) 每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小,自动增长(默 ...
- Day 5 :ArrayList原理、LinkedList原理和方法和迭代器注意事项
迭代器在变量元素的时候要注意事项: 在迭代器迭代元素 的过程中,不允许使用集合对象改变集合中的元素个数,如果需要添加或者删除只能使用迭代器的方法进行操作. 如果使用过了集合对象改变集合中元素个数那 ...
随机推荐
- 【转】Ubuntu12.04 LTS下环境变量设置
原文网址:http://blog.chinaunix.net/uid-26963688-id-3221439.html 1.设置当前用户环境变量(对root用户无效) 打开终端输入:light@cha ...
- junit 知识点
JUnit 测试框架具有以下重要特性: 测试工具 测试套件 测试运行器 测试分类 测试工具 测试工具是一整套固定的工具用于基线测试.测试工具的目的是为了确保测试能够在共享且固定的环境中运行,因此保证测 ...
- Action<T>和Func<T>委托
Action<T>和Func<T>委托 泛型Action<T>委托和Func<T>委托是系统定义的两个泛型委托. Action<T>委托表示 ...
- C#操作VFP的dbf数据库文件实例
C#操作VFP的dbf数据库文件实例 新一篇: js获取网站跟路径 实例中分别使用Oledb和Odbc操作vfp数据库dbf文件,操作包括:读取,增删改. 已测试可直接使用,使用方法:下面代码分两个部 ...
- [转载]Linux下关于system调用
曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原 ...
- tcp端口检测
# coding=utf-8 import sys import socket import re def check_server(address, port): s = socket.socket ...
- python random 随机选择操作
# -*- coding:utf-8 -*- import random arr = ['A','B','C','D','E','F'] #生成(0.0, 1.0)的随机数 print random. ...
- Find substring with K distinct characters
Given a string and number K, find the substrings of size K with K distinct characters. If no, output ...
- 关于UC、火狐、谷歌浏览器屏蔽布局中广告的解决办法
关于UC浏览器屏蔽了广西人才网的名企.品牌.热点的logo,是因为当成广告过滤掉了,以后div的class和id不能以“ad”开头.这可能只是其中一个规则,adxxxx是可以的,不能是adXxxx, ...
- javascript中有关this的解析题
1.作用域链 作用域:浏览器给js一个生存环境(栈)内存 作用域链:js中的关键字var function 都可以提前声明和定义,提前声明和定义,放在我们的内存地址(堆)内存中,然后js从上到下逐行执 ...