Java多线程(六)之Deque与LinkedBlockingDeque深入分析

转载自  Java多线程(六)之Deque与LinkedBlockingDeque深入分析

一、双向队列 Deque


Queue除了前面介绍的实现外,还有一种双向的Queue实现Deque。这种队列允许在队列头和尾部进行入队出队操作,因此在功能上比Queue显然要更复杂。下图描述的是Deque的完整体系图。需要说明的是LinkedList也已经加入了Deque的一部分(LinkedList是从jdk1.2 开始就存在数据结构)。

 


Deque在Queue的基础上增加了更多的操作方法。


从上图可以看到,Deque不仅具有FIFO的Queue实现,也有FILO的实现,也就是不仅可以实现队列,也可以实现一个堆栈。

同时在Deque的体系结构图中可以看到,实现一个Deque可以使用数组(ArrayDeque),同时也可以使用链表(LinkedList),还可以同实现一个支持阻塞的线程安全版本队列LinkedBlockingDeque。


1、ArrayDeque实现Deque

 

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

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

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


1.1、ArrayDeque的源码解析


//数组双端队列ArrayDeque的源码解析
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable{/*** 存放队列元素的数组,数组的长度为“2的指数”*/private transient E[] elements;/***队列的头部索引位置,(被remove()或pop()操作的位置),当为空队列时,首尾index相同*/private transient int head;/*** 队列的尾部索引位置,(被 addLast(E), add(E), 或 push(E)操作的位置).*/private transient int tail;/*** 队列的最小容量(大小必须为“2的指数”)*/private static final int MIN_INITIAL_CAPACITY = 8;// ******  Array allocation and resizing utilities ******/*** 根据所给的数组长度,得到一个比该长度大的最小的2^p的真实长度,并建立真实长度的空数组*/private void allocateElements(int numElements) {int initialCapacity = MIN_INITIAL_CAPACITY;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 offinitialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements}elements = (E[]) new Object[initialCapacity];}/*** 当队列首尾指向同一个引用时,扩充队列的容量为原来的两倍,并对元素重新定位到新数组中*/private void doubleCapacity() {assert head == tail;int p = head;int n = elements.length;int r = n - p; // number of elements to the right of pint 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 = (E[])a;head = 0;tail = n;}/*** 拷贝队列中的元素到新数组中*/private <T> T[] copyElements(T[] a) {if (head < tail) {System.arraycopy(elements, head, a, 0, size());} else if (head > tail) {int headPortionLen = elements.length - head;System.arraycopy(elements, head, a, 0, headPortionLen);System.arraycopy(elements, 0, a, headPortionLen, tail);}return a;}/*** 默认构造队列,初始化一个长度为16的数组*/public ArrayDeque() {elements = (E[]) new Object[16];}/*** 指定元素个数的构造方法*/public ArrayDeque(int numElements) {allocateElements(numElements);}/*** 用一个集合作为参数的构造方法*/public ArrayDeque(Collection<? extends E> c) {allocateElements(c.size());addAll(c);}//插入和删除的方法主要是: addFirst(),addLast(), pollFirst(), pollLast()。//其他的方法依赖于这些实现。/*** 在双端队列的前端插入元素,元素为null抛异常*/public void addFirst(E e) {if (e == null)throw new NullPointerException();elements[head = (head - 1) & (elements.length - 1)] = e;if (head == tail)doubleCapacity();}/***在双端队列的末端插入元素,元素为null抛异常*/public void addLast(E e) {if (e == null)throw new NullPointerException();elements[tail] = e;if ( (tail = (tail + 1) & (elements.length - 1)) == head)doubleCapacity();}/*** 在前端插入,调用addFirst实现,返回boolean类型*/public boolean offerFirst(E e) {addFirst(e);return true;}/*** 在末端插入,调用addLast实现,返回boolean类型*/public boolean offerLast(E e) {addLast(e);return true;}/*** 删除前端,调用pollFirst实现*/public E removeFirst() {E x = pollFirst();if (x == null)throw new NoSuchElementException();return x;}/*** 删除后端,调用pollLast实现*/public E removeLast() {E x = pollLast();if (x == null)throw new NoSuchElementException();return x;}//前端出对(删除前端)public E pollFirst() {int h = head;E result = elements[h]; // Element is null if deque emptyif (result == null)return null;elements[h] = null;     // Must null out slothead = (h + 1) & (elements.length - 1);return result;}//后端出对(删除后端)public E pollLast() {int t = (tail - 1) & (elements.length - 1);E result = elements[t];if (result == null)return null;elements[t] = null;tail = t;return result;}/*** 得到前端头元素*/public E getFirst() {E x = elements[head];if (x == null)throw new NoSuchElementException();return x;}/*** 得到末端尾元素*/public E getLast() {E x = elements[(tail - 1) & (elements.length - 1)];if (x == null)throw new NoSuchElementException();return x;}public E peekFirst() {return elements[head]; // elements[head] is null if deque empty}public E peekLast() {return elements[(tail - 1) & (elements.length - 1)];}/*** 移除此双端队列中第一次出现的指定元素(当从头部到尾部遍历双端队列时)。*/public boolean removeFirstOccurrence(Object o) {if (o == null)return false;int mask = elements.length - 1;int i = head;E x;while ( (x = elements[i]) != null) {if (o.equals(x)) {delete(i);return true;}i = (i + 1) & mask;}return false;}/*** 移除此双端队列中最后一次出现的指定元素(当从头部到尾部遍历双端队列时)。*/public boolean removeLastOccurrence(Object o) {if (o == null)return false;int mask = elements.length - 1;int i = (tail - 1) & mask;E x;while ( (x = elements[i]) != null) {if (o.equals(x)) {delete(i);return true;}i = (i - 1) & mask;}return false;}// *** 队列方法(Queue methods) ***/*** add方法,添加到队列末端*/public boolean add(E e) {addLast(e);return true;}/*** 同上*/public boolean offer(E e) {return offerLast(e);}/*** remove元素,删除队列前端*/public E remove() {return removeFirst();}/*** 弹出前端(出对,删除前端)*/public E poll() {return pollFirst();}public E element() {return getFirst();}public E peek() {return peekFirst();}// *** 栈 方法(Stack methods) ***public void push(E e) {addFirst(e);}public E pop() {return removeFirst();}private void checkInvariants() { ……    }private boolean delete(int i) {   ……   }// *** 集合方法(Collection Methods) ***……// *** Object methods ***……
}
整体来说:1个数组,2个index(head 索引和tail索引)。实现比较简单,容易理解。



