• LinkedList是一个实现了List接口和Deque接口的双端链表
  • 有关索引的操作可能从链表头开始遍历到链表尾部,也可能从尾部遍历到链表头部,这取决于看索引更靠近哪一端。
  • LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以使用如下方式:
List list=Collections.synchronizedList(new LinkedList(...));

iterator()和listIterator()返回的迭代器都遵循fail-fast机制。

从上图可以看出LinkedList与ArrayList的不同之处

  • ArrayList直接继承自AbstractList
  • LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。另外还实现了Dequeu接口,双端队列。

内部结构

LinkedList内部是一个双端链表的结构

LinkedList的结构

从上图可以看出,LinkedList内部是一个双端链表结构,有两个变量,first指向链表头部,last指向链表尾部。

成员变量

size表示当前链表中的数据个数

下面是Node节点的定义,LinkedList的静态内部类

从Node的定义可以看出链表是一个双端链表的结构。

构造方法

LinkedList有两个构造方法,一个用于构造一个空的链表,一个用已有的集合创建链表

添加

因为LinkedList即实现了List接口,又实现了Deque,所以LinkedList既可以添加将元素添加到尾部,也可以将元素添加到指定索引位置,还可以添加添加整个集合;另外既可以在头部添加,又可以在尾部添加。

分别从List接口和Deque接口介绍。

List接口的添加

add(E e)

add(E e)用于将元素添加到链表尾部,实现如下:

public boolean add(E e) {linkLast(e);return true;}void linkLast(E e) {final Node<E> l = last;//指向链表尾部final Node<E> newNode = new Node<>(l, e, null);//以尾部为前驱节点创建一个新节点last = newNode;//将链表尾部指向新节点if (l == null)//如果链表为空,那么该节点既是头节点也是尾节点first = newNode;else//链表不为空,那么将该结点作为原链表尾部的后继节点l.next = newNode;size++;//增加尺寸modCount++;
}

从上面代码可以看到,就是一个链表尾部添加一个双端节点的操作,但是需要注意对链表为空时头节点的处理。

add(int index,E e)

add(int index,E e)用于在指定位置添加元素

1. 检查index的范围,否则抛出异常
2. 如果插入位置是链表尾部,那么调用linkLast方
3. 如果插入位置是链表中间,那么调用linkBefore

看一下linkBefore的实现
在看linkBefore之前,先看一下node(int index)方法,该方法返回指定位置的节点

node(int index)将根据index是靠近头部还是尾部选择不同的遍历方向
一旦得到了指定索引位置的节点,再看linkBefore()

linkBefore()方法在第二个参数节点前插入一个新节点

linkBefore#第一步

linkBefore#第二步

linkBefore#第三步

linkBefore主要分三步
1. 创建newNode节点,将newNode的后继指针指向succ,前驱指针指向pred
2. 将succ的前驱指针指向newNode
3. 根据pred是否为null,进行不同操作。

  • 如果pred为null,说明该节点插入在头节点之前,要重置first头节点
  • 如果pred不为null,那么直接将pred的后继指针指向newNode即可

addAll

addAll有两个重载方法

  • 一个参数的方法表示将集合元素添加到链表尾部
  • 两个参数的方法指定了开始插入的位置
