前记:

    这个得首先声明一下,以下大部分内容均参考于:https://blog.csdn.net/wx_vampire/article/details/79585794,本随笔只作为学习作用,侵权删!

  说一下我看的学习心得吧!对于BlockingQueue这个接口以及常用的实现类的用法,真的是不看不知道,一看吓一跳!有点超出了我的现有水平的理解范畴了!主要是里面的一些对Java基础中一些不常用的方法,修饰符的使用,这个对我来说真的算是涨姿势了。

还有就是,一些链表在处理数据的算法,这些也让我有点头大,学习的过程中处于“半懂”状态,这让我很受打击,不过好处就是让我知道接下来要往哪方面学习了。

  好了,开始正文吧,以下算是一个网上转摘学习资料备份了。

正文:

ArrayBlockingQueue 是数组结构的堵塞队列的一种实现,那么肯定要实现的BlockingQueue接口。

解释一下接口含义

  • boolean add(E e); 队列添加元素,返回成功标识,队列满了抛出队列满的异常,无堵塞。
  • boolean offer(E e);队列添加元素,返回成功标识,无堵塞。
  • void put(E e);队列添加元素,无返回值,队列满了会堵塞。
  • boolean offer(E e, long timeout, TimeUnit unit);队列添加元素,队列满了堵塞,设置有超时时间。
  • E poll();队列拉取元素,无堵塞,没有值返回null。
  • E take();队列拉取元素,队列空了会堵塞,等待能拉取到值为止
  • E poll(long timeout, TimeUnit unit);队列拉取元素,队列空了等待,设置有等待超时时间
  • E peek() ; 只读队首元素的值,没有返回空
  • int remainingCapacity(); 计算剩余容量
  • boolean remove(Object o); 移除元素
  • int drainTo(Collection<? super E> c, int maxElements);  移除元素放到入参的集合当中
  • public Iterator<E> iterator() jdk 1.8以后ArrayBlockingQueue还增加了迭代器功能,这个模块下面会重点介绍,很有意思。

堵塞队列提供的功能:

  1. 在多线程环境下提供一个类似于生产者和消费者这样的一个模型
  2. 提供一个FIFO的顺序读取和插入

那就引起我的思考:

  1. 怎么实现的堵塞机制和堵塞的超时机制?
  2. 作为一个集合类,数组结构的怎么在多线程环境下实现安全扩容?
  3. 1.8jdk版本为什么会增加迭代器功能?

