在上篇文章Java集合(三) ArrayList详解的学习和源码分析中,我们知道ArrayList是以数组实现,它的优势是查询性能高,劣势是按顺序增删性能差。如果在不确定元素数量的情况时,不建议使用ArrayList。其实当这种情况时,我们就可以使用LinkedList了。

大O记号

  一直以来我们用快慢来描述存储算法,这种直观的描述是不好的。有一种方法可以量化一种操作的耗时情况。我们可以借用数学分析中的大O记号来描述这种关系。
  解决一个规模为n的问题所花费的步骤如果是n的常数倍,我们就记这个方法的复杂度为O(n)。例如,给一个数组,求这个数组中的最大值,需要遍历这个数组,如果数组长度为n,那么遍历数组的步骤就是n。所以,这是一种O(n)的操作。同样,解决一个规模为n的问题所花费的步骤如果是n^2的常数倍,我们就记这个方法的复杂度为O(n^2)。比如冒泡排序。而解决问题的步骤如果与问题规模无关,这个时间复杂度就是O(1)的。比如,数组的随机存取。而数组的顺序存储就是O(n)的。

链表

  在学习LinkedList之前,我们先对数据结构中链表做一个简单的回顾。链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素成为结点)组成,结点可以在运行时动态生成。
可以这样理解:
  有一条街,小明住在街中一角,他有小红的地址,然后小红也是住在这条街,他有小花的地址,同样小花也有别人的地址。某天我想找小红玩,但是我不知道她住在哪里,我可以问小明,就知道小红住在哪里了。那么小明小红小花这些人之间的关系就组成一个链表。

  • 单链表:就是小明只是右手握着小红的地址,他只有小红一个人的地址
  • 双链表:就是小红左手握着小明的地址,右手握着小花的地址,她有两个人的地址
  • 循环链表:就是小明握着小红的地址,小红握着小花的地址,而小花又握着小明的地址,这样就形成了一个循环
  • 有序链表:以某个标准,给链表的元素排序,比如比较内容大小、比较哈希值等 如果还不好理解,我们可以先看一个类的定义:
public class LinkNode {public int data;public LinkNode next;public LinkNode(int data) {this.data = data;this.next = null;}
}
复制代码

我们定义了一个LinkNode类,这个类有两个成员变量,一个是整形data,还有一个是指向LinkNode类型的引用。通过这个引用,就可以把多个LinkNode串起来,比如,下面的代码:

        LinkNode head = new LinkNode(1);head.next = new LinkNode(2);
复制代码

这样就在内存里创建了两个LinkNode对象,它们之间的关系就像这样:

head所引用的那个LinkNode,其data为1,其next指向下一个LinkNode,其data为2,next域为空。

链表增加一项

  如果我想在head之后,增加一个链表项,让这个链表变成下图的样子,该怎么做?

  思路就是先把新的结点的next设为head的next,然后再使得head.next指向新的结点即可。

        LinkNode newNode = new LinkNode(3);newNode.next = head.next;head.next = newNode;
复制代码

  反过来,想要把head后面的那个结点删掉,也很简单,只需要让head的next再往后指一项,把原来head后面的那一项跳过去就可以了:

        head.next = head.next.next;
复制代码

  可以看到,在链表中,指定的结点后面添加一个项是常数时间的,也就是O(1),删除一个项也是一样的。但与数组不同的,如果我要完成“查询链表中的第n项是什么”这个操作,就会变成O(n)的,因为我们每次查找都必须从头开始,依次向后查找。

 public static int queryData(LinkNode head, int index) {LinkNode cur = head;for (int i = 0; i < index; i++) {cur = cur.next;}return cur.data;}
复制代码

  再看一个问题,假如我知道了一个LinkNode,不妨记为temp,位于一个链表中,如果想知道这个结点的上一个结点是什么,应该怎么办?对于我们前边定义的链表结构,只能从头开始找,去判断是否有某个结点,它的next与temp相等。如果相等就说明,这个结点就是temp的前序结点。代码如下:

