从ArrayList说起
来源:互联网 发布:linux dd克隆系统 编辑:程序博客网 时间:2024/04/29 10:39
从何说起呢?ArrayList简单易用及强大的功能,注定了我们使用java语言编程时,用得最多的容器类非它莫属。正是因为它的简单,很多并没有认真了解过它,对它的理解并不深刻。也正是因为它是最常用,最基础的容器类,所以就从ArrayList说起吧!本文不会逐一去解释ArrayList的所有方法,仅挑选当中几个我们平时容易忽略的方面进行说明。
ArrayList内存机制
ArrayList,顾名思义,这个list是通过Array进行存储。那么它占用的内存空间是怎么进行分配,扩展以及收缩的呢?
空间分配
首先,每当JVM执行以下这条语句时:
ArrayList list = new ArrayList();
分配给这个list的初始空间大小(DEFAULT_CAPACITY
)为10。
空间扩展
每当往list中添加对象时,即调用add
或addAll
时,为了保证空间足够使用而不至于抛出异常,都需要对空间大小进行判断,如果现有空间足够大,那么直接插入,否则需要先扩展内存。ArrayList中判断内存是否足够以及进行内存扩展的方法有:
//保证空间至少为minCapacitypublic void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); }}private void ensureExplicitCapacity(int minCapacity) { modCount++; //当所需最小空间大于现有空间大小时,需要进行内存扩展 if (minCapacity - elementData.length > 0) grow(minCapacity);}private void grow(int minCapacity) { int oldCapacity = elementData.length; //计算新的内存大小 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果计算所得内存大小仍小于所需最小值,则将新的内存大小设为所需最小值, //一般只有addAll会用到 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新的大小超过最大可分配空间,则将其设置为最大可分配空间 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //内存中开辟一片新的大小为newCapacity的空间,并将现有内容拷贝过去 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;}
从上面这段代码,我们可以得出结论:
- newCapacity=min(max(3/2*oldCapacity, minCapacity), MAX_ARRAY_SIZE);
- 为了保证数组内存空间的连续性,每次进行空间扩展都是将原有数据拷贝到新的内存地址上,久的内存由gc(垃圾回收器)回收;
- ArrayList的最大存储量为
Integer.MAX_VALUE
,即 2147483647。
如果逐个往list中添加对象,那么list的空间大小呈阶梯状增长,如下图所示:
空间收缩
值得注意的是,当我们调用ArrayList的remove
,removeAll
以及retainAll
方法时,这些方法都是在原本的内存空间上进行操作(下文会详述)。因此,如若我们删除一些元素后,空间并没有得到释放。如果我们需要对空间进行回收,那么ArrayList提供了以下方法供我们使用。
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); }}
此方法可以将list占用内存大小压缩到list中元素个数。同样也是通过Arrays.copyof
方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。
removeAll & retainAll
此处专门设章节写这两个方法主要原因是它们的实现较为巧妙,值得一说。
如果是我们自己实现removeAll,最简单的方法就是循环调用remove方法,将需要删除的元素逐个删除;而retailAll则逐个查找需要保留的元素,并拷贝到一片新的空间上。这也许是最朴素,最简单粗暴的方法了。
JDK如何巧妙实现呢,先看看源码:
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false);}public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true);}private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified;}
从源码可以看出,这无论是removeAll还是retainAll,它们都通过调用batchRemove这一个方法实现。它们的实现方式类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。整个删除和保留过程如下图所示:
这种方法非常快速并节约地删除或保留元素,较为巧妙。
安全的toArray
toArray也没有什么神奇之处,无非就是返回ArrayList中自己保存的数组对象,这里单独描述,只是为了说明这个方法是安全的,我们无需担心对得到的数组进行操作会对原本的arraylist对象产生影响,它用于桥接基于数组和基于容器的两种API模式。源码如下:
public Object[] toArray() { return Arrays.copyOf(elementData, size);}
源码非常简洁,可以看出返回的数组对象是通过Arrays.copyOf拷贝出来的一份对象,和原本的对象不属于同一内存空间。因此,得到的数组对象是独立的,可以放心使用。
Java8 中新增方法
java 8 中,ArrayList新增方法如下表所示:
对于新增方法,我们将从如何使用它们的角度出发,进行描述。
首先我们定义一个员工类(Employee):
public class Employee { private String name; private String Dept; private Integer age; private Double salary; //getter and setter ......}
- forEach
在java8之前,如果我们要对一个遍历员工数组,输出每个员工的姓名,那么我们需要这么做:
for (Employee emp:employees) { System.out.println(emp.getName()); }
如今,我们可以这么做:
employees.forEach(e -> System.out.println(e.getName()));
也可以这么做:
employees.forEach(new Consumer<Employee>() { @Override public void accept(Employee t) { System.out.println(t.getName()); }});
- spliterator
这个用于返回一个ArrayListSpliterator
,此处不做具体介绍;如需了解可以参考:这儿 - removeIf
加入现在公司财务紧张,需要减员,假设需要裁去IT部门中工资超过30000的员工,可以这么办:
employees.removeIf(e -> e.getDept().equals("IT") && e.getSalary() > 30000d);
- replaceAll
现在公司重新整理架构,需要讲IT部门合并进RD部门,现在可以直接操作:
employees.replaceAll(new UnaryOperator<Employee>() { @Override public Employee apply(Employee t) { if (t.getDept().equals("IT")) { t.setDept("RD"); } return t; }});
- sort
公司HR发工资时,需要根据员工工资倒序排序,过去常用的方法是使用Collections的静态方法sort:
Collections.sort(employees, new Comparator<Employee>() { @Override public int compare(Employee o1, Employee o2) { return (int) (o2.getSalary() - o1.getSalary()); }});
现在可以直接进行排序:
employees.sort((a, b) -> a.getSalary().compareTo(b.getSalary()));
总结
本文主要分析了ArrayList几个常常容易忽略方法的源码,总结了java8中ArrayList新增的方法。仅当学习,记录。
- 从ArrayList说起
- 从C#中的ArrayList和List的区别说起
- 从Object_oriented 说起
- 从AFX_MANAGE_STATE(AfxGetStaticModuleState())说起
- 从“芙蓉姐姐”现象说起
- 不知从何说起
- 从jira说起
- 从AFX_MANAGE_STATE(GetStaticModuleState())说起
- 从辞职说起
- 从"爱因斯坦圆"说起
- 从PDCA说起
- 从被点到说起......
- 从大乘“六度法”说起
- 从AFX_MANAGE_STATE(AfxGetStaticModuleState())说起
- 从Proxy.newProxyInstance说起
- 从中医经络说起
- 从jira说起
- 从揣摩他人说起
- Bitmap,Drawable转换
- MySQL: Forcing close of thread **** user:''
- iOS学习之路-02-创建页面前的思考
- hdu 1166 敌兵布阵(线段树)
- 哥德巴赫猜想
- 从ArrayList说起
- leetcode|100|Same Tree
- java活锁测试
- 6.2 TCP滑动窗口
- 怎么理解ESB(纯属个人理解)
- 理解互斥量和信号量
- fopen和freopen
- IOCP扩展方法AcceptEx, DisconnectEx, GetAcceptExSockaddr用法示例
- C++中的fstream/ifstream/ofstream和MFC中的CFile/CStdioFile