下面的代码说明:部分是我自己按照自己的立即翻译,当然多数是参考原作者的注释。

  1 package java.util.concurrent;    //所在包
  2 import java.util.concurrent.locks.Condition;
  3 import java.util.concurrent.locks.ReentrantLock;//重入锁
  4 import java.util.AbstractQueue;//抽象队列
  5 import java.util.Collection;//集合
  6 import java.util.Iterator;//迭代器
  7 import java.util.NoSuchElementException;//异常
  8 import java.lang.ref.WeakReference;//弱引用
  9 import java.util.Spliterators;//分割迭代
 10 import java.util.Spliterator;//分割迭代
 11 class
 12 public class ArrayBlockingQueue<E>
 13         extends AbstractQueue<E>    //继承了抽象队列
 14         implements BlockingQueue<E>, //实现了BlockingQueue接口
 15                    java.io.Serializable//实现了Serializable接口,说明此类可以被序列化
 16 {
 17     //序列化ID
 18     private static final long serialVersionUID = -817911632652898426L;
 19
 20     /** 阻塞队列中存放的对象 */
 21     default final Object[] items;
 22
 23     /** 消费者获取对象的下一个对象下标,具体的操作有poll take peek remove */
 24     default int takeIndex
 25
 26     /** 生产者放入对象的下一个对象的下标,具体的操作有 put offer add */
 27     default int putIndex;
 28
 29     /** 队列中元素的数量 */
 30     default int count;
 31
 32      /** 这个锁就是实现生产者,消费者模型的锁模型,并且所有和并发相关的堵塞控制都是通过这个锁来实现的*/
 33     default final ReentrantLock lock;
 34
 35     /** 这个是有ReentrantLock 中的Condition一个标识队列中有元素非空标志,用于通知消费者队列中有数据了,快来取数据 */
 36     private final Condition notEmpty;
 37
 38    /** 这个也是ReentrantLock 中的Condition的一个标识,标识队列中的元素不满用于通知生产者队列中空地,快来塞数据*/
 39     private final Condition notFull;
 40
 41     /**
 42      * 这是一个迭代器集合,是之前没有的特性,
 43      * 细节:transient 标示变量是序列化忽略这个变量。
 44      */
 45     default transient Itrs itrs = null;
 46 /***********************【阻塞队列常用的方法】*************************************************************************************/
 47     /**
 48      *
 49      *堵塞提交,超时返回false
 50      */
 51     public boolean offer(E e, long timeout, TimeUnit unit)
 52             throws InterruptedException {
 53         checkNotNull(e);
 54         long nanos = unit.toNanos(timeout);
 55         final ReentrantLock lock = this.lock;
 56     //获取锁
 57         lock.lockInterruptibly();
 58         try {
 59             while (count == items.length) {
 60                 if (nanos <= 0)
 61                     return false;
 62     //这里是使用同步队列器的超时机制,在nanos的时间范围内,方法会在这里堵塞,超过这个时间段nanos的值会被赋值为负数,方法继续,然后在下一个循环返回false。这个标志是未满标志,队列里面未满就可以放进元素嘛。然后判断成功就是一个入队列操作
 63                 nanos = notFull.awaitNanos(nanos);
 64             }
 65             enqueue(e);
 66             return true;
 67         } finally {
 68             lock.unlock();
 69         }
 70     }
 71     /**
 72      * 入队列操作,因为putIndex已经是当前该放入元素的下标了,放入元素之后,
 73      * 需要将putIndex+1,并且元素数量加1。然后直接调用非空标志通知等待中的消费者
 74      * 质疑:如果我没有等待中的消费者,那也要通知,那不是浪费么?
 75      * 解释:下端代码是signal的实现
 76     public final void signal() {
 77                 if (!isHeldExclusively())
 78                     throw new IllegalMonitorStateException();
 79                     Node first = firstWaiter;
 80                         if (first != null)
 81                             doSignal(first)
 82      }
 83      signal方法已经在里面已经对队列的首元素判断空,不通知了,
 84      这个引起我的一个思考,确实在函数里面就应该对这些条件做判断要比外面判断更好一些,一个是更健壮,一个是更友好,但是这个最小作用模块还是功能模块,别一个调用链做了多次的这种条件的判断,这就让阅读者难受了。
 85      */
 86     private void enqueue(E x) {
 87         final Object[] items = this.items;
 88         items[putIndex] = x;
 89         if (++putIndex == items.length)
 90             putIndex = 0;
 91         count++;
 92         notEmpty.signal();
 93     }
 94     /***
 95      * poll的操作和offer基本一样,就是做的是出队列的操作。还有就是一个drainTo方法也很类似,有一个细节有意思就是drainTo是
 96     *一个批量操作,但是通知却是一个一个通知的。没有调用singalAll()。因为堵塞队列强调一个顺序。一进一出原则。还有就是在外面判断了有无等待者。因为这**样却是省不必要的循环了。
 97     */
 98     public E poll(long timeout, TimeUnit unit) throws InterruptedException {
 99         long nanos = unit.toNanos(timeout);
100         final ReentrantLock lock = this.lock;
101         lock.lockInterruptibly();
102         try {
103             while (count == 0) {
104                 if (nanos <= 0)
105                     return null;
106                 nanos = notEmpty.awaitNanos(nanos);
107             }
108             return dequeue();
109         } finally {
110             lock.unlock();
111         }
112     }
113
114     /**
115      * 出队列操作,跟入队列操作正好是相反的,多了一个清理操作
116      *
117      */
118     private E dequeue() {
119       final Object[] items = this.items;
120         @SuppressWarnings("unchecked")
121         E x = (E) items[takeIndex];
122         items[takeIndex] = null;
123         if (++takeIndex == items.length)
124             takeIndex = 0;
125         count--;
126         if (itrs != null)
127             //@key jdk1.8的新特性迭代器特性,这里是因为元素的出队列所以清理和这个元素相关联的迭代器
128         itrs.elementDequeued();
129         //对于生产者的通知
130         notFull.signal();
131         return x;
132     }
133     /**
134      * 根据下标移除元素,那么会分成两种情况一个是移除的是队首元素,一个是移除的是非队首元素,移除队首元素,就相当于出队*列操作,移除非队首元素那么中间就有空位了,后面元素需要依次补上,然后如果是队尾元素,那么putIndex也就是插入操作的*下标也就需要跟着移动。这里面同样有无用迭代器的清理和notFull标志的通知。elementDequeued 和removedAt 这两个函数差*不多主要做的就是清理。但是不一样的是第一种情况当成出队列来处理了。而第二种就相当于这个元素就没有进过队列来处理,*轻轻地来,轻轻地走不带走一片云彩
135      */
136     void removeAt(final int removeIndex) {
137        final Object[] items = this.items;
138         //当移除的元素正好是队列首元素,就是take元素,正常的类似出队列的操作,
139         if (removeIndex == takeIndex) {
140             // removing front item; just advance
141             items[takeIndex] = null;
142             if (++takeIndex == items.length)
143                 takeIndex = 0;
144             count--;
145             if (itrs != null)
146                 itrs.elementDequeued();
147             //
148         } else {
149             //因为是队列中间的值被移除了,所有后面的元素都要挨个迁移
150             final int putIndex = this.putIndex;
151             for (int i = removeIndex;;) {
152                 int next = i + 1;
153                 if (next == items.length)
154                     next = 0;
155                 if (next != putIndex) {
156                     items[i] = items[next];
157                     i = next;
158                 } else {
159                     items[i] = null;
160                     this.putIndex = I;
161                     break;
162                 }
163             }
164             count—;
165             if (itrs != null)
166             itrs.removedAt(removeIndex);
167         }
168         notFull.signal();
169     }
170         /**
171          * 当元素出队列的时候调用的方法这个出队列方法
172          */
173         void elementDequeued() {
174             // 在队列为空的时候调用清空所有的迭代器;
175             if (count == 0)
176                 queueIsEmpty();
177             // 当拿元素进行循环的时候,清理所有过期的迭代器
178             else if (takeIndex == 0)
179                 takeIndexWrapped();
180         }
181     }
182     /**
183      * 因为takeIndex等于0了,意味着开始下一个循环了.
184      * 然后通知所有的迭代器,删除无用的迭代器。
185      */
186     void takeIndexWrapped() {
187         //循环了一次cycle加1
188         cycles++;
189         for (Node o = null, p = head; p != null;) {
190             final Itr it = p.get();
191             final Node next = p.next;
192             //需要清理的条件,和清理代码
193             if (it == null || it.takeIndexWrapped()) {
194                 p.clear();
195                 p.next = null;
196                 if (o == null)
197                     head = next;
198                 else
199                     o.next = next;
200             } else {
201                 o = p;
202             }
203             p = next;
204         }
205         //没有迭代器了,就关掉迭代器的集合
206         if (head == null)   // no more iterators to track
207             itrs = null;
208     }
209     /**这个takeIndexWrapped 是内部类Itr 的方法跟上面不是一个类的方法
210      *这里就是判断这个迭代器所持有的元素还在队列里面么,那么有两个条件,1.isDetached()
211      * 2.就是看这个的循环次数,比建立这个迭代器的时候的循环次数,如果大于1,说明发生过两次以上的循环
212      * 拿里面的元素都换了个遍,拿肯定是不对了,拿这个迭代器就被关闭了。
213      * @return true if this iterator should be unlinked from itrs
214      */
215     boolean takeIndexWrapped() {
216         // assert lock.getHoldCount() == 1;
217         if (isDetached())
218             return true;
219         if (itrs.cycles - prevCycles > 1) {
220             // All the elements that existed at the time of the last
221             // operation are gone, so abandon further iteration.
222             shutdown();
223             return true;
224         }
225         return false;
226     }
227     //将所有的标志位都标记成remove ,null
228     void shutdown() {
229          cursor = NONE;
230         if (nextIndex >= 0)
231             nextIndex = REMOVED;
232         if (lastRet >= 0) {
233             lastRet = REMOVED;
234             lastItem = null;
235         }
236         prevTakeIndex = DETACHED;
237     }
238     /***
239      * 迭代器的基本方法之一,获取下一个元素,会发生缓存器失效的情况,如果是缓存器失效了,能重组就重组,即从takeIndex开始遍历,如果不行就标记失效,  *返回none
240      * @return
241      */
242     public E next() {
243         // assert lock.getHoldCount() == 0;
244         final E x = nextItem;
245         if (x == null)
246             throw new NoSuchElementException();
247         final ReentrantLock lock = ArrayBlockingQueue.this.lock;
248         lock.lock();
249         try {
250             //当判定该迭代器失效了,会重组迭代器,以takeIndex为起点开始遍历,或者标记失效
251             if (!isDetached())
252                 incorporateDequeues();
253             lastRet = nextIndex;
254             final int cursor = this.cursor;
255     //cursor这个值会在incorporateDequeues方法中修改,
256             if (cursor >= 0) {
257                 nextItem = itemAt(nextIndex = cursor);
258                 this.cursor = incCursor(cursor);
259             } else {
260                 nextIndex = NONE;
261                 nextItem = null;
262             }
263         } finally {
264             lock.unlock();
265         }
266         return x;
267     }
268
269     /**
270      * 发现元素发生移动,通过判定cycle等信息,然后cursor取值游标就重新从takeIndex开始
271      * 下面如果发现所有记录标志的值发生变化,就直接清理本迭代器了。
272      * */
273     private void incorporateDequeues() {
274        final int cycles = itrs.cycles;
275         final int takeIndex = ArrayBlockingQueue.this.takeIndex;
276         final int prevCycles = this.prevCycles;
277         final int prevTakeIndex = this.prevTakeIndex;
278         if (cycles != prevCycles || takeIndex != prevTakeIndex) {
279             final int len = items.length;
280             // 从本迭代器建立开始,到目前堵塞队列出队列的个数,也就是takeIndex的偏移量
281             long dequeues = (cycles - prevCycles) * len
282                     + (takeIndex - prevTakeIndex);
283             // 判断所记录的last,next cursor 还是不是原值如果不是,这个迭代器就判定detach
284             if (invalidated(lastRet, prevTakeIndex, dequeues, len))
285                 lastRet = REMOVED;
286             if (invalidated(nextIndex, prevTakeIndex, dequeues, len))
287                 nextIndex = REMOVED;
288             if (invalidated(cursor, prevTakeIndex, dequeues, len))
289                 cursor = takeIndex;
290             if (cursor < 0 && nextIndex < 0 && lastRet < 0)
291                 detach();
292             else {
293                 //重新记录cycle值
294                 this.prevCycles = cycles;
295                 this.prevTakeIndex = takeIndex;
296             }
297         }
298     }
299 /***********************【迭代器类的链表集合管理类】*************************************************************************************/
300      /**
301      * 下面是一个内部类:迭代集合链表类(用于处理迭代器)
302      *  作用:管理当前阻塞队列的迭代器
303       */
304     class Itrs {
305
306         /**
307          * 内部类中的内部类,自定义了一个节点
308          * 将里面的元素设置成弱引用,目标就是当成缓存使用的
309          * WeakReference:帮助JVM合理的释放对象,造成不必要的内存泄漏!!
310          */
311         private class Node extends WeakReference<Itr> {
312             //下一个节点
313             Node next;
314             //节点构造器
315             Node(Itr iterator, Node next) {
316                 super(iterator);
317                 this.next = next;
318             }
319         }
320
321         /** 记录循环的次数,当take下标到0的时候为一个循环 cycle+1 */
322         int cycles = 0;
323
324         /** 定义一个头节点 **/
325         private Node head;
326
327         /** 用于删除无用的迭代器 */
328         private Node sweeper = null;
329         /**
330          * 这个标识删除探针
331          */
332         private static final int SHORT_SWEEP_PROBES = 4;
333         private static final int LONG_SWEEP_PROBES = 16;
334         //迭代器链表集合的构造器
335         Itrs(Itr initial) {
336             register(initial);
337         }
338         /**
339          * 注册逻辑的实现,在链表的最前面加元素
340          */
341         void register(Itr itr) {
342             head = new Node(itr, head);//创建一个头节点
343         }
344         void doSomeSweeping(boolean tryHarder) {}    //删除旧的,过期的,无用的迭代器
345         void takeIndexWrapped() {}                    //
346         void removedAt(int removedIndex) {}            //
347         void queueIsEmpty() {}                        //
348         void elementDequeued() { }                    //
349     }
350 /***********************【迭代器类】*************************************************************************************/
351     /**
352      *创建一个内部类:当前阻塞队列的迭代器
353      */
354      private class Itr implements Iterator<E> {
355         /** 光标,是迭代器下一次迭代时的坐标,迭代器没有需要遍历的对象了,这个值会为负值*/
356         private int cursor;
357
358         /** 下一个元素内容,调用Iterator.next方法拿到的值 */
359         private E nextItem;
360
361         /** 下一个元素的下标,none 是-1 被移除了是-2对应下面的static int */
362         private int nextIndex;
363
364         /** 上一个元素的内容 */
365         private E lastItem;
366
367         /** 上一个元素的的下标,none 是-1 被移除的是-2 同样对应下面的static int */
368         private int lastRet;
369
370          /** 记录之前的开始遍历的下标,当这个迭代器判定为失效了这个值就是DETACHED */
371         private int prevTakeIndex;
372
373          /* 记录之前循环次数的值,和Cycles进行比对,就知道有没有再循环过 */
374         private int prevCycles;
375
376         /** 当阻塞队列中无数据时的状态值 */
377         private static final int NONE = -1;
378
379         /**元素被调用remove方法移走,的状态值*/
380         private static final int REMOVED = -2;
381
382         /**元素被调用detached方法后的状态值*/
383         private static final int DETACHED = -3;
384         /**迭代器的初始化函数从takeIndex位置开始遍历*/
385         Itr() {
386             lastRet = NONE;
387             /**拿到当前阻塞队列的锁*/
388             final ReentrantLock lock = ArrayBlockingQueue.this.lock;
389             /**开始锁住*/
390             lock.lock();
391             try {
392                 if (count == 0) {//当前阻塞队列中无数据
393                     cursor = NONE;//下一次迭代的索引值:-1
394                     nextIndex = NONE;//下一位元素的索引值:-1
395                     prevTakeIndex = DETACHED;//上一次遍历使用的索引值:-3  失效
396                 } else {//阻塞队列中有数据
397                     /** 初始化Itr迭代器的属性值 */
398                     final int takeIndex = ArrayBlockingQueue.this.takeIndex;//拿到当前阻塞队列下一个取到数据的索引值
399                     prevTakeIndex = takeIndex;//下一位索引值=前一位取值索引值
400                     nextItem = itemAt(nextIndex = takeIndex);
401                     cursor = incCursor(takeIndex);//队列首元素后一个
402                     if (itrs == null) {
403                         itrs = new Itrs(this);
404                     } else {
405                         //注册到itrs,所有迭代器的集合,顺序注册的
406                         itrs.register(this);
407                         // 清理无用的迭代器
408                         itrs.doSomeSweeping(false);
409                     }
410                     prevCycles = itrs.cycles;
411                 }
412             } finally {
413                 //解锁
414                 lock.unlock();
415             }
416         }
417         /**当前迭代器是否失效: 负值意味着失效迭代器 */
418         boolean isDetached() {
419             return prevTakeIndex < 0;
420         }
421         /**初始化下一个要拿到的元素的索引值 */
422         private int incCursor(int index) {
423             if (++index == items.length){//如果下一个元素的索引值刚好等于阻塞队列元素个数(说明已经到了队列的尾部),迭代重头开始
424                 index = 0;//返回第一个元素的索引值
425             }
426             if (index == putIndex){//下一个元素的索引值=putIndex:生产者放入对象的下一个对象的索引值
427                  index = NONE;//NONE=-1 表示队列中无数据
428             }
429             return index;
430         }
431
432         /**如果给定数量的索引无效,则返回true。*/
433         private boolean invalidated(int index, int prevTakeIndex,
434                                     long dequeues, int length) {
435             if (index < 0)//队列中无数据,失效
436                 return false;
437             int distance = index - prevTakeIndex;//计算  下一位元素的索引值-前一位元素索引值
438             if (distance < 0)//如果差值小于0
439                 distance += length;
440             return dequeues > distance;
441         }
442         private void incorporateDequeues() {
443             final int cycles = itrs.cycles;
444             final int takeIndex = ArrayBlockingQueue.this.takeIndex;
445             final int prevCycles = this.prevCycles;
446             final int prevTakeIndex = this.prevTakeIndex;
447
448             if (cycles != prevCycles || takeIndex != prevTakeIndex) {
449                 final int len = items.length;
450                 long dequeues = (cycles - prevCycles) * len
451                     + (takeIndex - prevTakeIndex);
452
453                 if (invalidated(lastRet, prevTakeIndex, dequeues, len))
454                     lastRet = REMOVED;
455                 if (invalidated(nextIndex, prevTakeIndex, dequeues, len))
456                     nextIndex = REMOVED;
457                 if (invalidated(cursor, prevTakeIndex, dequeues, len))
458                     cursor = takeIndex;
459
460                 if (cursor < 0 && nextIndex < 0 && lastRet < 0)
461                     detach();
462                 else {
463                     this.prevCycles = cycles;
464                     this.prevTakeIndex = takeIndex;
465                 }
466             }
467         }
468     }
469 }