2、LinkedList实现Deque



对于LinkedList本身而言,数据结构就更简单了,除了一个size用来记录大小外,只有head一个元素Entry。对比Map和Queue的其它数据结构可以看到这里的Entry有两个引用,是双向的队列。

在示意图中,LinkedList总是有一个“傀儡”节点,用来描述队列“头部”,但是并不表示头部元素,它是一个执行null的空节点。

队列一开始只有head一个空元素,然后从尾部加入E1(add/addLast),head和E1之间建立双向链接。然后继续从尾部加入E2,E2就在head和E1之间建立双向链接。最后从队列的头部加入E3(push/addFirst),于是E3就在E1和head之间链接双向链接。

双向链表的数据结构比较简单,操作起来也比较容易,从事从“傀儡”节点开始,“傀儡”节点的下一个元素就是队列的头部,前一个元素是队列的尾部,换句话说,“傀儡”节点在头部和尾部之间建立了一个通道,是整个队列形成一个循环,这样就可以从任意一个节点的任意一个方向能遍历完整的队列。

同样LinkedList也是一个没有容量限制的队列,因此入队列(不管是从头部还是尾部)总能成功。

 

3、小结 


上面描述的ArrayDeque和LinkedList是两种不同方式的实现,通常在遍历和节省内存上ArrayDeque更高效(索引更快,另外不需要Entry对象),但是在队列扩容下LinkedList更灵活,因为不需要复制原始的队列,某些情况下可能更高效。

同样需要注意的上述两个实现都不是线程安全的,因此只适合在单线程环境下使用,下面章节要介绍的LinkedBlockingDeque就是线程安全的可阻塞的Deque。事实上也应该是功能最强大的Queue实现,当然了实现起来也许会复杂一点。


二、 双向并发阻塞队列  LinkedBlockingDeque


1、LinkedBlockingDeque数据结构


双向并发阻塞队列。所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条件时挂起线程,这里说的队列是指支持FIFO/FILO实现的链表。

 

首先看下LinkedBlockingDeque的数据结构。通常情况下从数据结构上就能看出这种实现的优缺点,这样就知道如何更好的使用工具了。


