美文网首页工作生活
ArrayDeque中allocateElements的分析

ArrayDeque中allocateElements的分析

作者: YocnZhao | 来源:发表于2019-07-01 14:47 被阅读0次

    最近看了下ArrayDeque的源码,发现一个东西很有意思,之前也看到了,没有细想,现在仔细看了看,决定写下来。

    /**
         * The array in which the elements of the deque are stored.
         * The capacity of the deque is the length of this array, which is
         * always a power of two. The array is never allowed to become
         * full, except transiently within an addX method where it is
         * resized (see doubleCapacity) immediately upon becoming full,
         * thus avoiding head and tail wrapping around to equal each
         * other.  We also guarantee that all array cells not holding
         * deque elements are always null.
         */
        transient Object[] elements; // non-private to simplify nested class access
     /**
         * The minimum capacity that we'll use for a newly created deque.
         * Must be a power of 2.
         */
        private static final int MIN_INITIAL_CAPACITY = 8;
    
        // ******  Array allocation and resizing utilities ******
    
        /**
         * Allocates empty array to hold the given number of elements.
         *
         * @param numElements  the number of elements to hold
         */
        private void allocateElements(int numElements) {
            int initialCapacity = MIN_INITIAL_CAPACITY;
            // Find the best power of two to hold elements.
            // Tests "<=" because arrays aren't kept full.
            if (numElements >= initialCapacity) {
                initialCapacity = numElements;
                initialCapacity |= (initialCapacity >>>  1);
                initialCapacity |= (initialCapacity >>>  2);
                initialCapacity |= (initialCapacity >>>  4);
                initialCapacity |= (initialCapacity >>>  8);
                initialCapacity |= (initialCapacity >>> 16);
                initialCapacity++;
    
                if (initialCapacity < 0)   // Too many elements, must back off
                    initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
            }
            elements = new Object[initialCapacity];
        }
    
    /**
         * Doubles the capacity of this deque.  Call only when full, i.e.,
         * when head and tail have wrapped around to become equal.
         */
        private void doubleCapacity() {
            assert head == tail;
            int p = head;
            int n = elements.length;
            int r = n - p; // number of elements to the right of p
            int newCapacity = n << 1;
            if (newCapacity < 0)
                throw new IllegalStateException("Sorry, deque too big");
            Object[] a = new Object[newCapacity];
            System.arraycopy(elements, p, a, 0, r);
            System.arraycopy(elements, 0, a, r, p);
            elements = a;
            head = 0;
            tail = n;
        }
    

    这是ArrayDeque在调用传入指定数量的构造方法的时候会调用的方法,目的是分配一个合适长度的数组供使用,所以其实这个方法就是为了分配合适数量的数组。

        public ArrayDeque(int numElements) {
            allocateElements(numElements);
        }
    

    其实ArrayDeque来做扩容的时候都是通过doubleCapacity()方法来做的,ArrayDeque的容量只允许是2的倍数,那我们看上面我们要指定一个数来做构造方法的时候,ArrayDeque是怎么保证elements既是2的倍数又能够装起来我们需要的元素的?
    这就是allocateElements这个方法要做的了。
    主要的代码就是

                initialCapacity |= (initialCapacity >>>  1);
                initialCapacity |= (initialCapacity >>>  2);
                initialCapacity |= (initialCapacity >>>  4);
                initialCapacity |= (initialCapacity >>>  8);
                initialCapacity |= (initialCapacity >>> 16);
                initialCapacity++; 
    

    我们知道>>>是无符号右移,高位补零。|是或操作,0|0=0否则为1。
    我们来看initialCapacity |= (initialCapacity >>> 1);
    也就是initialCapacity = initialCapacity | (initialCapacity >>> 1);
    怎么来理解这个操作,自己或上自己右移一位,费解~
    其实我们这么想,不管initialCapacity是几,所有的数最高位一定是1,比如
    8 -> 1000
    8>>>1 -> 0100
    8|(8>>>1) -> 1100
    其实从这个例子我们就可以看出来规律,只要这个数二进制大于两位,经过initialCapacity |= (initialCapacity >>> 1);高位总是会变成11xxxxxx,那后面的几步操作就好理解了。
    我们用一个极端的例子来看,我们设置initialCapacity为Integer.MAX_VALUE >> 2,也就是1073741824,它的二进制是1000000000000000000000000000000。

    initialCapacity (initialCapacity >>> x) 结果
    1 1000000000000000000000000000000 0100000000000000000000000000000 1100000000000000000000000000000
    2 1100000000000000000000000000000 0011000000000000000000000000000 1111000000000000000000000000000
    4 1111000000000000000000000000000 0000111100000000000000000000000 1111111100000000000000000000000
    8 1111111100000000000000000000000 0000000011111111000000000000000 1111111111111111000000000000000
    16 1111111111111111000000000000000 000000000000000111111111111111 1111111111111111111111111111111

    运算完的结果就是全是1,有多少位就是多少个1,所以说8到15之间,运算完的结果都是1111,然后执行initialCapacity++; ,结果就是10000了,所以ArrayDeque的默认capacity是16。
    所以说只要是32位以内,不管什么数都可以搞定。起决定性作用的只是你int值最高位的那个1罢了。
    还有几个小细节要注意:
    1、java里面 + - 加号减号的优先级大于<< >> >>> 左移 右移 无符号右移的优先级,如果要做先右移再减一的操作需要用括号括起来,比如
    (Integer.MAX_VALUE >> 2) - 1
    表示的是max右移两位再减一,如果是Integer.MAX_VALUE >> 2 - 1 表示max右移一位。
    2、运算结束后要执行的这句。

    if (initialCapacity < 0)   // Too many elements, must back off
                   initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    

    如果结束后initialCapacity是负数。表示越界了,最后得到的数大于Integer.MAX_VALUE了。就直接把initialCapacity赋值为Integer.MAX_VALUE >> 1。这个时候其实是比user期待的numElements小的,这个时候怎么办呢?其实就交给后面doubleCapacity去处理了。

    相关文章

      网友评论

        本文标题:ArrayDeque中allocateElements的分析

        本文链接:https://www.haomeiwen.com/subject/ijhgcctx.html