回顾一下;

我介绍了ArrayblockingQueue其实是包含了两个部分一个是标准阻塞队列接口的实现。另一个是jdk1.8增加的迭代器。上一个满大街博客都能找的到,我就把接口描述了一下,然后介绍了两个还算是复杂一点的接口。和整个一个工作原理,没有太多使用case。主要是就是生产者和消费者模型。一个锁应用,和其他的JUC框架不一样。它什么操作都加锁,并发变串行。所以它就没有用到原子类修饰的共享变量。
    关于迭代器部分好像是只有我这里有写。如果有百度上有看到相关ArrayBlockingQueue迭代器文章的请留言。毕竟我一家之言,还是有可能会有理解上的偏差。我们总结一下这个迭代器。首先跟别的设计一样,谁用谁new。这个不一样的是会增加一个注册到堵塞队列对象里面itrs上面。然后呢用了一个软引用,那么就GC可以回收避免内存溢出。然后会有对无用的迭代器的清理,类似于threadLocal那样。那么什么是无用的迭代器呢。标识无用就一个条件,我的迭代器标识的结点被覆盖了,因为它空间就这么大,举个例子一个大小5的堵塞队列。然后我建了一个迭代器,那么这个迭代器的下标就是0.然后迭代器我没有马上用,然后进出队列10次,那么之前节点的值已经被替换了。队列里面还有值,但是迭代器的值已经在take方法中被干掉了,已经失效了。判断条件就是cycle的循环次数。有兴趣可以好好了解一下,这应该是我看过的最复杂的迭代器了。

