《Java Generics and Collections》笔记 Queues I

简单的看一下它特有的几个方法
boolean offer(E e) // insert the given element if possible
The methods that throw an exception for an empty queue are:
E element() // retrieve but do not remove the head element
E remove() // retrieve and remove the head element
====
E peek() // retrieve but do not remove the head element
E poll() // retrieve and remove the head element
除了上面提到的两个方法在queue为空时调用会抛出异常,其他的比如peek,poll,都用null返回值来表示queue为空,所以不允许放入null值。

14.1. Using the Methods of Queue

这部分内容比较少,代码中使用了ArrayDeque,我们接下来就来看一下ArrayDeque的实现方法。

ArrayDeque类实现Queue接口。

对于数组实现的Deque来说,数据结构上比较简单,只需要一个存储数据的数组以及头尾两个索引即可。由于数组是固定长度的,所以很容易就得到数组的头和尾,那么对于数组的操作只需要移动头和尾的索引即可。

特别说明的是 ArrayDeque并不是一个固定大小的队列 , 每次队列满了以后就将队列容量扩大一倍(doubleCapacity()),因此加入一个元素总是能成功 ,而且也不会抛出一个异常。也就是说ArrayDeque是一个没有容量限制的队列。

同样继续性能的考虑,使用System.arraycopy复制一个数组比循环设置要高效得多。

ArrayDeque不是线程安全的 。 

ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。 

当作为栈使用时,性能比Stack好(Stack是线程安全类的原因?);当作为队列使用时,性能比LinkedList好。

API

总结:

1. 两个重要的索引:head和tail 

Java代码
  1. // 第一个元素的索引
  2. privatetransientint  head;  
  3. // 下个要添加元素的位置,为末尾元素的索引 + 1
  4. privatetransientint  tail;  
  1. public  ArrayDeque() {  
  2.     elements = (E[])  new  Object[ 16 ];  // 默认的数组长度大小
  3. }  
  4. public  ArrayDeque( int  numElements) {  
  5.     allocateElements(numElements);  // 需要的数组长度大小
  6. }  
  7. public  ArrayDeque(Collection<?  extends  E> c) {  
  8.     allocateElements(c.size());  // 根据集合来分配数组大小
  9.     addAll(c);  // 把集合中元素放到数组中
  10. }  

3. 分配合适大小的数组 

Java代码
  1. privatevoid  allocateElements( int  numElements) {  
  2. int  initialCapacity = MIN_INITIAL_CAPACITY;  
  3. // 找到大于需要长度的最小的2的幂整数。
  4. // Tests "<=" because arrays aren't kept full.
  5. if  (numElements >= initialCapacity) {  
  6.         initialCapacity = numElements;  
  7.         initialCapacity |= (initialCapacity >>>   1 );  
  8.         initialCapacity |= (initialCapacity >>>   2 );  
  9.         initialCapacity |= (initialCapacity >>>   4 );  
  10.         initialCapacity |= (initialCapacity >>>   8 );  
  11.         initialCapacity |= (initialCapacity >>>  16 );  
  12.         initialCapacity++;  
  13. if  (initialCapacity <  0 )    // Too many elements, must back off
  14.             initialCapacity >>>=  1 ; // Good luck allocating 2 ^ 30 elements
  15.     }  
  16.     elements = (E[])  new  Object[initialCapacity];  
  17. }

4. 扩容 

Java代码
  1. // 扩容为原来的2倍。
  2. privatevoid  doubleCapacity() {  
  3. assert  head == tail;  
  4. int  p = head;  
  5. int  n = elements.length;  
  6. int  r = n - p;  // number of elements to the right of p
  7. int  newCapacity = n <<  1 ;  
  8. if  (newCapacity <  0 )  
  9. thrownew  IllegalStateException( "Sorry, deque too big" );  
  10.     Object[] a =  new  Object[newCapacity];  
  11. // 既然是head和tail已经重合了,说明tail是在head的左边。
  12.     System.arraycopy(elements, p, a,  0 , r);  // 拷贝原数组从head位置到结束的数据
  13.     System.arraycopy(elements,  0 , a, r, p);  // 拷贝原数组从开始到head的数据
  14.     elements = (E[])a;  
  15.     head =  0 ;  // 重置head和tail为数据的开始和结束索引
  16.     tail = n;  
  17. }  

  1. // 拷贝该数组的所有元素到目标数组
  2. private  <T> T[] copyElements(T[] a) {  
  3. if  (head < tail) {  // 开始索引大于结束索引,一次拷贝
  4.         System.arraycopy(elements, head, a,  0 , size());  
  5.     }  elseif  (head > tail) {  // 开始索引在结束索引的右边,分两段拷贝
  6. int  headPortionLen = elements.length - head;  
  7.         System.arraycopy(elements, head, a,  0 , headPortionLen);  
  8.         System.arraycopy(elements,  0 , a, headPortionLen, tail);  
  9.     }  
  10. return  a;  
  11. }  
  12. 拷贝到a里面,并返回。

