ArrayBlockingQueue是一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。

ArrayBlockingQueue继承自 AbstractQueue并实现 BlockingQueue接口。

ArrayBlockingQueue是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞,试图从空队列中提取元素将导致类似阻塞。

ArrayBlockingQueue支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。 公平性通过创建 ArrayBlockingQueue实例时指定。

1.成员变量

  1. /** 队列数组实现 */
  2. private final E[] items;
  3. /** 已取出元素索引,用于下一个元素的 take, poll or remove */
  4. private int takeIndex;
  5. /** 已插入元素索引,用于下一个元素的 put, offer, or add */
  6. private int putIndex;
  7. /** 队列中项目数 */
  8. private int count;
  9. /** 保护所有访问的主锁 */
  10. private final ReentrantLock lock;
  11. /** Condition 实例,用于等待中 take */
  12. private final Condition notEmpty;
  13. /** Condition 实例,用于等待中 put*/
  14. private final Condition notFull;

之前我们已经学习了锁相关的知识,所以几个成员变量不难理解。

1)其中E[] items;是数组式的队列实现;

2)takeIndex 用于记录 take操作的次数;

3)putIndex 用于记录 put操作的次数;

4)count 用于记录队列中元素数目;

5)ReentrantLock lock 是用于控制访问的主锁;

6)Condition notEmpty 获取操作时的条;

7)Condition notFull 插入操作时的条件;

2.构造方法

ArrayBlockingQueue的构造方法有3个。

1)最简单的构造方法:

  1. //指定队列大小
  2. public ArrayBlockingQueue(int capacity) {
  3. this(capacity, false);
  4. }

此种构造方法最为简单也最为常用,在创建 ArrayBlockingQueue实例时只需指定其大小即可。

  1. BlockingQueue<Object> q = new ArrayBlockingQueue<Object>(10);

2)增加访问策略的构造方法,除了指定队列大小外还可指定队列的访问策略:

  1. //指定队列大小、访问策略
  2. public ArrayBlockingQueue(int capacity, boolean fair) {
  3. if (capacity <= 0)
  4. throw new IllegalArgumentException();
  5. this.items = (E[]) new Object[capacity];
  6. lock = new ReentrantLock(fair);
  7. notEmpty = lock.newCondition();
  8. notFull = lock.newCondition();
  9. }

fair如果为 true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列;如果为 false,则访问顺序是不确定的。

构造方法中首先初始化了 items数组,然后根据fair创建 ReentrantLock实例,最后返回 notEmpty与 notFull两个 Condition实例,分别用于等待中的获取与添加操作。

3)带初始元素的构造方法:

  1. //指定队列大小、访问策略、初始元素
  2. public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
  3. this(capacity, fair);
  4. if (capacity < c.size())
  5. throw new IllegalArgumentException();
  6. for (Iterator<? extends E> it = c.iterator(); it.hasNext();)
  7. add(it.next());
  8. }

除了可以指定容量和访问策略外,还可以包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。

代码也可以观察到,在实例化队列之后还使用了add方法将 collection 中的元素按原有顺序添加到实例中。

        3.添加元素

1)add方法

ArrayBlockingQueue的add方法调用的是父类方法,而父类 add方法则调用的是 offer方法,以下是add方法的源代码:

  1. public boolean add(E e) {
  2. //调用父类add方法
  3. return super.add(e);
  4. }

2)offer方法

offer方法将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。此方法通常要优于 add(E) 方法,后者可能无法插入元素,而只是抛出一个异常。

  1. /**
  2. * 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),
  3. * 在成功时返回 true,如果此队列已满,则返回 false。
  4. * 此方法通常要优于 add(E) 方法,后者可能无法插入元素,而只是抛出一个异常。
  5. */
  6. public boolean offer(E e) {
  7. //判断e是否为null
  8. if (e == null)
  9. throw new NullPointerException();
  10. final ReentrantLock lock = this.lock;
  11. //获取锁
  12. lock.lock();
  13. try {
  14. //判断队列是否已满
  15. if (count == items.length)
  16. return false;
  17. else {
  18. //如果未满则插入
  19. insert(e);
  20. return true;
  21. }
  22. } finally {
  23. //释放锁
  24. lock.unlock();
  25. }
  26. }