留一些问题:

1.这个迭代器为什么会比arrayList复杂这么多?

2.其实作为堵塞队列来说无非就是数据交换,拿有什么场景是需要迭代器的?而且本身就全都锁控制,效率就不高。还加入这么复杂的迭代模块。会更慢一些的?

这篇文章会看起来比较碎。尽力了。。没有整块的时间去写。而且没想这个迭代器这么复杂。花费我很多时间去研究(没错,这就是我脱稿的原因)

还有就是风格和上一篇不一样了。我希望可以让看这篇文章的人不光是可以学习到之前不知道的知识。也可以触发大家更多的去主动的思考,去思考模块的设计,功能的实现。而不是被动接受这篇文章所传递出来的内容。

还有就是看这种源码。一定要先框架,功能。摸透再去看细节。如果你对这个代码块所要完成的功能不够了解。拿看起来费劲。框架,功能这些都摸透了。再钻到细节上面去。我们可能用到的框架很多,拿要读的源代码那就太多了。其实阅读源代码我觉得是培养一个阅读代码的能力。一个是学习处理这种场景的解决方案,一个是学习编程风格,编码模式。还有就是可能会培养对编程、对探究的兴趣。毕竟工作不能只是为了赚钱。

转载于:https://www.cnblogs.com/newwind/p/8976262.html