5. 添加元素 

Java代码
  1. publicvoid  addFirst(E e) {  
  2. if  (e ==  null )  
  3. thrownew  NullPointerException();  
  4.     elements[head = (head -  1 ) & (elements.length -  1 )] = e;  
  5. if  (head == tail)  // head和tail不可以重叠
  6.         doubleCapacity();  
  7. }  

  1. head-1为负值怎么办?
现在我们可以回答这个问题了,当head-1为负值时也不会有问题。我们看一下,加入head=0,现在head-1=-1,我们知道-1的二进制是全1,所以与mask相与后得到的还是mask,就是这里的length-1,刚好是我们需要调整的值,同时如果tail的值等于length-1,那么tail+1之后与mask相与的结果正好是0,也就是自动实现了循环。这里的length是2的n次方。如果不是2的n次方则不要用这种技巧。
  1. public   void  addLast(E e) {  
  2.      if  (e ==  null )  
  3.          throw   new  NullPointerException();  
  4.      // tail位置是空的,把元素放到这。   
  5.     elements[tail] = e;  
  6.      if  ( (tail = (tail +  1 ) & (elements.length -  1 )) == head)  
  7.         doubleCapacity();  
  8. }  

    

  1. public   boolean  offerFirst(E e) {  
  2.     addFirst(e);  
  3.      return   true ;  
  4. }  
  1. public   boolean  offerLast(E e) {  
  2.     addLast(e);  
  3.      return   true ;  
  4. }

6.

 删除元素 

删除首尾元素: 