public static LinkNode findPrevious(LinkNode head, LinkNode temp) {LinkNode cur = head;while (cur != null) {if (cur.next == temp)return cur;cur = cur.next;}return null;}
复制代码

双向链表

  实际上,在工程实践中,使用链表的时候,经常会有“查询某个结点的前一结点”这样的需求,为了加速这一过程,我们其实可以修改一下LinkNode的定义,结它加上一个指向前序结点的成员变量:

public class DoubleLinkNode {public int data;public LinkNode next;public LinkNode prev;public DoubleLinkNode(int data) {this.data = data;this.next = null;this.prev = null;}
}
复制代码

这样的话,链表就成了这个样子了:

  这样一来,查找某个结点的上一个结点,只要返回它的prev就可以了。
  好了。链表的学习就到这里了。

LinkedList

概述

  LinkedList以双向链表实现。链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作。除了实现List接口外,LinkedList还为在列表的开头及结尾get、remove和insert元素提供了统一的命名方法。这些操作可以将链接列表当作栈,队列和双端队列来使用。
  按索引访问元素:get(i)/set(i,e)要非常恶心的遍历链表将指针移动到位(如果i > 数组大小的一半,会从末尾开始移动)。插入、删除元素时修改前后节点的指针即可,但还是要遍历部分链表的指针才能移动到下标所指的位置,只有在链表两头的操作:add(),addFirst(),removeLast()或用iterator()上的remove()能省掉指针的移动。
  LinkedList同样是非线程安全的,只在单线程下适合使用。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。
  LinkedList的iterator和listIterator方法返回的迭代器是快速失败的(fail-fast机制):在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的remove或add方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。
  LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。

