这篇文章没有写完。以后继续更新。自己写给自己看的,有些不规范的地方:类名没有驼峰形式大写,方法或成员变量起名随意
一、概述
ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
二、ArrayList 的重要成员变量
1.private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
无参构造函数在jdk1.6(包含)之前时是构造默认一个容量为10的数组,但是现在给定为空的数组。
2.transient Object[] elementData;
elementData 表示底层组装的Object类型的数组。
3.private static final int DEFAULT_CAPACITY = 10;
默认容量大小是10,但是在无参构造方法中没有起到作用。目前在elementData扩容时用到了该参数。
但是为啥选择10?不是2的幂次方例如hashmap的16?我认为的原因是ArrayList在扩容时并没有类似hashmap的额外的开销而采用的数学技巧,可能根据人类十进制的惯性选择了一个任意的不能太小,也不能太大的10。
4. private int size;
表示当前ArrayList的大小(存储元素的数量)
5.protected transient int modCount = 0;
来自ArrayList的父类AbstractList。fail-fast机制。
6.private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
具体原因可以查看源码注释
/**
* The maximum size of array to allocate. 要分配的最大数组大小。
* Some VMs reserve some header words in an array. 一些虚拟机在数组中保留一些标题字
* Attempts to allocate larger arrays may result in 尝试分配更大的数组可能会导致OutOfMemoryError
* OutOfMemoryError: Requested array size exceeds VM limit 请求的阵列大小超出了VM限制
*/
三、ArrayList 的构造方法
1.无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.参数为初始化数组大小(int)的构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
其中:private static final Object[] EMPTY_ELEMENTDATA = {};
DEFAULTCAPACITY_EMPTY_ELEMENTDATA(默认容量[0]空元素数据) 和 EMPTY_ELEMENTDATA (空元素数据)都是空数组({}),为什么要初始化两个?不能替换使用吗?我觉得最大的意义是便于阅读源码。(具体原因还有待讨论,也许是原作者当时想问题时就用到了两个变量,就顺手定义了)。但是该方法关于和jdk1.7版本的性能改进请详见
https://blog.csdn.net/weixin_43390562/article/details/101236833
3.参数为集合的构造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
其中有这样一句注释:c.toArray might (incorrectly) not return Object[] (see 6260652) 具体原因请详见
https://blog.csdn.net/gulu_gulu_jp/article/details/51457492
四、ArrayList 一些重要的成员方法
1.ArrayList的增
1.1 末尾增加 add()方法
首先判定是否需要扩容,如果需要则需要一个新的newlength空间的数组,以及数组.length的值复制,即空间复杂度为O(n),时间复杂度为O(n);不扩容则空间复杂度为O(0),时间复杂度为O(1).
private void ensureCapacityInternal(int minCapacity) {
/** 首先判定是否为空,如果为空则重置目前需要的最小容量.(比较10和minCapacity,取最大值)
ensureCapacityInternal方法addall()也用了。
所以minCapacity的取值范围是[1,Integer.MAX_VALUE]
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;// fast-fail 机制
// overflow-conscious code
/** 溢出感知代码
* minCapacity的值是有溢出风险的,ArrList没有做当minCapacity<0(向上溢出)时直接抛出异
* 常的检查,而把检查下沉到 private static int hugeCapacity(int minCapacity) 方法中
* 了。通过观看源码上下文可以得出minCapacity=size+n ;elementData.length=size+x;其
*中size是当前容器里容纳的元素个数,n为要新添加的元素个数,x为当前容器里还可以容纳元素的
*个数。所以minCapacity - elementData.length > 0 可以转化为 n-x>0;可以肯定n和x都是自
*然数,所以这样写一定可以说明在数学意义上(如果溢出了就换算成数学意义上的值)
*minCapacity> elementData.length,则表明数组需要扩容
*/
if (minCapacity - elementData.length > 0)
grow(minCapacity);// 在这里minCapacity的值还可能是溢出的
}
grow(int minCapacity) 方法实现的功能用一句话概括就是,如果 minCapacity 小于等于原数组的 1.5 倍,则扩容至原数组的 1.5 倍,如果 minCapacity 大于原数组的 1.5 倍,则扩容至 minCapacity。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
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;
}
2.2 插入增加 add(index, element);
同样需要判定是否扩容,但是无论是否扩容时间复杂度都是O(n-i)。
2.ArrayList的删除
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
}
以上是主要的删除方法,无论是删除下标还是删除对象。最后都要走这个逻辑 。
删除方法都是先比较时间复杂度O(n)再删除(元素顺序前移)O(n)
但是有一些隐含坑,阿里规约也提到了:【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
具体可以看这篇博文:https://blog.csdn.net/Sun_flower77/article/details/78008491
总结:当需要循环删除时慎用ArrayList的remove方法,循环删除时采用for循环倒叙删除或者Iterator方式;删除自定义的类对象一定要重写equals和hashcode方法。
五、一些有趣的问题
1.参数为null会调用哪个方法?
public class hwbtest {
public static void f(Integer integer) {
System.err.println("Integer");
}
public static void f(int i) {
System.err.println("int");
}
public static void f(String string) {
System.err.println("String");
}
public static void main(String[] args) {
f(null); // 编译报错,The method f(Integer) is ambiguous for the type hwbtest
}
}
2. ArrayList<String> arrayList = new ArrayList<>(null); 会怎样?
编译不报错,运行报错。 Effective java:检查参数的有效性。 java源码里并没有对传入null的检查
3.阿里规约:【推荐】集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
public class hwbtest {
/**
* 在eclipse上编辑的,为了防止eclipse报notused警报,所以打印每个实例
* @param args
*/
public static void main(String[] args) {
/**
* 全省略方式,eclipse报警告信息。
* 调用add()方法只能添加String类型
*/
List<String> s0 = new ArrayList(10);
System.err.println(s0);
/**
* diamond方式,eclipse不报警告信息。
* 调用add()方法只能添加String类型
*/
List<String> s1 = new ArrayList<>(10);
System.err.println(s1);
/**
* 我自己编写代码的一般方式,eclipse不报警告信息。
* 调用add()方法只能添加String类型
*/
List<String> s2 = new ArrayList<String>(10);
System.err.println(s2);
/**
* eclipse报警告信息。
* 调用add()方法能添加任意引用类型元素
* 调用get(index) 方法可以获得元素
*/
List s3 = new ArrayList<String>(10);
System.err.println(s3);
/**
* eclipse报警告信息。
* 调用add()方法能添加任意引用类型元素
* 调用get(index) 方法可以获得元素
*/
List s4 = new ArrayList(10);
System.err.println(s4);
/**
* eclipse报警告信息。
* 不能调用add()方法
* 原因是s5不能确定到底可以容纳啥类型
*/
List<?> s5 = new ArrayList(10);
s5.get(0);
System.err.println(s5);
// 总结:左边<>确定容器容纳元素类型,右边<> 不起多大作用
// 左边<?>可以通过编译运行,但是无法调用add方法
// diamond方式 个人觉得舒服(不报警告),而且少写右边<>中的类型
}
}
4.private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 那么数组的最大容量是多少呢?
不考虑jvm的内存限制和不同版本jdk的设计,理论上在java中数组的最大容量是 Integer.MAX_VALUE。因为当时设计length属性时选择的是int。
public static void main(String[] args) {
byte[] bs = new byte[Integer.MAX_VALUE-2];
System.err.println(bs[Integer.MAX_VALUE-3]);
}
由于我的机器的原因byte数组取的最大容量是 Integer.MAX_VALUE-2
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}
上边是java ArrayList的一部分源码,可以发现返回的可能是 Integer.MAX_VALUE
5.b-a>0可以说明b>a吗?
我们知道在java中int类型的取值范围是-2^31 至 2^31-1 ,也就是-2147483648 至2147483647。正整数超出2147483647范围后会出现循环取值的现象,也就是2147483647+1=2147483648溢出后回到了最小负整数-2147483648,2147483647+2=2147483649溢出后变成了-2147483648+1=-2147483647,依次类推。
public static void main(String[] args) throws SQLException, IOException, InterruptedException {
int a = Integer.MAX_VALUE; int b = Integer.MIN_VALUE;
System.err.println(b-a>0);
System.err.println(b>a);
}
由此可见在java中b-a>0不一定说明b>a。那什么情况下可以说明呢?(在数学意义上计算)|b-a|<2147483649(可以自己画有一个小缺口的的圆圈理解下,缺口的两端分别为最大值和最小值)。其实在ArrayList中有好多b-a>0的比较方式 ,我自己觉得用b>a的比较方式好点吧,毕竟少了一步减法运算而且肯定和数学意义的逻辑计算一致。
我上述的观点(b>a的比较方式好点)有点错误,在源码中有这样一句注释:overflow-conscious code。
其实可以看看这篇博文:https://www.jianshu.com/p/58f36f37405a