Java代码
  1. public  E removeFirst() {  
  2.     E x = pollFirst();  
  3.      if  (x ==  null )  
  4.          throw   new  NoSuchElementException();  
  5.      return  x;  
  6. }  
  7.   
  8. public  E removeLast() {  
  9.     E x = pollLast();  
  10.      if  (x ==  null )  
  11.          throw   new  NoSuchElementException();  
  12.      return  x;  
  13. }  

  1. public  E pollFirst() {  
  2.      int  h = head;  
  3.     E result = elements[h];  // Element is null if deque empty   
  4.      if  (result ==  null )  
  5.          return   null ;  
  6.      // 表明head位置已为空   
  7.     elements[h] =  null ;      // Must null out slot   
  8.     head = (h +  1 ) & (elements.length -  1 );  // 处理临界情况(当h为elements.length - 1时),与后的结果为0。   
  9.      return  result;  


  1. public  E pollLast() {  
  2.      int  t = (tail -  1 ) & (elements.length -  1 );  // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。   
  3.     E result = elements[t];  
  4.      if  (result ==  null )  
  5.          return   null ;  
  6.     elements[t] =  null ;  
  7.     tail = t;  // tail指向的是下个要添加元素的索引。   
  8.      return  result;  
  9. }  

删除指定元素: 

Java代码
  1. public   boolean  removeFirstOccurrence(Object o) {  
  2.      if  (o ==  null )  
  3.          return   false ;  
  4.      int  mask = elements.length -  1 ;  
  5.      int  i = head;  
  6.     E x;  
  7.      while  ( (x = elements[i]) !=  null ) {  
  8.          if  (o.equals(x)) {  
  9.             delete(i);  
  10.              return   true ;  
  11.         }  
  12.         i = (i +  1 ) & mask;  // 从头到尾遍历   
  13.     }  
  14.      return   false ;  
  15. }  

  1. public   boolean  removeLastOccurrence(Object o) {  
  2.      if  (o ==  null )  
  3.          return   false ;  
  4.      int  mask = elements.length -  1 ;  
  5.      int  i = (tail -  1 ) & mask;  // 末尾元素的索引   
  6.     E x;  
  7.      while  ( (x = elements[i]) !=  null ) {  
  8.          if  (o.equals(x)) {  
  9.             delete(i);  
  10.              return   true ;  
  11.         }  
  12.         i = (i -  1 ) & mask;  // 从尾到头遍历   
  13.     }  
  14.      return   false ;  
  15. }  
  1. private   boolean  delete( int  i) {  
    1. checkInvariants();  
  1.      final  E[] elements =  this .elements;  
  2.      final   int  mask = elements.length -  1 ;  
  3.      final   int  h = head;  
  4.      final   int  t = tail;  
  5.      final   int  front = (i - h) & mask;  // i前面的元素个数   
  6.      final   int  back  = (t - i) & mask;  // i后面的元素个数   
  7.   
  8.      // Invariant: head <= i < tail mod circularity   
  9.      if  (front >= ((t - h) & mask))  // i不在head和tail之间   
  10.          throw   new  ConcurrentModificationException();  
  11.   
  12.      // Optimize for least element motion   
  13.      if  (front < back) {  // i的位置靠近head,移动开始的元素,返回false。   
  14.          if  (h <= i) {  
  15.             System.arraycopy(elements, h, elements, h +  1 , front);  
  16.         }  else  {  // Wrap around   
  17.             System.arraycopy(elements,  0 , elements,  1 , i);  
  18.             elements[ 0 ] = elements[mask];  // 处理边缘元素   
  19.             System.arraycopy(elements, h, elements, h +  1 , mask - h);  
  20.         }  
  21.         elements[h] =  null ;  
  22.         head = (h +  1 ) & mask;  // head位置后移   
  23.          return   false ;  
  24.     }  else  {  // i的位置靠近tail,移动末尾的元素,返回true。   
  25.          if  (i < t) {  // Copy the null tail as well   
  26.             System.arraycopy(elements, i +  1 , elements, i, back);  
  27.             tail = t -  1 ;  
  28.         }  else  {  // Wrap around   
  29.             System.arraycopy(elements, i +  1 , elements, i, mask - i);  
  30.             elements[mask] = elements[ 0 ];  
  31.             System.arraycopy(elements,  1 , elements,  0 , t);  
  32.             tail = (t -  1 ) & mask;  
  33.         }  
  34.          return   true ;  
  35.     }  
  36. }  

7.

 获取元素 

Java代码
  1. public  E getFirst() {  
  2.     E x = elements[head];  
  3.      if  (x ==  null )  
  4.          throw   new  NoSuchElementException();  
  5.      return  x;  
  6. }  
  7.   
  8. public  E getLast() {  
  9.     E x = elements[(tail -  1 ) & (elements.length -  1 )];  // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。   
  10.      if  (x ==  null )  
  11.          throw   new  NoSuchElementException();  
  12.      return  x;  
  13. }  

public  E peekFirst() {  
     return  elements[head];  // elements[head] is null if deque empty   
}  

public  E peekLast() {  
     return  elements[(tail -  1 ) & (elements.length -  1 )];  
}  

10.

 集合方法 

Java代码
  1. public   int  size() {  
  2.      return  (tail - head) & (elements.length -  1 );  // 和elements.length - 1进行与操作是为了处理当tail < head时的情况。   
  3. }  
  4.   
  5. public   boolean  isEmpty() {  
  6.      return  head == tail;  // tail位置的元素一定为空,head和tail相等,也为空。   
  7. }  
  1. public   void  clear() {  
  2.      int  h = head;  
  3.      int  t = tail;  
  4.      if  (h != t) {  // clear all cells   
  5.         head = tail =  0 ;  // 重置首尾索引   
  6.          int  i = h;  
  7.          int  mask = elements.length -  1 ;  
  8.          do  {  
  9.             elements[i] =  null ;  // 清除元素   
  10.             i = (i +  1 ) & mask;  
  11.         }  while  (i != t);  
  12.     }  
  13. }  
  14.   
  15. public  Object[] toArray() {  
  16.      return  copyElements( new  Object[size()]);    
  17. }  
  18.   
  19. public  <T> T[] toArray(T[] a) {  
  20.      int  size = size();  
  21.      if  (a.length < size)  // 目标数组大小不够   
  22.         a = (T[])java.lang.reflect.Array.newInstance(  
  23.                 a.getClass().getComponentType(), size);  // 利用反射创建类型为T,大小为size的数组。   
  24. yElements(a);  // 拷贝所有元素到目标数组。   
  25.      if  (a.length > size)  
  26.         a[size] =  null ;  // 结束标识   
  27.      return  a;  
  28. }  

11.

 Object方法 

Java代码
  1. public  ArrayDeque<E> clone() {  
  2.      try  {  
  3.         ArrayDeque<E> result = (ArrayDeque<E>)  super .clone();  
  4.         result.elements = Arrays.copyOf(elements, elements.length);  // 深度复制。   
  5.          return  result;  
  6.   
  7.     }  catch  (CloneNotSupportedException e) {  
  8.          throw   new  AssertionError();  
  9.     }  
  10. }  
这里面我们发现了一些与位运算有关的操作,比较有意思。比如对一个4个比特位的数来说,-4的二进制补码就是1100,我们把它与它的掩码相与,结果为0100,也就是4,这也正说明了减去一个数与加上一个它的补的关系,这个放到数轴上更加容易理解。其他比如(num&(num-1))==0,说明num是2的n次方等等。






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值