//将集合插入到链表尾部,即开始索引位置为size
public boolean addAll(Collection<? extends E> c) {return addAll(size, c);}//将集合从指定位置开始插入
public boolean addAll(int index, Collection<? extends E> c) {//Step 1:检查index范围checkPositionIndex(index);//Step 2:得到集合的数据Object[] a = c.toArray();int numNew = a.length;if (numNew == 0)return false;//Step 3:得到插入位置的前驱节点和后继节点Node<E> pred, succ;//如果插入位置为尾部,前驱节点为last,后继节点为nullif (index == size) {succ = null;pred = last;}//否则,调用node()方法得到后继节点,再得到前驱节点else {succ = node(index);pred = succ.prev;}//Step 4:遍历数据将数据插入for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;//创建新节点Node<E> newNode = new Node<>(pred, e, null);//如果插入位置在链表头部if (pred == null)first = newNode;elsepred.next = newNode;pred = newNode;}//如果插入位置在尾部,重置last节点if (succ == null) {last = pred;}//否则,将插入的链表与先前链表连接起来else {pred.next = succ;succ.prev = pred;}size += numNew;modCount++;return true;}

1. 检查index索引范围
2. 得到集合数据
3. 得到插入位置的前驱和后继节点
4. 遍历数据,将数据插入到指定位置

Deque接口的添加

addFirst(E e)

将元素添加到链表头部

 public void addFirst(E e) {linkFirst(e);}private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点first = newNode;//如果链表为空,last节点也指向该节点if (f == null)last = newNode;//否则,将头节点的前驱指针指向新节点elsef.prev = newNode;size++;modCount++;}

在头节点插入一个节点使新节点成为新节点,但是和linkLast一样需要注意当链表为空时,对last节点的设置

addLast(E e)

将元素添加到链表尾部,与add()方法一样

public void addLast(E e) {linkLast(e);
}

offer(E e)

将数据添加到链表尾部,其内部调用了add(E e)方法

  public boolean offer(E e) {return add(e);
}

offerFirst(E e)方法

将数据插入链表头部,与addFirst的区别在于

  • 该方法可以返回特定的返回值
  • addFirst的返回值为void。
  public boolean offerFirst(E e) {addFirst(e);return true;
}

offerLast(E e)方法

offerLast()与addLast()的区别和offerFirst()和addFirst()的区别一样

添加操作总结

LinkedList由于实现了List和Deque接口,所以有多种添加方法,总结一下

  • 将数据插入到链表尾部

    • boolean add(E e):
    • void addLast(E e)
    • boolean offerLast(E e)
  • 将数据插入到链表头部
    • void addFirst(E e)
    • boolean offerFirst(E e)
  • 将数据插入到指定索引位置
    • boolean add(int index,E e)

2检索

2.1 根据位置取数据

2.1.1 get(int index)

获取任意位置的,get(int index)方法根据指定索引返回数据,如果索引越界,那么会抛出异常

/*** Returns the element at the specified position in this list.** @param index index of the element to return* @return the element at the specified position in this list* @throws IndexOutOfBoundsException {@inheritDoc}*/public E get(int index) {checkElementIndex(index);return node(index).item;}

1.检查index边界,index>=0&&index
2.返回指定索引位置的元素

2.1.2 获得位置为0的头节点数据

LinkedList中有多种方法可以获得头节点的数据,区别在于对链表为空时的处理,是抛异常还是返回null
主要方法有getFirst()、element()、peek()、peekFirst()
其中getFirst()和element()方法将会在链表为空时,抛出异常

   /*** Returns the first element in this list.** @return the first element in this list* @throws NoSuchElementException if this list is empty*/public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;}
   /*** Retrieves, but does not remove, the head (first element) of this list.** @return the head of this list* @throws NoSuchElementException if this list is empty* @since 1.5*/public E element() {return getFirst();}

从代码可以看到,element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛NoSuchElementException
下面再看peek()和peekFirst()

   /*** Retrieves, but does not remove, the head (first element) of this list.** @return the head of this list, or {@code null} if this list is empty* @since 1.5*/public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;}
   /*** Retrieves, but does not remove, the first element of this list,* or returns {@code null} if this list is empty.** @return the first element of this list, or {@code null}*         if this list is empty* @since 1.6*/public E peekFirst() {final Node<E> f = first;return (f == null) ? null : f.item;}

当链表为空时,peek()和peekFirst()方法返回null

2.1.3 获得位置为size-1的尾节点数据

获得尾节点数据的方法有

  • getLast()
   /*** Returns the last element in this list.** @return the last element in this list* @throws NoSuchElementException if this list is empty*/public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;}

getLast()在链表为空时,会抛NoSuchElementException

  • peekLast()
    只是会返回null
    peekLast()
   /*** Retrieves, but does not remove, the last element of this list,* or returns {@code null} if this list is empty.** @return the last element of this list, or {@code null}*         if this list is empty* @since 1.6*/public E peekLast() {final Node<E> l = last;return (l == null) ? null : l.item;}