LinkedList源码解析

    public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable,java.io.Serializable {//LinkedList中链表元素个数transient int size = 0;//链表头结点transient Node<E> first;//链表尾结点transient Node<E> last;//默认构造方法,生成一个空的链表public LinkedList() {}//根据c里面的元素生成一个LinkedListpublic LinkedList(Collection<? extends E> c) {//调用空的构造方法this();//将c里面的元素添加到空链表尾部addAll(c);}//首部增加结点,结点的值为eprivate void linkFirst(E e) {final Node<E> f = first;//f指向头结点//生成一个新结点,结点的值为e,其前向指针为null,后向指针为ffinal Node<E> newNode = new Node<>(null, e, f);//first指向新生成的结点,f保存着旧的头结点信息first = newNode;if (f == null)//如果f为null,则表示整个链表目前是空的,则尾结点也指向新结点last = newNode;else//f(老的头结点)的前向指向最新的结点信息f.prev = newNode;size++;//元素个数+1modCount++;//修改次数+1}//尾部增加结点,结点的值为evoid linkLast(E e) {final Node<E> l = last; //l指向尾结点//生成一个新结点,结点的值为e,其前向指针为l,后向指针为nullfinal Node<E> newNode = new Node<>(l, e, null);//last指向新生成的结点,l保存着旧的尾结点信息last = newNode;if (l == null)//如果l为null,则表示整个链表目前是空的,则头结点也指向新结点first = newNode;else//l(旧的尾结点)的后向指针指向最新的结点信息l.next = newNode;size++;//元素个数+1modCount++;//修改次数+1}//非空结点succ之前插入新结点,新结点的值为evoid linkBefore(E e, Node<E> succ) {// assert succ != null; //外界调用需保证succ不为null,否则程序会抛出空指针异常final Node<E> pred = succ.prev;//pred指向succ的前向结点//生成一个新结点,结点的值为e,其前向指针指向pred,后向指针指向succfinal Node<E> newNode = new Node<>(pred, e, succ);succ.prev = newNode;//succ的前向指针指向newNodeif (pred == null)//如果pred为null,则表示succ为头结点,此时头结点指向最新生成的结点newNodefirst = newNode;else//pred的后向指针指向新生成的结点,此时已经完成了结点的插入操作pred.next = newNode;size++;//元素个数+1modCount++;//修改次数+1}//删除头结点,并返回该结点的值private E unlinkFirst(Node<E> f) {// assert f == first && f != null;//需确保f为头结点,且链表不为nullfinal E element = f.item;//获得结点的值final Node<E> next = f.next;//next指向f的后向结点f.item = null;//释放数据结点f.next = null; // help GC   //释放f的后向指针first = next;   //first指向f的后向结点if (next == null)//如果next为null,则表示f为last结点,此时链表即为空链表last = null;else//修改next的前向指针,因为first结点的前向指针为nullnext.prev = null;size--; //元素个数-1modCount++; //修改次数+1return element;}//删除尾结点,并返回尾结点的内容private E unlinkLast(Node<E> l) {// assert l == last && l != null;   //需确保l为尾结点,且链表不为nullfinal E element = l.item;   //获得结点的值final Node<E> prev = l.prev;    //prev执行1的前向结点l.item = null;  //释放l结点的值l.prev = null; // help GC   //释放l结点的前向指针last = prev;    //last结点指向l的前向结点if (prev == null)//如果prev为null,则表示l为first结点,此时链表即为空链表first = null;else//修改prev的后向指针,因为last结点的后向指针为nullprev.next = null;size--;//元素个数-1modCount++;//修改次数+1return element;}//删除结点xE unlink(Node<E> x) {// assert x != null;    //需确保x不为null,否则后续操作会抛出空指针异常final E element = x.item;   //保存x结点的值final Node<E> next = x.next;//next指向x的后向结点final Node<E> prev = x.prev;//prev指向x的前向结点if (prev == null) {//如果prev为空,则x结点为first结点,此时first结点指向next结点(x的后向结点)first = next;} else {prev.next = next;//x的前向结点的后向指针指向x的后向结点x.prev = null;  //释放x的前向指针}if (next == null) {//如果next结点为空,则x结点为尾部结点,此时last结点指向prev结点(x的前向结点)last = prev;} else {next.prev = prev;//x的后向结点的前向指针指向x的前向结点x.next = null;  //释放x的后向指针}x.item = null;  //释放x的值节点,此时x节点可以完全被GC回收size--; //元素个数-1modCount++; //修改次数+1return element;}//获得头结点的值public E getFirst() {final Node<E> f = first;//f指向first结点if (f == null)//如果链表为空throw new NoSuchElementException();return f.item;//返回first结点的值}//获得尾结点的值public E getLast() {final Node<E> l = last; //l指向last结点if (l == null)//如果链表为空throw new NoSuchElementException();return l.item;//返回last结点的值}//移除头结点public E removeFirst() {final Node<E> f = first;//获得头结点if (f == null)//如果链表为空throw new NoSuchElementException();return unlinkFirst(f);  //摘除头结点}//移除尾结点public E removeLast() {final Node<E> l = last;//获得尾结点if (l == null)//如果链表为空throw new NoSuchElementException();return unlinkLast(l);//摘除尾结点}//添加到头结点,结点的值为epublic void addFirst(E e) {linkFirst(e);//添加到头部}//添加到尾结点public void addLast(E e) {linkLast(e);//添加到尾部}//判断元素(值为o)是否o在链表中public boolean contains(Object o) {return indexOf(o) != -1;//定位元素}//返回元素个数public int size() {return size;}//添加元素,元素值为epublic boolean add(E e) {linkLast(e);//添加到链表尾部return true;}//移除值为o的元素,o可以为null,找到一个删除即返回public boolean remove(Object o) {if (o == null) {//元素为nullfor (Node<E> x = first; x != null; x = x.next) {//从结点开始遍历if (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;}//将c中的元素都添加到当前链表中public boolean addAll(Collection<? extends E> c) {return addAll(size, c);//添加到链表尾部}//在序号为index处,添加c中所有的元素到当前链表中(后向添加)public boolean addAll(int index, Collection<? extends E> c) {checkPositionIndex(index);//判断index是否超出界Object[] a = c.toArray();//将集合转换为数组int numNew = a.length;if (numNew == 0)return false;Node<E> pred, succ;if (index == size) {//如果index为元素个数,即index个结点为尾结点succ = null;pred = last;//指向为结点} else {succ = node(index); //succ指向第index个结点pred = succ.prev;   //pred指向succ的前向结点}//for循环结束后,a里面的元素都添加到当前链表里面,后向添加for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;//新生成一个结点,结点的前向指针指向pred,后向指针为nullNode<E> newNode = new Node<>(pred, e, null);if (pred == null)//如果pred为null,则succ为当前头结点first = newNode;else//pred的后向指针指向新结点pred.next = newNode;pred = newNode;//pred移动到新结点}if (succ == null) {last = pred;//succ为null,这表示index为尾结点之后} else {//pred表示所有元素添加之后的最后结点,此时pred的后向指针指向之前的记录的结点pred.next = succ;succ.prev = pred;//之前记录的结点指向添加元素之后的最后结点}size += numNew;//元素个数+nummodCount++;//修改次数+1return true;}//清除链表里面的所有元素public void clear() {for (Node<E> x = first; x != null; ) {Node<E> next = x.next;x.item = null;  //释放值结点,便于GC回收x.next = null;  //释放前向指针x.prev = null;  //释放后向指针x = next;   //后向遍历}first = last = null;//释放头尾结点size = 0;modCount++;}//获得第index个结点的值public E get(int index) {checkElementIndex(index);return node(index).item;    //点位第index结点,返回值信息}//设置第index元素的值public E set(int index, E element) {checkElementIndex(index);Node<E> x = node(index);//定位第index个结点E oldVal = x.item;x.item = element;return oldVal;}//第index个结点之前添加结点public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));}//删除第index个结点public E remove(int index) {checkElementIndex(index);return unlink(node(index));}//判断index是否是链表中的元素的索引private boolean isElementIndex(int index) {return index >= 0 && index < size;}//判断index是否是链表中的元素的索引private boolean isPositionIndex(int index) {return index >= 0 && index <= size;}private String outOfBoundsMsg(int index) {return "Index: " + index + ", Size: " + size;}private void checkElementIndex(int index) {if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private void checkPositionIndex(int index) {if (!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}//定位链表中的第index个结点Node<E> node(int index) {// assert isElementIndex(index);//确保是合法的索引,即0<=index<=size//index小于size的一半时,从头向后找if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {//index大于size的一半时,从尾向前找Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}//定位元素,首次出现的元素的值为o的结点序号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;}//定位元素,最后一次出现的元素值为o的元素序号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;}//实现队列操作,返回第一个元素的值public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;}//实现队列操作,返回第一个结点public E element() {return getFirst();}//实现队列操作,弹出第一个结点public E poll() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}//删除结点public E remove() {return removeFirst();}//添加结点public boolean offer(E e) {return add(e);}//添加头结点public boolean offerFirst(E e) {addFirst(e);return true;}//添加尾结点public boolean offerLast(E e) {addLast(e);return true;}//返回头结点的值public E peekFirst() {final Node<E> f = first;return (f == null) ? null : f.item;}//返回尾结点的值public E peekLast() {final Node<E> l = last;return (l == null) ? null : l.item;}//弹出第一个结点public E pollFirst() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}//弹出最后一个结点public E pollLast() {final Node<E> l = last;return (l == null) ? null : unlinkLast(l);}//添加头部结点public void push(E e) {addFirst(e);}//弹出第一个结点public E pop() {return removeFirst();}//删除值为o的结点public boolean removeFirstOccurrence(Object o) {return remove(o);}//删除值为o的结点(从尾部遍历)public boolean removeLastOccurrence(Object o) {if (o == null) {for (Node<E> x = last; x != null; x = x.prev) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = last; x != null; x = x.prev) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;}//返回双向迭代器public ListIterator<E> listIterator(int index) {checkPositionIndex(index);return new ListItr(index);}//私有内部类,实现双向迭代器private class ListItr implements ListIterator<E> {private Node<E> lastReturned;//记录当前结点信息private Node<E> next;//当前结点的后向结点private int nextIndex;//当前结点的序号private int expectedModCount = modCount;//修改次数//初始化ListItr(int index) {// assert isPositionIndex(index);next = (index == size) ? null : node(index);nextIndex = index;}//是否有结点public boolean hasNext() {return nextIndex < size;}//返回下一个结点public E next() {checkForComodification();if (!hasNext())throw new NoSuchElementException();lastReturned = next;//记录当前结点next = next.next;//向后移动nextIndex++;//结点序号+1return lastReturned.item;}//是否有前向结点public boolean hasPrevious() {return nextIndex > 0;}//返回前向结点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++;}// Lambda表达式结合迭代器进行遍历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();}}private static class Node<E> {E item; //结点的值Node<E> next;   //结点的后向指针Node<E> prev;   //结点的前向指针//构造方法中已完成Node成员的赋值Node(Node<E> prev, E element, Node<E> next) {this.item = element;    //结点的值赋值为elementthis.next = next;       //后向指针赋值this.prev = prev;       //前向指针赋值}}//返回前向迭代器public Iterator<E> descendingIterator() {return new DescendingIterator();}//前向迭代器private class DescendingIterator implements Iterator<E> {private final ListItr itr = new ListItr(size());public boolean hasNext() {return itr.hasPrevious();}public E next() {return itr.previous();}public void remove() {itr.remove();}}@SuppressWarnings("unchecked")private LinkedList<E> superClone() {try {return (LinkedList<E>) super.clone();} catch (CloneNotSupportedException e) {throw new InternalError(e);}}//拷贝操作,执行浅拷贝,只复制引用,而没有复制引用指向的内存public Object clone() {LinkedList<E> clone = superClone();// Put clone into "virgin" stateclone.first = clone.last = null;clone.size = 0;clone.modCount = 0;// Initialize clone with our elementsfor (Node<E> x = first; x != null; x = x.next)clone.add(x.item);return clone;}//转换为数组public Object[] toArray() {Object[] result = new Object[size];int i = 0;for (Node<E> x = first; x != null; x = x.next)result[i++] = x.item;return result;}//转换为数组@SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {if (a.length < size)a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);int i = 0;Object[] result = a;for (Node<E> x = first; x != null; x = x.next)result[i++] = x.item;if (a.length > size)a[size] = null;return a;}//序列化版本private static final long serialVersionUID = 876323262645176354L;//序列化private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// Write out any hidden serialization magics.defaultWriteObject();// Write out sizes.writeInt(size);// Write out all elements in the proper order.for (Node<E> x = first; x != null; x = x.next)s.writeObject(x.item);}//反序列化@SuppressWarnings("unchecked")private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in any hidden serialization magics.defaultReadObject();// Read in sizeint size = s.readInt();// Read in all elements in the proper order.for (int i = 0; i < size; i++)linkLast((E) s.readObject());}//获取一个分割器,1.8新增@Overridepublic Spliterator<E> spliterator() {return new LLSpliterator<E>(this, -1, 0);}/*** A customized variant of Spliterators.IteratorSpliterator*/static final class LLSpliterator<E> implements Spliterator<E> {···}}
复制代码

总结

  关于LinkedList的源码,给出几点比较重要的总结:
  1.从源码中很明显可以看出,LinkedList的实现是基于双向链表的,且头结点中不存放数据。
  2.注意两个不同的构造方法。无参构造方法直接建立一个仅包含head节点的空链表;包含Collection的构造方法,先调用无参构造方法建立一个空链表,而后将Collection中的数据加入到链表的尾部后面。
  3.在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。
  4.LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
  5.Node node(int index)方法。该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index<(size<<1),就只从位置0往后遍历到位置index处,而如果index>(size<<1),就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
  6.LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。
  7.要注意源码中还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用。

参考

数据结构(二):链表
LearningNotes

Java集合(四) LinkedList详解相关推荐

  1. java集合Collection常用方法详解

    前言 出去面试的时候,对java的集合框架考察的知识点还是蛮多的.除了基础的集合常见API使用,对集合底层的实现原理以及数据结构等也有很多考察方面.而自己对这方面知之甚少,特地抽空进行研究和学习一下. ...

  2. Java集合:ConcurrentHashMap详解

    前言 近期深入学习了ConcurrentHashMap,便整理成一篇博文记录一下,请注意:此博文针对的是JDK1.6,因此如果你看到的源码跟我文中的不同,则可能是由于版本不一样. Concurrent ...

  3. java list 在头部添加6_【Java提高十六】集合List接口详解

    在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!java中集合大家族的成员实在是太丰富了,有常用的ArrayList. ...

  4. Java集合迭代器原理图解_Java Iterator接口遍历单列集合迭代器原理详解

    这篇文章主要介绍了Java Iterator接口遍历单列集合迭代器原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Iterator接口概述 ...

  5. Java中LinkedList详解

    Java中LinkedList详解 LinkedList底层是双向链表 单向链表 双向链表 LinkedList新增的方法 主要增加了针对头结点与尾结点进行操作的方法, 即针对第一个元素和最后一个元素 ...

  6. Java中Iterator迭代器详解

    目录 一.Java中Iterator迭代器详解 1.为什么需要迭代器 2.迭代器长什么样子 3.如何使用迭代器 使用步骤: 代码演示: 迭代器可以简化为增强型for循环: 4.Iterator与Lis ...

  7. Java单元测试之JUnit4详解

    2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @B ...

  8. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

  9. Java基础学习总结(24)——Java单元测试之JUnit4详解

    Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且是第一个运行 @Before  ...

  10. 集合框架 Queue---BlockingQueue详解

    转载自  集合框架 Queue---BlockingQueue详解 摘要:本例介绍一个特殊的队列:BlockingQueue,如果BlockingQueue是空的,从BlockingQueue取东西的 ...

最新文章

  1. 基于Opencv实现眼睛控制鼠标
  2. 修改MAC ADDRESS
  3. POJ_2001_Shortest Prefixes
  4. 殊途同归!招聘软件与社交软件最终都要走向约P宿命?
  5. 安装autoit libary失败问题解决
  6. having and group by
  7. 用幻灯片做完整的“一站到底”抢答器
  8. spring cloud config 加密配置
  9. 每天一点matlab——字符分割
  10. java+网络框架netty_GitHub - linyu19872008/getty-1: 一个完全基于java 实现的,长得有点像netty的aio网络框架...
  11. 最近 火火火 的开源项目
  12. matlab mat转bmp,mat格式转换
  13. Win10系统下基于Docker构建Appium容器连接Android模拟器Genymotion完成移动端Python自动化测试
  14. caffe内CHECK_EQ等函数意义解释
  15. c语言数组相同字符主元素,C语言数组考点归纳
  16. C/C++中各种类型char、int、long、double等数据范围
  17. 乐播:手机投屏和镜像有什么区别?
  18. 断路器Hystrix实现服务容错
  19. 00 后程序员就要为“你”加班?呵呵
  20. 基于stm32单片机的WIFI智能联网天气预报自动校时系统(源码+原理图+全套资料)

热门文章

  1. 283EEZOJ #89 Cow Tennis Tournament
  2. android:id = @+id 用法,@+id/android:list和@android:id/list的写法
  3. python find函数_Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案
  4. thinkcmf 横向排列数据_Excel横向筛选出销量靠后的月份数据,你知道如何实现吗...
  5. java链接mysql原始方法_java连接mysql数据库的方法
  6. [Ext JS6]Ext.Template
  7. java 前端导出exvel_java导出数据到Excel文件 前端进行下载
  8. python调用函数怎么错_python调用函数失败是什么原因
  9. mysql 5.6 外键_mysql 5.6外键约束错误;没有发生在5.5
  10. c语言中数组结尾的0的作用,C语言里面一个数组最后的\0表示什么意思?