因为 add方法在队列已满时会抛出异常,所以 offer方法一般优于 add方法使用。

首先,判断要添加的元素是否为 null,如果为null则抛出空指针异常。

接着,创建一个 ReentrantLock实例,ReentrantLock是可重入锁实现。更详细介绍参考http://286.iteye.com/blog/2296191

然后,获取锁。

最后,判断队列是否已满,如果已满则返回 false;如果未满则调用insert方法插入元素,返回true。

所有操作完成后释放锁。

offer方法的处理流程可以参照以下流程图:


        从代码中就可以看到,offer方法利用了ReentrantLock来实现队列阻塞的功能,所以多线程操作相同队列时会排队等待。

offer的另一个重载方法是 offer(E e, long timeout, TimeUnit unit),此重载方法将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。其源代码为:

  1. /**
  2. * 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
  3. */
  4. public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
  5. if (e == null)
  6. throw new NullPointerException();
  7. long nanos = unit.toNanos(timeout);
  8. final ReentrantLock lock = this.lock;
  9. lock.lockInterruptibly();
  10. try {
  11. for (;;) {
  12. if (count != items.length) {
  13. insert(e);
  14. return true;
  15. }
  16. if (nanos <= 0)
  17. return false;
  18. try {
  19. nanos = notFull.awaitNanos(nanos);
  20. } catch (InterruptedException ie) {
  21. notFull.signal(); // propagate to non-interrupted thread
  22. throw ie;
  23. }
  24. }
  25. } finally {
  26. lock.unlock();
  27. }
  28. }

与普通offer方法不同之处在于:offer(E e, long timeout, TimeUnit unit)方法利用循环在指定时间内不断去尝试添加元素,如果成功则返回true,如果指定时间已到则退出返回 false。

3)insert方法

insert方法在当前位置(putIndex)插入元素。

  1. /**
  2. * 在当前位置(putIndex)插入元素(在获得锁的情况下调用)
  3. */
  4. private void insert(E x) {
  5. //设置 putIndex位置 items数组元素为x
  6. items[putIndex] = x;
  7. //返回putIndex新值,如果已满则返回0,未满则+1
  8. putIndex = inc(putIndex);
  9. //增加元素数量
  10. ++count;
  11. //唤醒获取线程
  12. notEmpty.signal();
  13. }

因为 ArrayBlockingQueue内部队列实现为数组items,而 putIndex则记录了队列中已添加元素的位置,所以新添加的元素就直接被添加到数组的指定位置。随后修改 putIndex值,如果队列未满则+1,如果已满则从0重新开始。最后唤醒 notEmpty中的一个线程。

4)put方法

将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

以下是put方法的源代码:

  1. /**
  2. * 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间
  3. */
  4. public void put(E e) throws InterruptedException {
  5. //判断e是否为null
  6. if (e == null)
  7. throw new NullPointerException();
  8. final E[] items = this.items;
  9. final ReentrantLock lock = this.lock;
  10. //获取中断锁
  11. lock.lockInterruptibly();
  12. try {
  13. try {
  14. //利用循环判断队列是否已满
  15. while (count == items.length)
  16. //如果已满则调用await方法阻塞等待
  17. notFull.await();
  18. } catch (InterruptedException ie) {
  19. notFull.signal(); // propagate to non-interrupted thread
  20. throw ie;
  21. }
  22. //队列未满则插入
  23. insert(e);
  24. } finally {
  25. //释放锁
  26. lock.unlock();
  27. }
  28. }

put方法与其他方法类似,其中会循环判断队列是否已满,如果已满则阻塞 notFull,如果未满则调用 insert方法添加元素。因为其中运用了循环判断队列是否有位置添加新元素,如果队列已满则产生阻塞等待,直至可以添加元素为止。