2.2 根据对象得到索引

  • 第一个匹配的索引
    从前往后遍历
  • 最后一个匹配的索引
    从后往前遍历

2.2.1 indexOf()

/*** 返回第一个匹配的索引* in this list, or -1 if this list does not contain the element.* More formally, returns the lowest index {@code i} such that* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,* or -1 if there is no such index.** @param o element to search for* @return the index of the first occurrence of the specified element in*         this list, or -1 if this list does not contain the element*/public int indexOf(Object o) {int index = 0;if (o == null) {//从头往后遍历for (Node<E> x = first; x != null; x = x.next) {if (x.item == null)return index;index++;}} else {//从头往后遍历for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item))return index;index++;}}return -1;}

可以看到,LinkedList可包含null,遍历方式都是从前往后,一旦匹配了,就返回索引

2.2.2 lastIndexOf()

返回最后一个匹配的索引,实现为从后往前遍历

   /*** Returns the index of the last occurrence of the specified element* in this list, or -1 if this list does not contain the element.* More formally, returns the highest index {@code i} such that* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,* or -1 if there is no such index.** @param o element to search for* @return the index of the last occurrence of the specified element in*         this list, or -1 if this list does not contain the element*/public int lastIndexOf(Object o) {int index = size;if (o == null) {for (Node<E> x = last; x != null; x = x.prev) {index--;if (x.item == null)return index;}} else {for (Node<E> x = last; x != null; x = x.prev) {index--;if (o.equals(x.item))return index;}}return -1;}

2.3 检查是否包含某对象

contains(Object o)
检查对象o是否存在于链表中

  /*** Returns {@code true} if this list contains the specified element.* More formally, returns {@code true} if and only if this list contains* at least one element {@code e} such that* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.** @param o element whose presence in this list is to be tested* @return {@code true} if this list contains the specified element*/public boolean contains(Object o) {return indexOf(o) != -1;}

从代码可以看到contains()方法调用了indexOf()方法,只要返回结果不是-1,那就说明该对象存在于链表中

2.4 检索操作总结

检索操作分为按照位置得到对象以及按照对象得到位置两种方式,其中按照对象得到位置的方法有indexOf()和lastIndexOf();按照位置得到对象有如下方法:

  • 根据任意位置得到数据的get(int index)方法,当index越界会抛出异常
  • 获得头节点数据
  • getFirst()和element()方法在链表为空时会抛出NoSuchElementException
  • peek()和peekFirst()方法在链表为空时会返回null
  • 获得尾节点数据
  • getLast()在链表为空时会抛出NoSuchElementException
  • peekLast()在链表为空时会返回null

3删除

  • 按照位置删除

    • 返回是否删除成功的标志
    • 返回被删除的元素
  • 按照对象删除

3.1 删除指定对象

remove(Object o)
一次只删除一个匹配的对象,如果删除了匹配对象,返回true,否则false

   /*** Removes the first occurrence of the specified element from this list,* if it is present.  If this list does not contain the element, it is* unchanged.  More formally, removes the element with the lowest index* {@code i} such that* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>* (if such an element exists).  Returns {@code true} if this list* contained the specified element (or equivalently, if this list* changed as a result of the call).** @param o element to be removed from this list, if present* @return {@code true} if this list contained the specified element*/public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {//一旦匹配,调用unlink()方法和返回trueif (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;}

由于LinkedList可以存储null,所以对删除对象以是否为null做区分
然后从链表头开始遍历,一旦匹配,就会调用unlink()方法将该节点从链表中移除

下面是unlink()

   /*** Unlinks non-null node x.*/E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;//得到后继节点final Node<E> prev = x.prev;//得到前驱节点//删除前驱指针if (prev == null) {first = next;如果删除的节点是头节点,令头节点指向该节点的后继节点} else {prev.next = next;//将前驱节点的后继节点指向后继节点x.prev = null;}//删除后继指针if (next == null) {last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}
unlink第一步

第一步:得到待删除节点的前驱节点和后继节点

unlink第二步

第二步:删除前驱节点

unlink第三步

第三步:删除后继节点
经过三步,待删除的结点就从链表中脱离了。需要注意的是删除位置是头节点或尾节点时候的处理,上面的示意图没有特别指出。

3.2 按照位置删除对象

3.2.1 删除任意位置的对象

  • boolean remove(int index)
    删除任意位置的元素,删除成功将返回true,否则false
  /*** Removes the element at the specified position in this list.  Shifts any* subsequent elements to the left (subtracts one from their indices).* Returns the element that was removed from the list.** @param index the index of the element to be removed* @return the element previously at the specified position* @throws IndexOutOfBoundsException {@inheritDoc}*/public E remove(int index) {checkElementIndex(index);return unlink(node(index));}

1. 检查index范围,属于[0,size)
2. 将索引出节点删除

3.2.2 删除头节点的对象

  • remove()、removeFirst()、pop()
    在链表为空时将抛出NoSuchElementException
  /*** Retrieves and removes the head (first element) of this list.** @return the head of this list* @throws NoSuchElementException if this list is empty* @since 1.5*/public E remove() {return removeFirst();}/*** Pops an element from the stack represented by this list.  In other* words, removes and returns the first element of this list.** <p>This method is equivalent to {@link #removeFirst()}.** @return the element at the front of this list (which is the top*         of the stack represented by this list)* @throws NoSuchElementException if this list is empty* @since 1.6*/public E pop() {return removeFirst();}/*** Removes and returns the first element from this list.** @return the first element from this list* @throws NoSuchElementException if this list is empty*/public E removeFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return unlinkFirst(f);}

remove()和pop()内部调用了removeFirst()
而removeFirst()在链表为空时将抛出NoSuchElementException

  • poll()和,pollFirst()
    在链表为空时将返回null
  /*** Retrieves and removes the head (first element) of this list.** @return the head of this list, or {@code null} if this list is empty* @since 1.5*/public E poll() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}/*** Retrieves and removes the first element of this list,* or returns {@code null} if this list is empty.** @return the first element of this list, or {@code null} if*     this list is empty* @since 1.6*/public E pollFirst() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}

