Java集合学习记录---ArrayList的add和部分Integer内容的学习


前言

学习Java的list时,发现只靠脑子记很容易忘掉,写在博客里更能加深印象,梳理逻辑。
请谨慎阅读,因为里面的内容是我的学习记录,可能有很多不对的地方。


一、Integer的一点了解

1.基本说明,和自动装箱的intValue/Valueof

Integer是基本类型的包装int->Integer。
关于Integer的赋值,如

Integer i = 32;//i = Integer.valueOf(32);
int t = i;//t = i.intValue()

32是int类型,赋值为Integer中会自动转换–好像被叫作自动装箱?自动调用了Integer.valueOf(32)
int t = i 调用了i.intValue();
valueOf(),源码如下

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
        //若获取的i在缓存的[low,high]中,返回这个Integer的引用
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);//返回新建
    }

使用了缓存池,根据这个返回方式,是存储了所有从low 到high的Integer对象,至于这个缓存池的作用,我还没有了解,查找(alt+7,ctrl+f12)了IntegerCache结构后,在定义处发现了几个不懂的地方:

		static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

如所示,为什么 low最小值是写在代码里的,而high却是可以配置的(在下面初始化的代码里)?为什么要用archivedCache初始化数组,而用常量数组cache访问?可能是为了避免修改缓存池的值。
之后在这段代码

if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

可以看到,获取虚拟机设置的最大值后,取其为上界,但是必须包含[-128,127],缓存长度最长为MaxInt,居然是2^31-1,这么大的 缓存,震惊。
之后从CDS读取缓存。。还看不懂,但是看懂了如果没有读取到,会怎么样。

// Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }

如所示,就创建新数组,并从low到high赋值,但是有个疑惑,为什么用这种for(i)j++的办法赋值?while(j<=high)c[i] = new Integer(j++);比这个差在哪呢?
而i.intValue()就直接返回它存储的常量value,这个常量创建时就存储了。

二、ArrayList的了解

0.ArrayList属性

ArrayList属性
如图是其属性,依次是
序列id
默认容量为10
默认空数组(有参构造,初始化容量)
默认空数组(无参构造,第一次加入元素扩容为默认容量)
存放元素的数组(比如雷元素,岩元素)
有效元素个数

1.add函数

直接调用的源码如下

public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

看上面的注释说永远返回true是Collection.add规定的?点开发现写着:如果集合按调用改变了就返回真,emm,确实。
modCount是因为这个容器线程不安全,为了防止并发修改同一个容器出错,在迭代器和原容器里都放了个modCount,迭代器里叫expectedModCount,每次调用,如果二者的modcount,即修改次数不一致,即说明有别的线程修改了这个容器,迭代器可能失效。
请看Integer.itr.next()的代码:

 public E next() {
            checkForComodification();

和check…函数的代码

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

这个函数定义为final,我本来以为是指函数里不会修改变量的值,结果查到是不能继承,很合理,原来是不能修改方法本身。
里面检查自己的修改次数和容器的一不一样,抛出并发修改异常。

再看调用的方法 add(e,data,size);

private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

这个方法最后设置size = s+1,而不是size++,也许是为了并发?(被称为操作的原子性?)或者一些奇怪的考虑。
先判断s是不是等于存储数组的上限—我才发现size可能不定是存储数组的大小,而是数组内有效元素的个数,为此我先看了ArrayList的构造函数,又想了一会,想到C++里的capacity这个变量应该是java里的elementData.length,不用单独存放了,我真傻,真的。

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);
        }
    }

构造函数里,无参直接返回无参默认数组,EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别就是,前者返回于有参构造,后者于无参构造,都是空的,但是添加的时候就能比较地址判断怎么构造的了,这个说明了解于这里,里面写的无惨构造函数用词有点奇怪啊。
这个构造函数挺简单的,就判断大小,申请数组而已。

上面的add函数调用了grow(),是增长空间用的函数,代码如下

private Object[] grow() {
        return grow(size + 1);
    }