多线程学习-基础(十三)(学习参考·网摘) ArrayBlockingQueue源代碼解析(base jdk 1.8)...相关推荐

  1. [深度学习基础] 深度学习基础及数学原理

    图像分类 (image classification) 问题是指, 假设给定一系列离散的类别(categories)(如猫, 狗, 飞机, 货车, ...), 对于给定的图像, 从这些类别中赋予一个作 ...

  2. 总结1-深度学习-基础知识学习

    [小记]下采样和池化的区别:  池化的神解释: 池化 = 涨水 池化的过程 = 升高水位(扩大矩阵网格) 池化的目的是为了得到物体的边缘形状.可以想象水要了解山立体的形状,水位低时得出山脚的形状,水位 ...

  3. 计算机培训教案入门,计算机二级学习基础PPT学习教案.pptx

    文档介绍: 会计学 1 计算机二级学****基础 2 内容提要 算法:算法的基本概念.算法复杂度 数据结构的基本概念:什么是数据结构. 数据结构的图形表示. 线性结构与非线性结构 线性表及其顺序存储结 ...

  4. 深度学习基础论文学习

    轻量级网络 (一)MobileNet_v1--2017论文解读 (二)ShuffleNet_v1--2017论文解读 (三)MobileNet_v2--2018CVPR论文解读 (四)ShuffleN ...

  5. java 微商_Java 基础语法 - V8微商网_www.vip3158.com---时代创业网_www.sdcye.com - BlogJava...

    一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作.下面简要介绍下类.对象.方法和实例变量的概念. 对象:对象是类的一个实例,有状态和行为.例如,一条狗是一个对象,它的 ...

  6. 对计算机知识的兴趣,大学新生计算机学习基础与兴趣的调查分析

    文章编号:1672-5913(2008)12-0029-04 摘要:本文根据大学计算机基础课程的教学目标和面临的情况,对大学新生的计算机基础知识和能力以及学习兴趣进行了调查,分析了调查结果,提出了相关 ...

  7. java多线程学习笔记--一.多线程的基础知识

    需要学习的知识 多线程基础知识讲解 参考索隆和jim的视频,以及自己做的笔记 导读 为了充分利用CPU资源,人们发明了线程和进程 进程 由来:在单核cpu的时期,为了方便操作把一系列的操作的指令写下来 ...

  8. 基础知识学习---牛客网C++面试宝典(五)C/C++基础之新特性

    1.本栏用来记录社招找工作过程中的内容,包括基础知识学习以及面试问题的记录等,以便于后续个人回顾学习: 暂时只有2023年3月份,第一次社招找工作的过程: 2.个人经历: 研究生期间课题是SLAM在无 ...

  9. python基础教程自学网-Python基础系统管理学习手册视频教程

    Python 当前位置:主页 > 编程教程 > Python > Python基础&系统管理&学习手册视频教程 Python基础&系统管理&学习手册视 ...