将本文开始时的例子修改一下,去掉消费者,只留下生产者,这样当队列满了之后没有消费者去消费产品,生产者就不会再向队列中插入了:

  1. class Producer implements Runnable {
  2. private final ArrayBlockingQueue<Integer> queue;
  3. private int i;
  4. Producer(ArrayBlockingQueue<Integer> q) {
  5. queue = q;
  6. }
  7. public void run() {
  8. try {
  9. while (true) {
  10. int p=produce();
  11. queue.put(p);// 将产品放入缓冲队列
  12. System.out.println("插入成功:"+p);
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. int produce() {
  19. return i++;// 生产产品
  20. }
  21. }
  22. public class Runner {
  23. public static void main(String[] args) {
  24. ArrayBlockingQueue<Integer> q = new ArrayBlockingQueue<Integer>(10);
  25. Producer p = new Producer(q);
  26. new Thread(p).start();
  27. }
  28. }
  29. //结果:
  30. 插入成功:0
  31. 插入成功:1
  32. 插入成功:2
  33. 插入成功:3
  34. 插入成功:4
  35. 插入成功:5
  36. 插入成功:6
  37. 插入成功:7
  38. 插入成功:8
  39. 插入成功:9

此时程序并不会退出,而是阻塞在那里等待队列有位置插入。

4.获取元素

1)peek方法

获取但不移除此队列的头;如果此队列为空,则返回 null。以下是peek方法的源代码:

  1. /**
  2. * 获取但不移除此队列的头;如果此队列为空,则返回 null
  3. */
  4. public E peek() {
  5. final ReentrantLock lock = this.lock;
  6. //获取锁
  7. lock.lock();
  8. try {
  9. //判断队列中是否有元素,如果没有则返回null,如果存在则返回该元素
  10. return (count == 0) ? null : items[takeIndex];
  11. } finally {
  12. //释放锁
  13. lock.unlock();
  14. }
  15. }

peek代码比较简单,首先判断队列是否有元素,即count==0,如果为空则返回null,非空则返回相应元素。

2)poll方法

获取并移除此队列的头,如果此队列为空,则返回 null。以下是poll方法的源代码:

  1. /**
  2. * 获取并移除此队列的头,如果此队列为空,则返回 null
  3. */
  4. public E poll() {
  5. final ReentrantLock lock = this.lock;
  6. //获取锁
  7. lock.lock();
  8. try {
  9. //判断是否存在元素
  10. if (count == 0)
  11. return null;
  12. //调用extract方法返回元素
  13. E x = extract();
  14. return x;
  15. } finally {
  16. //释放锁
  17. lock.unlock();
  18. }
  19. }

poll方法调用的是 extract()方法来获取头元素。

3)extract方法

以下是extract()方法的源代码:

  1. /**
  2. * 从 takeIndex位置获取元素(在获得锁的情况下调用)
  3. */
  4. private E extract() {
  5. final E[] items = this.items;
  6. //获取元素
  7. E x = items[takeIndex];
  8. //移除原位置元素
  9. items[takeIndex] = null;
  10. //计算 takeIndex新值
  11. takeIndex = inc(takeIndex);
  12. --count;
  13. //唤醒添加线程
  14. notFull.signal();
  15. return x;
  16. }

4)take方法

获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

  1. /**
  2. * 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)
  3. */
  4. public E take() throws InterruptedException {
  5. final ReentrantLock lock = this.lock;
  6. // 获取中断锁
  7. lock.lockInterruptibly();
  8. try {
  9. try {
  10. // 如果队列未空则阻塞等待,直到有元素为止
  11. while (count == 0)
  12. notEmpty.await();
  13. } catch (InterruptedException ie) {
  14. notEmpty.signal(); // 唤醒获取线程
  15. throw ie;
  16. }
  17. // 调用extract方法返回元素
  18. E x = extract();
  19. return x;
  20. } finally {
  21. // 释放锁
  22. lock.unlock();
  23. }
  24. }

与put方法类似,take方法也是利用循环阻塞的方式来获取元素,如果没有元素则等待,直至获取元素为止。