poll()和pollFirst()的实现代码是相同的,在链表为空时将返回null

3.2.3 删除尾节点的对象

  • removeLast()
  /*** Removes and returns the last element from this list.** @return the last element from this list* @throws NoSuchElementException if this list is empty*/public E removeLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return unlinkLast(l);}

可以看到removeLast()在链表为空时将抛出NoSuchElementException

  • pollLast()
  /*** Retrieves and removes the last element of this list,* or returns {@code null} if this list is empty.** @return the last element of this list, or {@code null} if*     this list is empty* @since 1.6*/public E pollLast() {final Node<E> l = last;return (l == null) ? null : unlinkLast(l);}

可以看到pollLast()在链表为空时会返回null,而不是抛出异常

3.3 删除操作总结

删除操作由很多种方法

按照指定对象删除

boolean remove(Object o),一次只会删除一个匹配的对象

按照指定位置删除

  • 删除任意位置的对象
    E remove(int index),当index越界时会抛出异常
  • 删除头节点位置的对象
    • 在链表为空时抛出异常
      E remove()、E removeFirst()、E pop()
    • 在链表为空时返回null
      E poll()、E pollFirst()
  • 删除尾节点位置的对象
    • 在链表为空时抛出异常
      E removeLast()
    • 在链表为空时返回null
      E pollLast()

4迭代器

LinkedList的iterator()内部调用了其listIterator()方法,所以可只分析listIterator()方法listIterator()提供了两个重载方法。

iterator()方法和listIterator()方法的关系如下

 public Iterator<E> iterator() {return listIterator();}public ListIterator<E> listIterator() {return listIterator(0);}public ListIterator<E> listIterator(int index) {checkPositionIndex(index);return new ListItr(index);}

从上面可以看到三者的关系是iterator()——>listIterator(0)——>listIterator(int index)
最终都会调用listIterator(int index),其中参数表示迭代器开始的位置
ListIterator是一个可以指定任意位置开始迭代,并且有两个遍历方法

下面直接看ListItr

