ArrayList 源码解析 及其扩展(jdk1.7)
来源:互联网 发布:c js混淆工具 编辑:程序博客网 时间:2024/05/20 20:32
概述
ArrayList 基于数组实现,是一个动态数组,其容量能自然增长(1.5倍增长)。
不是线程安全,你可以使用Collection.synchronizedList方法将该列表包装起来,以防止意外对列表进行不同步的访问。也可以使用concurrent并发包下的CopyOnWriteArrayList类。
java 1.6API对其解释
返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。在返回的列表上进行迭代时,用户必须手工在返回的列表上进行同步: List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); } 不遵从此建议将导致无法确定的行为。 如果指定列表是可序列化的,则返回的列表也将是可序列化的。
ArrayList实现了 List,RandomAccess,Cloneable,Serialiable 接口
RandomAccess接口,支持随机访问,实际上就是通过下标序号进行快速访问。实际上,实现此接口的List使用 for (int i=0, n=list.size(); i < n; i++) 这种方式迭代的速度会比用for(int i : list)会快一点
ArrayList实现(JDK1.7)
ArrayList中定义了四个私有属性:
private static final int DEFAULT_CAPACITY = 10; //默认容量private static final Object[] EMPTY_ELEMENTDATA = {}; //一个空数组,当用户指定了0为容量时,返回该空数组private transient Object[] elementData; //实际存放数据的数组private int size; //实际存放数据的大小
构造方法:
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //构造一个新数组,指定容量 this.elementData = new Object[initialCapacity]; } public ArrayList() { super(); //默认等于空的数组 this.elementData = EMPTY_ELEMENTDATA; } //此方法的Collection是指集合类只要实现了Collection接口 都能重新转换为ArrayList(List接口已经继承了Collection接口) public ArrayList(Collection<? extends E> c) { //转换为数组 elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) //调用native方法 快速构造数组 elementData = Arrays.copyOf(elementData, size, Object[].class); }
ArrayList增加操作
''' //将当前容量调整为实际个数 public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } } public boolean add(E e) { //此方法的关键是grow函数,增加元素是一个一个添加 所以size+1传入进去 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } 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); } private void grow(int minCapacity) { int oldCapacity = elementData.length; //默认增加为原大小的1.5倍 oldCapacity>>1是把数转换为二进制 并向右移动一位 效果相当于 oldCapacity/2 int newCapacity = oldCapacity + (oldCapacity >> 1); //判断增加后的大小够不够,够了就直接使用newCapacity创建新数组 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//如果增加后的大小比规定的最大size还大 则调用hugeCapacity方法 newCapacity = hugeCapacity(minCapacity); //正常情况下 新的数组大小都为以前的1.5倍 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; }
容量扩大,调用的是Arrays.copyOf(..,..);这个方法,虽然这个方法是native方法,源码里面是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消耗。如果放在程序中,我们最好能够预计其大小,避免重复申请内存。
注意:
1.在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); }}
很明显 这里的数组扩容没有使用位运算,而是直接使用除法和乘法,从效率上来看,jdk1.7 的ArrayList 会比 jdk1.6快一点(位运算更接近系统底层,有兴趣的同学 可以百度)
2.jdk1.6中没有定义MAX_ARRAY_SIZE的大小,所以无法做判断,这也是1.7中改进的地方。
例子
我们在15 16行打上断点
到达15行 还未运行完15行时 list中只有4个数据 所以size为4 因为刚开始定义了5为list的初始大小,则下标为4的为null
当运行完15行 到达16行时,很明显list已经满了
运行完16行后,list扩容为7 (5*1.5 省略小数点后面的数) 所以下标为6的为null
最后输出list.size()的大小,因为是返回list实际的大小 和elementData大小无关
说完了最复杂的增加操作,我们说删,改,查。
删除操作
//移除指定位置的数据 public E remove(int index) { //检查是否下标越界 rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //elementData从第index+1下标开始 复制到原elementData的index下标开始 numMoved是复制的长度 //numMoved已经定义好了 是从要移除的下标号后面还剩余的数组长度 System.arraycopy(elementData, index+1, elementData, index, numMoved); //数组最后一个数就为null了 elementData[--size] = null; //返回移除后的数据 return oldValue; } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //移除指定数据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;}//其实这个方法和remove(int index) 里面的方法如出一辙//可能开发人员没有重复把这个方法运用到remove(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; }
以上这些 就很好的说明了 为什么ArrayList增和删的效率不高了 都是要重新给数组赋值,或者新new一个数组接收更改后的旧数组,这样对内存的消耗会很大。
修改和删除
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } public E get(int index) { rangeCheck(index); return elementData(index); }
这个应该很简单了吧 我就不给注释了。
ArrayList扩展
我们在eclipse中 ctrl+t 查看实现list接口的类有哪些
这其中我们所了解的恐怕只有LinkedList和Vector了
这里大致说一下这两个集合类
LinkedList 底层是链表结构,而且是双链表,也可以当作堆栈,队列或双端队列进行操作。 特点是:查询效率低,增删效率高
Vector 底层也是用数组实现的,里面大部分方法都被声明了synchronized关键字,所以说是线程安全的集合,就是因为synchronized关键字的存在,这个集合本身就是很重量级,几乎很少用到。
关于线程安全和不安全的问题,因为我们是程序员嘛,当然会有办法外部控制这个操作,所以没必要纠结用哪个集合。但是如果有现成的类使用,就不要重复造轮子了。
- ArrayList 源码解析 及其扩展(jdk1.7)
- ArrayList源码解析(基于JDK1.7)
- ArrayList源码解析(jdk1.8)
- jdk1.8----ArrayList源码解析
- Java集合框架--ArrayList源码解析(JDK1.7)
- java ArrayList 源码解析(jdk1.6)
- ArrayList源码解析(基于JDK1.6)
- ArrayList源码解析(jdk1.6)
- ArrayList源码解析——JDK1.8
- jdk1.8中arrayList源码解析
- [JDK1.7源码阅读]ArrayList
- JDK1.7源码笔记之ArrayList类
- 【Java集合框架源码分析(JDK1.7)】-ArrayList源码分析
- ArrayList源码分析(jdk1.8)
- ArrayList源码分析 JDK1.8
- jdk1.8 ArrayList源码详解
- java jdk1.7版本的ArrayList原理解析
- jdk1.7--ArrayList
- Qt实现图像旋转
- C语言中返回的0和1
- Code Vs-problem-1205 单词翻转
- LSTM结构理解与python实现
- unit3.8 文档
- ArrayList 源码解析 及其扩展(jdk1.7)
- 如何更改wamp根目录(项目存放地址)
- Java中的volatile关键字
- Kinect1.0如何安装与运行
- Linux命令学习总结: file命令[转载]
- 建立一个Map实例,k值为String类型,v值为Integer类型。依次用put方法输入如下: map.put("Kobe", 24); map.put("James", 6); map.put("
- Linux之awk命令
- 『安全工具』Nmap 强悍的端口扫描工具
- JavaScript之防篡改对象(高级技巧)