从数据结构和功能需求上可以得到以下结论:

  1. 要想支持阻塞功能,队列的容量一定是固定的,否则无法在入队的时候挂起线程。也就是capacity是final类型的。
  2. 既然是双向链表,每一个结点就需要前后两个引用,这样才能将所有元素串联起来,支持双向遍历。也即需要prev/next两个引用。
  3. 双向链表需要头尾同时操作,所以需要first/last两个节点,当然可以参考LinkedList那样采用一个节点的双向来完成,那样实现起来就稍微麻烦点。
  4. 既然要支持阻塞功能,就需要锁和条件变量来挂起线程。这里使用一个锁两个条件变量来完成此功能。

2、 LinkedBlockingDeque源码分析


public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>,  java.io.Serializable {/** 包含前驱和后继节点的双向链式结构 */static final class Node<E> {E item;Node<E> prev;Node<E> next;Node(E x, Node<E> p, Node<E> n) {item = x;prev = p;next = n;}}/** 头节点 */private transient Node<E> first;/** 尾节点 */private transient Node<E> last;/** 元素个数*/private transient int count;/** 队列容量 */private final int capacity;/** 锁 */private final ReentrantLock lock = new ReentrantLock();/** notEmpty条件 */private final Condition notEmpty = lock.newCondition();/** notFull条件 */private final Condition notFull = lock.newCondition();/** 构造方法 */public LinkedBlockingDeque() {this(Integer.MAX_VALUE);}public LinkedBlockingDeque(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;}public LinkedBlockingDeque(Collection<? extends E> c) {this(Integer.MAX_VALUE);for (E e : c)add(e);}/*** 添加元素作为新的头节点*/private boolean linkFirst(E e) {if (count >= capacity)return false;++count;Node<E> f = first;Node<E> x = new Node<E>(e, null, f);first = x;if (last == null)last = x;elsef.prev = x;notEmpty.signal();return true;}/*** 添加尾元素*/private boolean linkLast(E e) {if (count >= capacity)return false;++count;Node<E> l = last;Node<E> x = new Node<E>(e, l, null);last = x;if (first == null)first = x;elsel.next = x;notEmpty.signal();return true;}/*** 返回并移除头节点*/private E unlinkFirst() {Node<E> f = first;if (f == null)return null;Node<E> n = f.next;first = n;if (n == null)last = null;elsen.prev = null;--count;notFull.signal();return f.item;}/*** 返回并移除尾节点*/private E unlinkLast() {Node<E> l = last;if (l == null)return null;Node<E> p = l.prev;last = p;if (p == null)first = null;elsep.next = null;--count;notFull.signal();return l.item;}/*** 移除节点x*/private void unlink(Node<E> x) {Node<E> p = x.prev;Node<E> n = x.next;if (p == null) {//x是头的情况if (n == null)first = last = null;else {n.prev = null;first = n;}} else if (n == null) {//x是尾的情况p.next = null;last = p;} else {//x是中间的情况p.next = n;n.prev = p;}--count;notFull.signalAll();}//--------------------------------- BlockingDeque 双端阻塞队列方法实现public void addFirst(E e) {if (!offerFirst(e))throw new IllegalStateException("Deque full");}public void addLast(E e) {if (!offerLast(e))throw new IllegalStateException("Deque full");}public boolean offerFirst(E e) {if (e == null) throw new NullPointerException();lock.lock();try {return linkFirst(e);} finally {lock.unlock();}}public boolean offerLast(E e) {if (e == null) throw new NullPointerException();lock.lock();try {return linkLast(e);} finally {lock.unlock();}}public void putFirst(E e) throws InterruptedException {if (e == null) throw new NullPointerException();lock.lock();try {while (!linkFirst(e))notFull.await();} finally {lock.unlock();}}public void putLast(E e) throws InterruptedException {if (e == null) throw new NullPointerException();lock.lock();try {while (!linkLast(e))notFull.await();} finally {lock.unlock();}}public boolean offerFirst(E e, long timeout, TimeUnit unit)throws InterruptedException {if (e == null) throw new NullPointerException();long nanos = unit.toNanos(timeout);lock.lockInterruptibly();try {for (;;) {if (linkFirst(e))return true;if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}} finally {lock.unlock();}}public boolean offerLast(E e, long timeout, TimeUnit unit)throws InterruptedException {if (e == null) throw new NullPointerException();long nanos = unit.toNanos(timeout);lock.lockInterruptibly();try {for (;;) {if (linkLast(e))return true;if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}} finally {lock.unlock();}}public E removeFirst() {E x = pollFirst();if (x == null) throw new NoSuchElementException();return x;}public E removeLast() {E x = pollLast();if (x == null) throw new NoSuchElementException();return x;}public E pollFirst() {lock.lock();try {return unlinkFirst();} finally {lock.unlock();}}public E pollLast() {lock.lock();try {return unlinkLast();} finally {lock.unlock();}}public E takeFirst() throws InterruptedException {lock.lock();try {E x;while ( (x = unlinkFirst()) == null)notEmpty.await();return x;} finally {lock.unlock();}}public E takeLast() throws InterruptedException {lock.lock();try {E x;while ( (x = unlinkLast()) == null)notEmpty.await();return x;} finally {lock.unlock();}}public E pollFirst(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);lock.lockInterruptibly();try {for (;;) {E x = unlinkFirst();if (x != null)return x;if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}} finally {lock.unlock();}}public E pollLast(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);lock.lockInterruptibly();try {for (;;) {E x = unlinkLast();if (x != null)return x;if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}} finally {lock.unlock();}}public E getFirst() {E x = peekFirst();if (x == null) throw new NoSuchElementException();return x;}public E getLast() {E x = peekLast();if (x == null) throw new NoSuchElementException();return x;}public E peekFirst() {lock.lock();try {return (first == null) ? null : first.item;} finally {lock.unlock();}}public E peekLast() {lock.lock();try {return (last == null) ? null : last.item;} finally {lock.unlock();}}public boolean removeFirstOccurrence(Object o) {if (o == null) return false;lock.lock();try {for (Node<E> p = first; p != null; p = p.next) {if (o.equals(p.item)) {unlink(p);return true;}}return false;} finally {lock.unlock();}}public boolean removeLastOccurrence(Object o) {if (o == null) return false;lock.lock();try {for (Node<E> p = last; p != null; p = p.prev) {if (o.equals(p.item)) {unlink(p);return true;}}return false;} finally {lock.unlock();}}//---------------------------------- BlockingQueue阻塞队列 方法实现public boolean add(E e) {addLast(e);return true;}public boolean offer(E e) {return offerLast(e);}public void put(E e) throws InterruptedException {putLast(e);}public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {return offerLast(e, timeout, unit);}public E remove() {return removeFirst();}public E poll() {return pollFirst();}public E take() throws InterruptedException {return takeFirst();}public E poll(long timeout, TimeUnit unit) throws InterruptedException {return pollFirst(timeout, unit);}public E element() {return getFirst();}public E peek() {return peekFirst();}//------------------------------------------- Stack 方法实现public void push(E e) {addFirst(e);}public E pop() {return removeFirst();}//------------------------------------------- Collection 方法实现public boolean remove(Object o) {return removeFirstOccurrence(o);}public int size() {lock.lock();try {return count;} finally {lock.unlock();}}public boolean contains(Object o) {if (o == null) return false;lock.lock();try {for (Node<E> p = first; p != null; p = p.next)if (o.equals(p.item))return true;return false;} finally {lock.unlock();}}boolean removeNode(Node<E> e) {lock.lock();try {for (Node<E> p = first; p != null; p = p.next) {if (p == e) {unlink(p);return true;}}return false;} finally {lock.unlock();}}……
}


3、 LinkedBlockingDeque的优缺点


有了上面的结论再来研究LinkedBlockingDeque的优缺点。

优点当然是功能足够强大,同时由于采用一个独占锁,因此实现起来也比较简单。所有对队列的操作都加锁就可以完成。同时独占锁也能够很好的支持双向阻塞的特性。

凡事有利必有弊。缺点就是由于独占锁,所以不能同时进行两个操作,这样性能上就大打折扣。从性能的角度讲LinkedBlockingDeque要比LinkedBlockingQueue要低很多,比CocurrentLinkedQueue就低更多了,这在高并发情况下就比较明显了。

前面分析足够多的Queue实现后,LinkedBlockingDeque的原理和实现就不值得一提了,无非是在独占锁下对一个链表的普通操作。


4、LinkedBlockingDeque的序列化、反序列化


有趣的是此类支持序列化,但是Node并不支持序列化,因此fist/last就不能序列化,那么如何完成序列化/反序列化过程呢?

清单4 LinkedBlockingDeque的序列化、反序列化

private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {lock.lock();try {// Write out capacity and any hidden stuffs.defaultWriteObject();// Write out all elements in the proper order.for (Node<E> p = first; p != null; p = p.next)s.writeObject(p.item);// Use trailing null as sentinels.writeObject(null);} finally {lock.unlock();}
}private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();count = 0;first = null;last = null;// Read in all elements and place in queuefor (;;) {E item = (E)s.readObject();if (item == null)break;add(item);}
}

清单4 描述的是LinkedBlockingDeque序列化/反序列化的过程。序列化时将真正的元素写入输出流,最后还写入了一个null。读取的时候将所有对象列表读出来,如果读取到一个null就表示结束。这就是为什么写入的时候写入一个null的原因,因为没有将count写入流,所以就靠null来表示结束,省一个整数空间。


参考内容来源:

集合框架 Queue篇(1)---ArrayDeque 
http://hi.baidu.com/yao1111yao/item/1a1346f65a50d9c8521c266d 
集合框架 Queue篇(7)---LinkedBlockingDeque 
http://hi.baidu.com/yao1111yao/item/b1649cff2cf60be91a111f6d 
深入浅出 Java Concurrency (24): 并发容器 part 9 双向队列集合 Deque 
http://www.blogjava.net/xylz/archive/2010/08/12/328587.html 
深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque 
http://www.blogjava.net/xylz/archive/2010/08/18/329227.html 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab边算边出图命令,Matlab:不包含边境和工具栏的figure(移除保存图片的白边)...

Matlab:不包含边界和工具栏的figure(移除保存图片的白边)当我们使用matlab的imshow命令显示图片时&#xff0c;会有白框和工具栏出现。在保存图片时会出现白色的边框。下面将说明如何去除这些显示。Matlab启动时运行脚本script.m&#xff0c;文件位置在~/matlab/ directory。在…

JavaScript实现复选框全选与全不选的效果

//里面涉及到几张图片&#xff0c;有需要的可以联系我要&#xff0c;直接私信我就行&#xff0c;每天在这个点都会上线&#xff0c;看到就回&#xff0c;或者从我的资料里面找我的联系方式&#xff0c;收到之后会发给你们的<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1…

ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

之前两篇文章简析.NET Core 以及与 .NET Framework的关系和.NET Core的构成体系从总体上介绍.NET Core,接下来计划用一个系列对ASP.NET Core的运行原理进行剖析。 ASP.NET Core 是新一代的 ASP.NET&#xff0c;早期称为 ASP.NET vNext&#xff0c;并且在推出初期命名为ASP.NET …

深入并发包-ConcurrentHashMap

转载自 深入并发包-ConcurrentHashMap 前言 以前写过介绍HashMap的文章&#xff0c;文中提到过HashMap在put的时候&#xff0c;插入的元素超过了容量&#xff08;由负载因子决定&#xff09;的范围就会触发扩容操作&#xff0c;就是rehash&#xff0c;这个会重新将原数组的内容…

红帽、微软和 Codenvy 联合推出语言服务器协定(Language Server Protocol,LSP)项目

微软、红帽及容器开发环境供应商Codenvy本周在Red Hat DevNation开放源码大会上宣布将共同发展语言服务器协定&#xff08;Language Server Protocol&#xff0c;LSP&#xff09;项目&#xff0c;让不同的程序编辑器与集成开发环境&#xff08;IDE&#xff09;方便嵌入各种程序…

ConcurrentHashMap总结

转载自 ConcurrentHashMap总结并发编程实践中&#xff0c;ConcurrentHashMap是一个经常被使用的数据结构&#xff0c;相比于Hashtable以及Collections.synchronizedMap()&#xff0c;ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力&#xff0c;但同时降低了对读一…

最全面的常用正则表达式大全

很多不太懂正则的朋友&#xff0c;在遇到需要用正则校验数据时&#xff0c;往往是在网上去找很久&#xff0c;结果找来的还是不很符合要求。所以我最近把开发中常用的一些正则表达式整理了一下&#xff0c;在这里分享一下。给自己留个底&#xff0c;也给朋友们做个参考。 转载至…

php access allow,PHP标头不适用于Access-Control-Allow-Origin

我使用jQuery File Upload plugin by Blueimp将图像上传到服务器.问题是,发送服务器是admin.example.com,存储图像的接收服务器位于www.example.com上.相同的域,不同的子域.XMLHttpRequest cannot load http://www.example.com/upload/. Origin http://admin.example.com is no…

.NET Core系列 : 1、.NET Core 环境搭建和命令行CLI入门

2016年6月27日.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布&#xff0c;社区里涌现了很多文章&#xff0c;我也计划写个系列文章&#xff0c;原因是.NET Core的入门门槛相当高&#xff0c;很有必要写个深入浅出的系列文章&#xff0c;本节内容帮助你入门。我将可能…

java语言中的访问权限控制符有哪些,18.Java的访问控制符

Java的访问控制符一.类的成员的可见性对于类的成员变量和成员方法&#xff0c;我们可以通过设定一定的访问可见性来限定应用范围。(一).privateprivate表示当前类访问权限。如果类里的一个成员(包括成员变量、方法、构造器等)使用private访问控制符来修饰&#xff0c;则这个成员…

ConcurrentHashMap能完全替代HashTable吗?

转载自 ConcurrentHashMap能完全替代HashTable吗&#xff1f;关于ConcurrentHashMap在之前的ConcurrentHashMap原理分析中已经解释了原理&#xff0c;而HashTable其实大抵上只是对HashMap的线程安全的封装&#xff0c;在JDK7与JDK8中HashMap的实现中解释了HashMap的原理。 至此…

购物车的功能——界面源码

里面所用到的图片资源统一都在“我的资源”里面&#xff0c;相对应的图片是“ 购物车源码相关图片 ”http://download.csdn.net/detail/qq_34137397/9665878&#xff0c; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.o…

Asp.Net Core 发布和部署( MacOS + Linux + Nginx )

前言 在上篇文章中&#xff0c;主要介绍了 Dotnet Core Run 命令&#xff0c;这篇文章主要是讲解如何在Linux中&#xff0c;对 Asp.Net Core 的程序进行发布和部署。 有关如何在 Jexus 中进行部署&#xff0c;请参见本人的另一篇文章&#xff1a;http://www.cnblogs.com/savorb…

php渐变字,jQuery_jQuery实现的立体文字渐变效果,先截两个图看看: 效果很 - phpStudy...

jQuery实现的立体文字渐变效果先截两个图看看&#xff1a;效果很不错吧&#xff1f;会不会误以为这些字体是图片&#xff1f;这可不是图片&#xff0c;而是用JS实现的在线演示 http://demo.phpstudy.net/js/gradient-test/demo.htm下面来简单分享下实现过程及原理(网站中使用了…

https 单向认证和双向认证

转载自 https 单向认证和双向认证 一、Http HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff0c;是互联网上使用最广泛的一种协议&#xff0c;所有WWW文件必须遵循的标准。HTTP协议传输的数据都是未加密的&#xff0c;也就是明文的&#xff0c;因此使用HTT…

python中将整数转化为八进制的函数,Python进制转化

Python中的进制有二进制、八进制、十进制、十六进制&#xff0c;用python的内置函数可以方便的进行不同进制之间的转换&#xff0c;二、八、十六进制数字表示前面分别添加0b、0o、0x(前面为零)。二进制八进制十进制十六进制0b1010o1271230x1a转化为十进制int()可以接收一个或者…

Asp.Net Core 发布和部署(Linux + Jexus )

前言 在上篇文章中&#xff0c;主要介绍了 Dotnet Core Run 命令&#xff0c;这篇文章主要是讲解如何在 asp.net core 中对我们的已经完成的程序进行发布和部署。 有关如何使用 Nginx 进行部署&#xff0c;请参见本人的另一篇文章&#xff1a;http://www.cnblogs.com/savorboar…

购物车的功能——CSS源码

里面所用到的图片资源统一都在“我的资源”里面&#xff0c;相对应的图片是“ 购物车源码相关图片 ”http://download.csdn.net/detail/qq_34137397/9665878&#xff0c; 此CSS的对应的是“购物车的功能——界面源码”的内容 charset "gb2312"; /* CSS Document */…

Java NIO系列教程(十 五)Java NIO Path

转载自 Java NIO系列教程&#xff08;十 五&#xff09;Java NIO Path译文链接 译者&#xff1a;章筱虎 Java的Path接口是Java NIO2 的一部分&#xff0c;是对Java6 和Java7的 NIO的更新。Java的Path接口在Java7 中被添加到Java NIO&#xff0c;位于java.nio.file包中&#x…

discuz 版块导航function_forumlist.php,Discuz! X2“扩建”左侧版块导航 让社区层次一目了然...

一般情况下&#xff0c;当社区具有大量栏目和版块的时候&#xff0c;用户往往很容易迷失其中&#xff0c;不清楚自己所在的版块位置&#xff0c;同时也很难找到“目的”版块&#xff0c;容易造成不良的用户体验。Discuz!X2针对此问题在“用户体验”方面做了深度优化。Discuz!X2…