private class ListItr implements ListIterator<E> {private Node<E> lastReturned;private Node<E> next;private int nextIndex;private int expectedModCount = modCount;//保存当前modCount,确保fail-fast机制ListItr(int index) {// assert isPositionIndex(index);next = (index == size) ? null : node(index);//得到当前索引指向的next节点nextIndex = index;}public boolean hasNext() {return nextIndex < size;}//获取下一个节点public E next() {checkForComodification();if (!hasNext())throw new NoSuchElementException();lastReturned = next;next = next.next;nextIndex++;return lastReturned.item;}public boolean hasPrevious() {return nextIndex > 0;}//获取前一个节点,将next节点向前移public E previous() {checkForComodification();if (!hasPrevious())throw new NoSuchElementException();lastReturned = next = (next == null) ? last : next.prev;nextIndex--;return lastReturned.item;}public int nextIndex() {return nextIndex;}public int previousIndex() {return nextIndex - 1;}public void remove() {checkForComodification();if (lastReturned == null)throw new IllegalStateException();Node<E> lastNext = lastReturned.next;unlink(lastReturned);if (next == lastReturned)next = lastNext;elsenextIndex--;lastReturned = null;expectedModCount++;}public void set(E e) {if (lastReturned == null)throw new IllegalStateException();checkForComodification();lastReturned.item = e;}public void add(E e) {checkForComodification();lastReturned = null;if (next == null)linkLast(e);elselinkBefore(e, next);nextIndex++;expectedModCount++;}public void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);while (modCount == expectedModCount && nextIndex < size) {action.accept(next.item);lastReturned = next;next = next.next;nextIndex++;}checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}

构造器中,得到了当前位置的节点,就是变量next
next()返回当前节点的值并将next指向其后继节点
previous()返回当前节点的前一个节点的值并将next节点指向其前驱节点
由于Node是一个双端节点,所以这儿用了一个节点就可以实现从前向后迭代和从后向前迭代
另外在ListIterator初始时,exceptedModCount保存了当前的modCount,如果在迭代期间,有操作改变了链表的底层结构,那么再操作迭代器的方法时将会抛出ConcurrentModificationException。

5 例子

由于LinkedList是一个实现了Deque的双端队列,所以LinkedList既可以当做Queue,又可以当做Stack,下面的例子将LinkedList成Stack

public class LinkedStack<E> {private LinkedList<E> linkedList;public LinkedStack() {linkedList = new LinkedList<E>();}//压入数据public void push(E e) {linkedList.push(e);}//弹出数据,在Stack为空时将抛出异常public E pop() {return linkedList.pop();}//检索栈顶数据,但是不删除public E peek() {return linkedList.peek();}}

在将LinkedList当做Stack时,使用pop()、push()、peek()方法需要注意的是LinkedList内部是将链表头部当做栈顶,链表尾部当做栈底,也就意味着所有的压入、摊入操作都在链表头部进行

6总结

LinkedList是基于双端链表的List,其内部的实现源于对链表的操作

  • 适用于频繁增加、删除的情况
  • 该类不是线程安全的
  • 由于LinkedList实现了Queue接口,所以LinkedList不止有队列的接口,还有栈的接口,可以使用LinkedList作为队列和栈的实现

LinkedList源码分析(基于Java8)相关推荐

  1. Java8collection.sort_Collections.sort()源码分析(基于JAVA8)

    java.lang.Object java.util.Collections简介 此类仅包含操作或返回集合的静态方法. 它包含多样的对集合进行操作的算法,"包装器",返回由指定集合 ...

  2. LinkedList 源码分析

    前言 上篇文章分析了 ArrayList 的源码,面试过程中经常会被面试官来问 LinkedList 和 ArrayList 的区别,这篇文章从源码的角度来看下 LinkedList 以后,再和上篇文 ...

  3. java linkedlist源码_Java集合之LinkedList源码分析

    一.LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的. ps:这里有一个问题,就是关于实现LinkedList的数据结构是否 ...

  4. Java类集框架 —— LinkedList源码分析