与put方法的例子类似,生产者只生产5个产品,消费完这5个产品后,消费者就不得不等待队列有元素可取:

  1. class Producer implements Runnable {
  2. private final ArrayBlockingQueue<Integer> queue;
  3. private int i;
  4. Producer(ArrayBlockingQueue<Integer> q) {
  5. queue = q;
  6. }
  7. public void run() {
  8. try {
  9. for (int i = 0; i < 5; i++) {
  10. int p = produce();
  11. queue.put(p);// 将产品放入缓冲队列
  12. System.out.println("插入成功:" + p);
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. int produce() {
  19. return i++;// 生产产品
  20. }
  21. }
  22. class Consumer implements Runnable {
  23. private final ArrayBlockingQueue<Integer> queue;
  24. Consumer(ArrayBlockingQueue<Integer> q) {
  25. queue = q;
  26. }
  27. public void run() {
  28. try {
  29. while (true) {
  30. int p = queue.take();
  31. System.out.println("获取成功:" + p);
  32. }
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. void consume(Object x) {
  38. System.out.println("消费:" + x);// 消费产品
  39. }
  40. }
  41. public class Runner {
  42. public static void main(String[] args) {
  43. ArrayBlockingQueue<Integer> q = new ArrayBlockingQueue<Integer>(10);// 或其他实现
  44. Producer p = new Producer(q);
  45. Consumer c1 = new Consumer(q);
  46. Consumer c2 = new Consumer(q);
  47. new Thread(p).start();
  48. new Thread(c1).start();
  49. new Thread(c2).start();
  50. }
  51. }
  52. //结果:
  53. 插入成功:0
  54. 插入成功:1
  55. 插入成功:2
  56. 插入成功:3
  57. 插入成功:4
  58. 获取成功:0
  59. 获取成功:1
  60. 获取成功:2
  61. 获取成功:3
  62. 获取成功:4

之后程序也是会阻塞在那里。

5.移除元素

1)remove方法

remove方法从此队列中移除指定元素的单个实例(如果存在)。更确切地讲,如果此队列包含一个或多个满足 o.equals(e) 的元素 e,则移除该元素。如果此队列包含指定的元素(或者此队列由于调用而发生更改),则返回 true。

  1. /**
  2. * 从此队列中移除指定元素的单个实例(如果存在)
  3. */
  4. public boolean remove(Object o) {
  5. //判断要移除元素是否为空
  6. if (o == null)
  7. return false;
  8. final E[] items = this.items;
  9. final ReentrantLock lock = this.lock;
  10. //获取锁
  11. lock.lock();
  12. try {
  13. int i = takeIndex;
  14. int k = 0;
  15. for (;;) {
  16. //判断队列是否含有元素
  17. if (k++ >= count)
  18. return false;
  19. //比较
  20. if (o.equals(items[i])) {
  21. //移除
  22. removeAt(i);
  23. return true;
  24. }
  25. //返回i新值,以便下次循环使用
  26. i = inc(i);
  27. }
  28. } finally {
  29. //释放锁
  30. lock.unlock();
  31. }
  32. }

remove方法其中利用循环来不断判断该元素的位置,如果找到则调用 removeAt方法移除指定位置的数组元素。

以下是一个移除的小例子:

  1. ArrayBlockingQueue<Integer> q = new ArrayBlockingQueue<Integer>(10);
  2. // 添加10个元素
  3. for (int i = 0; i < 10; i++) {
  4. q.add(i);
  5. }
  6. // 移除值为 1,3,5,7,9 的这五个元素
  7. q.remove(1);
  8. q.remove(3);
  9. q.remove(5);
  10. q.remove(7);
  11. q.remove(9);
  12. //又移除了一次 9
  13. q.remove(9);
  14. for (Integer i : q) {
  15. System.out.println(i);
  16. }
  17. //结果:
  18. 0
  19. 2
  20. 4
  21. 6
  22. 8

从结果可以看出,从队列中正确的移除了我们指定的元素,在最后即使指定已经不存在的元素值,remove方法也之后返回false。

2)drainTo方法

drainTo方法用于移除此队列中所有可用的元素,并将它们添加到给定 collection 中。此操作可能比反复轮询此队列更有效。在试图向 collection c 中添加元素没有成功时,可能导致在抛出相关异常时,元素会同时在两个 collection 中出现,或者在其中一个 collection 中出现,也可能在两个 collection 中都不出现。如果试图将一个队列放入自身队列中,则会导致 IllegalArgumentException 异常。此外,如果正在进行此操作时修改指定的 collection,则此操作行为是不确定的。

以下是 drainTo方法的源代码:

  1. public int drainTo(Collection<? super E> c) {
  2. //如果指定 collection为 null 抛出异常
  3. if (c == null)
  4. throw new NullPointerException();
  5. //如果指定 collection 是此队列,或者此队列元素的某些属性不允许将其添加到指定 collection 抛出异常
  6. if (c == this)
  7. throw new IllegalArgumentException();
  8. final E[] items = this.items;
  9. final ReentrantLock lock = this.lock;
  10. //获取锁
  11. lock.lock();
  12. try {
  13. //take操作的位置
  14. int i = takeIndex;
  15. int n = 0;
  16. //元素数量
  17. int max = count;
  18. //利用循环不断取出元素添加到c中
  19. while (n < max) {
  20. c.add(items[i]);
  21. items[i] = null;
  22. i = inc(i);
  23. ++n;
  24. }
  25. //添加完成后初始化必要值,唤醒添加线程
  26. if (n > 0) {
  27. count = 0;
  28. putIndex = 0;
  29. takeIndex = 0;
  30. notFull.signalAll();
  31. }
  32. //返回添加元素数量
  33. return n;
  34. } finally {
  35. //释放锁
  36. lock.unlock();
  37. }
  38. }

代码中并没有添加失败的相关处理,所以结果如上所说并不一定完整。以下是相关实例:

  1. ArrayBlockingQueue<Integer> q = new ArrayBlockingQueue<Integer>(10);
  2. List<Integer> list = new ArrayList<Integer>();
  3. // 添加10个元素
  4. for (int i = 0; i < 10; i++) {
  5. q.add(i);
  6. list.add(i + 10);
  7. }
  8. //将q中的元素添加到 list中
  9. q.drainTo(list);
  10. for (Integer i : list) {
  11. System.out.println(i);
  12. }
  13. //结果:
  14. 10
  15. 11
  16. 12
  17. 13
  18. 14
  19. 15
  20. 16
  21. 17
  22. 18
  23. 19
  24. 0
  25. 1
  26. 2
  27. 3
  28. 4
  29. 5
  30. 6
  31. 7
  32. 8
  33. 9

需要值得注意的是 drainTo方法是将队列中的元素按顺序添加到指定 Collection中,别弄反了。

drainTo(Collection<? super E> c, int maxElements)用法类似,只不过指定了移除元素数。

3)clear方法

移除此队列中的所有元素。在此调用返回之后,队列将为空。

  1. /**
  2. * 移除此队列中的所有元素。在此调用返回之后,队列将为空
  3. */
  4. public void clear() {
  5. final E[] items = this.items;
  6. final ReentrantLock lock = this.lock;
  7. //获取锁
  8. lock.lock();
  9. try {
  10. int i = takeIndex;
  11. int k = count;
  12. //将数组元素置为null
  13. while (k-- > 0) {
  14. items[i] = null;
  15. i = inc(i);
  16. }
  17. //初始化其他参数
  18. count = 0;
  19. putIndex = 0;
  20. takeIndex = 0;
  21. //唤醒添加线程
  22. notFull.signalAll();
  23. } finally {
  24. //释放锁
  25. lock.unlock();
  26. }
  27. }

clear方法比较简单,就是利用循环清除数组中的元素,然后将相关参数置为初始值。

ArrayBlockingQueue还有一些其他方法,这些方法相对简单这里就不细说了。

Java并发之ArrayBlockingQueue的更多相关文章

  1. 深入理解Java并发之synchronized实现原理

    深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...

  2. JAVA并发之阻塞队列浅析

    背景 因为在工作中经常会用到阻塞队列,有的时候还要根据业务场景获取重写阻塞队列中的方法,所以学习一下阻塞队列的实现原理还是很有必要的.(PS:不深入了解的话,很容易使用出错,造成没有技术深度的样子) ...

  3. java并发之Future与Callable使用

    java并发之Future与Callable使用 这篇文章需要大家知道线程.线程池的知识,尤其是线程池. 有的时候我们要获取线程的执行结果,这个时候就需要用到Callable.Future.Futur ...

  4. java并发之固定对象与实例

    java并发之固定对象与实例 Immutable Objects An object is considered immutable if its state cannot change after ...

  5. Java并发之BlockingQueue的使用

    Java并发之BlockingQueue的使用 一.简介 前段时间看到有些朋友在网上发了一道面试题,题目的大意就是:有两个线程A,B,  A线程每200ms就生成一个[0,100]之间的随机数, B线 ...

  6. Java并发之Semaphore的使用

    Java并发之Semaphore的使用 一.简介 今天突然发现,看着自己喜欢的球队发挥如此的棒,然后写着博客,这种感觉很爽.现在是半场时间,就趁着这个时间的空隙,说说Java并发包中另外一个重量级的类 ...

  7. Java并发之CyclicBarria的使用(二)

    Java并发之CyclicBarria的使用(二) 一.简介 之前借助于其他大神写过一篇关于CyclicBarria用法的博文,但是内心总是感觉丝丝的愧疚,因为笔者喜欢原创,而不喜欢去转载一些其他的文 ...

  8. Java并发之CyclicBarria的使用

    Java并发之CyclicBarria的使用 一.简介 笔者在写CountDownLatch这个类的时候,看到了博客园上的<浅析Java中CountDownLatch用法>这篇博文,为博主 ...

  9. Java并发之CountDownLatch的使用

    Java并发之CountDownLatch的使用 一. 简介 Java的并发包早在JDK5这个版本中就已经推出,而且Java的并发编程是几乎每个Java程序员都无法绕开的屏障.笔者今晚在家闲来无事,翻 ...

随机推荐

  1. vi 替换操作

    举例一: ,$s/// 从第一行到最后一行 147都替换为150 举例二: 例:替换当前文件中所有old为new :%s/old/new/g #%表示替换说有行,g表示替换一行中所有匹配点. 举例三: ...

  2. Odoo 11 Backend

    Table of Contents 命令入口 服务器 启动server thread 模式 prefork 模式 gevent模式 wsgi 应用 响应 客户端请求 xmlrpc web http路由 ...

  3. 谁动了我的cpu——oprofile使用札记(转)

    引言 cpu无端占用高?应用程序响应慢?苦于没有分析的工具? oprofile利用cpu硬件层面提供的性能计数器(performance counter),通过计数采样,帮助我们从进程.函数.代码层面 ...

  4. UVA - 434 Matty&#39;s Blocks

    题意:给你正视和側视图,求最多多少个,最少多少个 思路:贪心的思想.求最少的时候:由于能够想象着移动,尽量让两个视图的重叠.所以我们统计每一个视图不同高度的个数.然后计算.至于的话.就是每次拿正视图的 ...

  5. MySQL性能优化的最佳20+条经验(转)

    今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序 员需要去关注的事情.当我们去设计数据库表结构,对操作数 ...

  6. MVC基础操作

    C#-MVC基础操作-数据的展示及增删改.登录页面及状态保持一.数据展示1.View代码: <%@Page Language="C#" Inherits="Syst ...

  7. PHP面试题及答案解析(4)—PHP核心技术

    1.写出一个能创建多级目录的PHP函数. <?php /** * 创建多级目录 * @param $path string 要创建的目录 * @param $mode int 创建目录的模式,在 ...

  8. SQLServer 2017安装时的错误:Polybase要求安装Oracle JRE 7更新51或更高版本

    2016应该也有这个问题 下载JDK7就可以了(我装10是不可以解决的) 重新运行下 安装完后再安装SSMS 附: MS SQL SERVER 2017全套下载地址(含JDK7.SSMS.KEY): ...

  9. Ubuntu 16.04 安装opencv的各种方法(含opencv contrib扩展包安装方法)

    Ubuntu 16.04 安装opencv的各种方法(含opencv contrib扩展包安装方法) https://blog.csdn.net/ksws0292756/article/details ...

  10. vim与windows/linux之间的复制粘贴小结

    vim与windows/linux之间的复制粘贴小结 用 vim这么久了,始终也不知道怎么在vim中使用系统粘贴板,通常要在网上复制一段代码都是先gedit打开文件,中键粘贴后关闭,然后再用vim打开 ...