最新文章

  1. php 链接多个mysql_PHP同时操作多个MySQL连接
  2. zw版【转发·台湾nvp系列Delphi例程】HALCON Histogram
  3. Swift教程Swift语言快速入门(内部资料)
  4. 皮一皮:现在当爹妈的不容易...
  5. php 判断百度浏览器版本,jquery获取浏览器类型和版本号的方法
  6. hibernate5(8)操纵对象入门[3]操控对象封装方法
  7. 铺铜规则在哪设定_干货丨PCB layout结合生产设计必须遵循这六大规则!
  8. FY-4A建立中国区域图像行列号转经纬度的经纬度查找表进行几何校正
  9. 如何打造高绩效团队?团队成功的关键要素?
  10. 1亿年轻人在Soul找到社交新选择
  11. 余淼杰老师 经济学原理复习笔记(微观)
  12. 安卓:点击空白处隐藏软键盘
  13. 快递企业玩转微信扫一扫教程
  14. JSTL 标签库c:if :forEach :forTokens
  15. 易基因 | 新研究:ChIP-seq揭示酒精性肝炎超级增强子调控机制
  16. mysql(Mariadb)
  17. 基于SSM的学生选课系统
  18. EXCEL下拉列表选项设置
  19. sklearn的决策树和随即森林的demo
  20. vue点击遮罩层禁止关闭dialog弹窗

热门文章

  1. 主动,是因为在乎,不再联系,是因为感到自己多余
  2. 为小区物业开发一个停车位租赁系统
  3. 【php】运算符优先级界定
  4. openresty nginx升级版
  5. 软件工程-GoldPoint游戏
  6. @RequestBody注解失效?从前端传来的数据到底需不需要@RequestBody注解?前端传输数据解析的问题?
  7. 深度linux卡在扫描硬盘,linux使用badblocks命令扫描硬盘排除故障
  8. qml 自定义消息框_Qt qml 自定义消息提示框
  9. linux将字符串转小写_Python教程第10讲:字符串的使用
  10. java io文件流序列化_Java——Properties集合,Object序列化流与反序列化流,打印流,commons-IO文件工具类...