只是返回了比当前+1的大小,再点进去看

    private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //取得存储数组长度
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //增长分为两种,如果初始未指定大小,证明用的是无参的,根据上面写的能知道是用是不是等于DEE判断的
        //如果不是无参,或者是无参,但是仍需扩充容量---这时大小应该是DEFAULT_CAPACITY,10(如果一直一个一个加入的话)
        //总之,无参的会在第一次需要扩容时试图赋值为10(默认容量),不够就为最小所需容量
        //有参会执行下面的内容
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth最小增长 */
                    oldCapacity >> 1           /* preferred growth希望的增长 */);
                    
        //取得新数组的长度,注,此处还没申请数组,只判断了长度,根据参数和注释能看出,原长度为oldCapacity,
        //最小增长是所需长度,期望增长是旧长度的0.5倍,加上旧长度,扩容1.5倍。
        //之所以要在这判断,应该是防止溢出等等
            return elementData = Arrays.copyOf(elementData, newCapacity);
            //这个调用是真申请和复制数组。
        } else {
            //数组长度为0,就是没有原内容,如果是无参构造的,就执行这个
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
            //但是如果数组长度是0,而且不是默认构造,居然也用上面那个--
            //当然了,如果长度是0,就证明不希望使用默认值,声明为0
            //这样的话,小数组是用默认的好还是有参的?
            //默认的直接返回空数组,添加时才返回,
            //如果定义了长度,构造时就返回那个长度的数组,
            //而且如果定义的小,比如0,增长到10生成新数组的次数是 1-2-3-4-6-9-13一共7次
            //应该是这样,但是IDEA的调试好像会把默认的大小0改成1,还会往数组里加些奇怪的东西,所以没有测试成功
            //因为调试 调用的modcount在除构建以外第一次显示modcount已经是2,而且看elementData里已经有两个元素,好像是url,debug的jar包,证明已经加入了2个东西,至少已经操作过两次,而构建的initcapacity传的0被变成了1,不知道为啥。
            //那个调试应该是在构建的时候调用了啥注入的东西,因为很奇怪的加入了debug的url。
        }
    }

再看newLength,

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
        // preconditions not checked because of inlining
        // assert oldLength >= 0
        // assert minGrowth > 0
		//这确保了旧长度和最小增长之和应该大于0
        int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
        //用最小增长和期望增长中更大的判断期望的长度,注释说可能会溢出
        if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        //没溢出的话,返回长度
            return prefLength;
        } else {
        //这部分是溢出了,
            // put code cold in a separate method
            return hugeLength(oldLength, minGrowth);
        }
    }

其中SOFT_MAX_ARRAY_LENGTH定义为

public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

为什么是Max_value - 8,注释写因为某些VM的实现限制(比如hotspot),可能在申请接近MAX_VALUE大小的数组的时候就算有足够的空间,也会报vm限`制的内存溢出错误,是由于比如object的头大小之类的,所以最大值要选小一点。
如果溢出,看下hugeLength

private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth;
        //算出长度,为了别的地方调用,或者防止传进来溢出的值?
        if (minLength < 0) { // overflow
        //溢出就抛出异常---确实,溢出肯定大于SOFT_MAX_ARRAY_LENGTH
            throw new OutOfMemoryError(
                "Required array length " + oldLength + " + " + minGrowth + " is too large");
        } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
        //emmmm,为啥要返回这个,可能是因为调用这个的都是接近溢出的那种?或者只返回minlen和SMALEN的最大可能值?
            return SOFT_MAX_ARRAY_LENGTH;
        } else {
        //这是在smal和intmax之间的部分
            return minLength;
        }
    }

回到上面的grow(int min…)
判断完新申请的数组长度之后,copyOf正式申请空间

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

直接调用函数,

    @IntrinsicCandidate
    //说明有hotspot的基于cpu指令的高效实现,而非下面的实现?不清楚
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
            //这句是申请新数组,还看不太懂为什么要判断
            //总之是判断传进来的申请的是不是object类型的数组,是就返回object类型的,不然就调用生成函数
            //newInstance点到最底没有实现,没有什么太重要的,差不多就看到这了。
            //
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
                         //这几个居然是公有函数,怪不得要加那么多判断。
                         //也没有实现,应该就是把旧数组复制进新数组,还有个判断,只复制最多新数组长度的数量。
        return copy;
    }

总之,grow函数最后会将原数组的内容复制进新申请的更大的数组里,最后add函数

private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
        //如果数组满了
            elementData = grow();
            //申请长度+1大小的数组
        elementData[s] = e;
        //把新内容放进数组,居然不是elementData[size++]=e,想来是为了并发考虑
        size = s + 1;
        //更新size
    }

总结

因此,add的过程就是,先增加modcount,防多线程修改错误,调用添加,如果当前大小不够,就申请增长,一次增长有几种情况,如果是无参构造并且第一次添加,就直接增长到10,如果不是无参构造,就扩容到1.5倍,至少+1,先判断扩容后长度是不是溢出,扩容后,将原数组复制到新数组,再加入新的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值