【问题标题】:Why the ArrayDeque class use bitwise operation in the pollFirst method?为什么 ArrayDeque 类在 pollFirst 方法中使用按位运算?
【发布时间】:2016-08-17 22:29:54
【问题描述】:

我翻翻java源代码尝试学习collection的实现。 在 ArrayDeque 类中发现了一个有趣的东西。

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
}

public E pollLast() {
    int t = (tail - 1) & (elements.length - 1);
    @SuppressWarnings("unchecked")
    E result = (E) elements[t];
    if (result == null)
        return null;
    elements[t] = null;
    tail = t;
    return result;
}

以下两行是什么意思? 是按位运算吗?他们为什么使用它,这里的目的是什么?

    head = (h + 1) & (elements.length - 1);
    int t = (tail - 1) & (elements.length - 1);

我知道使用按位的一种情况是将 2 个值打包到 1 个变量中。但似乎情况并非如此。

【问题讨论】:

    标签: java collections bitwise-operators bitwise-and arraydeque


    【解决方案1】:

    这是计算(head+1) % elements.length 的一种更有效的方法,我们可以这样做,因为elements.length 是 2 的幂。它更有效是因为 mod 运算符比 更昂贵按位和

    另一方面,仅对tail 使用 mod 是行不通的,因为在 Java 中,-1 % N == -1

    【讨论】:

      【解决方案2】:

      看一下初始化代码 - Deque 表示为一个数组,其大小始终是 2 的幂:

      195    public ArrayDeque(int numElements) {
      196        allocateElements(numElements);
      197    }
      
      124    private void allocateElements(int numElements) {
      125        int initialCapacity = MIN_INITIAL_CAPACITY;
      126        // Find the best power of two to hold elements.
      127        // Tests "<=" because arrays aren't kept full.
      128        if (numElements >= initialCapacity) {
      129            initialCapacity = numElements;
      130            initialCapacity |= (initialCapacity >>>  1);
      131            initialCapacity |= (initialCapacity >>>  2);
      132            initialCapacity |= (initialCapacity >>>  4);
      133            initialCapacity |= (initialCapacity >>>  8);
      134            initialCapacity |= (initialCapacity >>> 16);
      135            initialCapacity++;
      136
      137            if (initialCapacity < 0)   // Too many elements, must back off
      138                initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
      139        }
      140        elements = (E[]) new Object[initialCapacity];
      141    }
      

      所以elements.length - 1 是二进制的,基本上是一系列1 在数组大小超出之前的位。

      例如,如果 elements 初始化为大小为 16 的数组,则 elements.length - 1 为 15,这意味着 0..001111(截断前导零)。

      所以当head 元素在pollFirst 方法中被重置时(加一),按位&amp; 运算符用于使双端队列循环。同样,如果 elements 的大小为 16,而当前 head 为 15,那么 head + 1 将是 16,所以:

      10000
      01111
      -----
      00000
      

      意思是,head 被重置为索引 0。这允许您重新使用已分配的空间,使用数组及其在插入和检索中的 O(1) 效率,而无需分配新空间。

      pollLast 也是如此,您重置了 tail 变量,即如果 tail 为 0,elements 的大小为 16,则:

      tail      00000
      tail-1    11111   (-1 in two's complement)
                01111
                -----
                01111
      

      意思是 tail 减一,但从 0 移动到 elements.length - 1

      您可以使用更“复杂”的 if 语句(或使用三元运算符)实现相同的效果,但这是实现循环数组的一种相当常见且可接受的方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-06
        • 2023-04-06
        • 1970-01-01
        • 1970-01-01
        • 2019-05-31
        • 1970-01-01
        • 2018-08-03
        • 1970-01-01
        相关资源
        最近更新 更多