    在JDK1.7之前,LinkedList是采用双向环形链表来实现的,在1.7及之后,Oracle将LinkedList做了优化,将环形链表改成了线性链表.本文对于LinkedList的源码分析基于JD ...

  5. List接口的常用方法以及ArrayList/LinkedList源码分析

    1.List接口的常用方法 ArrayList list = new ArrayList();list.add(123);list.add(456);list.add("AA"); ...

  6. 数组、链表、LinkedList源码分析、跳表

    一.数组 1.什么是数组 数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据 线性表:数据排成像一条线一样的结构.每个线性表上的数据最多只有前和后两个方向.其实除了数组,链 ...

  7. AsyncHttpClient源码分析-基于Netty的连接池实现

    原文地址:asynchttpclient源码分析-基于Netty的连接池实现 最近项目重构,有了个机会更多接触一个有别于HttpAsyncClient的异步网络框架AsyncHttpClient,是个 ...

  8. Spark资源调度机制源码分析--基于spreadOutApps及非spreadOutApps两种资源调度算法

    Spark资源调度机制源码分析--基于spreadOutApps及非spreadOutApps两种资源调度算法 1.spreadOutApp尽量平均分配到每个executor上: 2.非spreadO ...

  9. uboot源码分析(基于S5PV210)之启动第一阶段

    目录 一.start.S引入 1.u-boot.lds中找到start.S入口 2.SourceInsight中如何找到文件 3.SI中找文件技巧 二.start.S解析 1.不简单的头文件包含 2. ...

  10. iostat IO统计原理linux内核源码分析----基于单通道SATA盘

    iostat IO统计原理linux内核源码分析----基于单通道SATA盘 先上一个IO发送submit_bio流程图,本文基本就是围绕该流程讲解. 内核版本 3.10.96 详细的源码注释:htt ...

最新文章

  1. R语言数据热力图绘制实战(基于原生R函数、ggplot2包、plotly包)
  2. 四川托普计算机职业学校里能拿什么快递,四川托普计算机职业学校怎么样_招生问答...
  3. 新手建议学php吗,关于PHP新手学习的一些指导和建议,新手来我的
  4. acwing算法题--直方图中最大的矩形
  5. 新式类和经典类的区别类的特殊方法单例模式
  6. boost::fusion::zip用法的测试程序
  7. 在BAdI definition PRODUCT_R3_ADAPTER的implementation里获得download type
  8. 鸿蒙OS电脑体验,华为鸿蒙OS体验抢先曝光!有多个更新版本,界面和安卓完全不同...
  9. JAVA连接solr报404,java-Solr管理员给出404错误
  10. UI设计灵感|3D\C4D元素网站,流行最前沿
  11. 返回List的分页方法
  12. 洛谷 P1114 “非常男女”计划
  13. javascript 高级程序设计 (第四版) 第二章 下
  14. 单片机技术与c语言编程教学大纲,单片机原理及应用课程教学大纲
  15. 装饰器模式实现咖啡店(Java代码实例)
  16. 看美剧《疑犯追踪》,学地道美语 Learn idiomatic American English by watching Tv series Person of Interest
  17. bzoj 4742 [Usaco2016 Dec]Team Building
  18. 2020电子设计竞赛G题 - 非接触物体尺寸形态测量
  19. KK凯文.凯利:第一届中国社群领袖峰会演讲实录(全部版)
  20. 学习笔记(2):A110测试-测试课程申请1888

热门文章

  1. android 读取 网页,Android读取网页内容
  2. 怎么把cad的图导入ps_PS中怎么抠图?以扣取头部图像为例
  3. DHCP协议原理及其实现流程
  4. java状态模式所有情况_轻松掌握Java状态模式
  5. lnmp php fpm 默认,LNMP(PHP-FPM)
  6. php常用操作字符串函数,php字符串几个常用的操作函数
  7. 天翼云从业认证课后习题(3.3天翼云网络产品)
  8. 天翼云从业认证(1.6)虚拟化技术基础、服务器虚拟化、存储虚拟化和网络虚拟化技术;
  9. jquery ajax的post、get方式
  10. 笔记-中项案例题-2020年下-风险管理