jdk1.8 的ArrayList

这篇文章没有写完。以后继续更新。自己写给自己看的,有些不规范的地方:类名没有驼峰形式大写,方法或成员变量起名随意

一、概述

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

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值