LinkedList类的注解阅读


/**
* Doubly-linked list implementation of the {@code List} and {@code Deque}
* interfaces. Implements all optional list operations, and permits all
* elements (including {@code null}).
*
* <p>All of the operations perform as could be expected for a doubly-linked
* list. Operations that index into the list will traverse the list from
* the beginning or the end, whichever is closer to the specified index. 双链表实现了List和Deque的接口,实现所有可选列表操作,并允许所有操作元素(包括null)
对于双向链表,所有操作都可以预期。 索引到列表中的操作将从开头或结尾遍历列表,以较接近指定索引为准。
  • Deque接口就是双向队列,是Queue(队列)的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll);

    如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则。
 *
* Note that this implementation is not synchronized.</strong>
* If multiple threads access a linked list concurrently, and at least
* one of the threads modifies the list structurally, it <i>must</i> be
* synchronized externally. (A structural modification is any operation
* that adds or deletes one or more elements; merely setting the value of
* an element is not a structural modification.) This is typically
* accomplished by synchronizing on some object that naturally
* encapsulates the list.
*
*
* If no such object exists, the list should be "wrapped" using the
* {@link Collections#synchronizedList Collections.synchronizedList}
* method. This is best done at creation time, to prevent accidental
* unsynchronized access to the list:<pre>
* List list = Collections.synchronizedList(new LinkedList(...));</pre> 请注意,此实现不同步。 如果多个线程同时访问链表,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这通常通过同步自然封装列表的某个对象来完成。
如果不存在此类对象,则应使用Collections.synchronizedList}方法“包装”该列表。 这最好在创建时完成,以防止对列表的意外不同步访问: List list = Collections.synchronizedList(new LinkedList(...))
  • LinkedList线程不同步,可以使用synchronizedList包装此列表,使其线程安全
 * The iterators returned by this class's {@code iterator} and
* {@code listIterator} methods are <i>fail-fast</i>: if the list is
* structurally modified at any time after the iterator is created, in
* any way except through the Iterator's own {@code remove} or
* {@code add} methods, the iterator will throw a {@link
* ConcurrentModificationException}. Thus, in the face of concurrent
* modification, the iterator fails quickly and cleanly, rather than
* risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
* 这个类的iterator和listIterator方法返回的迭代器是 fail-fast 机制:如果在创建迭代器之后的任何时候对列表进行了结构修改,除了通过Iterator自己的 remove或 add方法之外,迭代器将抛出 ConcurrentModificationException。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
  • LinkedList的迭代器也是采用快速失败的策略,在阅读ArrayList源码的时候分析过(参考阅读源码JDK1.8(集合篇)- ArrayList)

* <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
* as it is, generally speaking, impossible to make any hard guarantees in the
* presence of unsynchronized concurrent modification. Fail-fast iterators
* throw {@code ConcurrentModificationException} on a best-effort basis.
* Therefore, it would be wrong to write a program that depended on this
* exception for its correctness: <i>the fail-fast behavior of iterators
* should be used only to detect bugs.</i> 注意,迭代器的fail-fast行为是不能保证的.一般来说,保证非同步的同步操作是不太可能的.在最优基础上,Fail-fast迭代器会抛出ConcurrentModificationException.因此,写一个为了自身正确性而依赖于这个异常的程序是不对的.迭代器的fail-fast行为应该只是用来检测bug而已.
  • 我们要主动封装list以便进行同步操作,程序要要避免此异常而不是使用此异常

LinkedList类的定义

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • 继承的类

    AbstractSequentialList:抽象类,AbstractList的子类,以最大限度地减少“顺序访问”数据存储实现此接口所需的工作量

  • 实现的接口

    List:不多说了


    Deque:(标记接口)Deque接口就是双向队列,是Queue(队列)的一个子接口

    Cloneable:(标记接口)代表 Object.clone() 方法可以合法地对该类实例进行按字段复制。(没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常)

    java.io.Serializable(标记接口)

属性的定义

    protected transient int modCount = 0;

  • 这是父类AbstractList的一个属性 :用于记录列表结构被修改的次数。每次列表结构被修改都会modCount++

    为什么要记录此数据呢?

    在线程不安全的集合中,正如上面所说:迭代器采用了fail-fast机制。而fail-fast机制触发原理就是比对expectedModCount 和 modCount 是否相等,不相等就报ConcurrentModificationException异常

    此处不理解没关系,后面会讲迭代器方法的源码时,就会明白了
    transient int size = 0;

  • transient修饰不可被序列化
    /**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*
* 指向第一个节点的指针
*/
transient Node<E> first;
  • 存储第一个节点的Node实例,记住一个定律:当first为null时,last必为null,此时list为empty或null;当first.prev 为null时,说明至少有一个元素存在,first.item必不为空;当只存在一个元素时,它即是first又是last
    /**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*
* 指向最后一个节点的指针。
*
*/
transient Node<E> last;
  • 存储最后一个节点的Node实例,记住一个定律:当first为null时,last必为null,此时list为empty或null;当last.next 为null时,说明至少有一个元素存在,last.item必不为空;当只存在一个元素时,它即是first又是last

内部类Node

private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
  • 分成三部分 prev:存储前一位的Node,first的prev为null; item:存储元素;next:存储后一位的Node,last的next为null;

LinkedList构造器

    /**
* Constructs an empty list.
*
* 构造一个空列表。
*
*/
public LinkedList() {
}
    /**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
*
* 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
  • 相当于一个空LinkedList调用addAll()方法

核心方法

    /**
* Returns the (non-null) Node at the specified element index.
*
* 返回指定元素索引处的(非null)节点。
*
*/
Node<E> node(int index) {
// assert isElementIndex(index); if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
// 一个个赋值,目的是将第index个赋值
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
  • LinkedList并不支持随机访问,所以根据index来返回对应元素效率很低。 注意:size >> 1 相当于除以2,一分为二决定从头部开始遍历还是从尾部开始遍历,提高效率

/**
* Links e as first element.
*
* 将e作为第一个元素
*
*/
private void linkFirst(E e) {
final Node<E> f = first;
// 新建一个元素为e的Node实例,next指针指向first
final Node<E> newNode = new Node<>(null, e, f);
// 将元素为e的Node实例作为first
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
} /**
* Inserts the specified element at the beginning of this list.
*
* 在list的开头添加指定元素,就是调用上方法
*
* @param e the element to add
*/
public void addFirst(E e) {
// 直接调用如上方法
linkFirst(e);
}
  • 简单分步理解:第一步:创建元素为e的Node实例:newNode;第二步:newNode成为first,并且它的next结点指向旧first;第三步:若旧first不存在,newNode也当作last,若旧first存在,则旧first的prev结点要指向newNode;这属于结构修改,所以modCount++

/**
* Links e as last element.
*/
private void linkLast(E e) {
final Node<E> l = last;
// 新建一个元素为e的Node实例,pre指针指向first
final Node<E> newNode = new Node<>(l, e, null);
// 将元素为e的Node实例作为last
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
} /**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #add}.
* @param e the element to add
*/
public void addLast(E e) {
linkLast(e);
} /**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*/
public boolean add(E e) {
linkLast(e);
return true;
}
  • 简单理解:(同上,试着自己总结一下),linkLast(E e)被addLast,add方法所使用。
   /**
* Inserts element e before non-null Node succ.
*
* 在非null节点succ之前插入元素e。
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
} 此方法会被add(int index, E element)使用 public void add(int index, E element) {
checkPositionIndex(index); if (index == size)
linkLast(element);
else
linkBefore(element, node(index)); // 这里的node(index)就使用了一开始说的根据index返回对应的元素的方法
}
  • 简单分步理解:第一步:创建元素为e的Node实例:newNode,且将prev结点指向succ前一位node;第二步:succ的prev结点指向newNode;第三步:若succ是first,newNode就是first,若succ不是first,则succ的前结点的next要指向newNode;这属于结构修改,所以modCount++
   /**
* Unlinks non-null first node f.
*
* 删除非空的第一个节点f
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
  • f.item = null;f.next = null;置为空求助于GC回收。first赋值为其next
   /**
* Unlinks non-null last node l.
*
* 删除非空的最后节点f
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
  • 与unlinkFirst异曲同工
   /**
* Unlinks non-null node x.
*
* 删除指定的非空的节点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;
}
  • 逻辑不复杂,其中所有置空操作都是为了让GC回收,以上的方法(除了node(int index))都要modCount++,因为改变了结构

校验方法

    /**
* Tells if the argument is the index of an existing element.
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
} /**
* Tells if the argument is the index of a valid position for an
* iterator or an add operation.
*/
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
} /**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
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));
}

笔记:

  1. checkElementIndex方法调用isElementIndex方法,是检验此参数index是否是现有元素的索引。用于查,改,删操作的校验比如:get,set,remove方法调用
  2. checkPositionIndex方法调用isPositionIndex方法,是校验此参数index是否是迭代器或添加操作的有效位置的索引。用于add,addAll和迭代器相关方法调用

普通方法

大部分查改的方法都是内部调用的以上介绍的核心方法

    public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
} public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
} 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);
} public void addFirst(E e) {
linkFirst(e);
} public void addLast(E e) {
linkLast(e);
} public E get(int index) {
checkElementIndex(index);
return node(index).item;
} public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
} public void add(int index, E element) {
checkPositionIndex(index); if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
} public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

Queue operations. 以下为接口Queue的方法实现

    

    // 队列查询
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);
}

笔记:

  1. getFirst,peek,poll方法都是返回第一个元素,区别在于:frist元素为空时getFirst报异常,peek返回null,poll也返回null;frist元素不为空时,getFirst,peek仅仅返回first,poll返回之后会将first元素删除;getFirst,peek相当于查询,poll相当于取出(出队)。
  2. checkElementIndex(index),checkPositionIndex(index)的区别,上一标题的笔记
  3. set(int index, E element)有返回值,返回的是元素值(oldVal),这点注意。

Deque operations 以下为双向队列Deque的方法实现

这里列出重要的四种6种方法:

当作双向队列时的入队(头或尾),出队(头或尾)四个方法;

当作栈使用时的入栈(push)和出栈(pop)两个方法;


public boolean offerFirst(E e) {
addFirst(e);
return true;
} public boolean offerLast(E e) {
addLast(e);
return true;
} 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);
} /**
* Pushes an element onto the stack represented by this list. In other
* words, inserts the element at the front of this list.
*
* 将元素推送到此列表所表示的堆栈上。 换句话说,将元素插入此列表的前面。
*
* <p>This method is equivalent to {@link #addFirst}.
*/
public void push(E e) {
addFirst(e);
} /**
* 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()}.
*/
public E pop() {
return removeFirst();
}

笔记:以上通过方法的分析可以得出 队列,双向队列,栈的区别

  1. 回顾Queue队列的入队(offer)只能从尾部加入,也能出队(poll)只能从头部出去:先进先出
  2. Deque双向队列,支持在首尾两端插入(offerFirst,offerLast)和移除(pollFirst,pollLast)元素;
  3. 栈的特点是先进后出,后进先出;
    public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
} /**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* 从指定位置开始,将指定集合中的所有元素插入此列表。
* 将当前位置的元素(如果有)和任何后续元素向右移动(增加其索引)。
* 新元素将按照指定集合的迭代器返回的顺序出现在列表中。
*
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 将c转化成数组
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false; Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
} for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
} if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
} size += numNew;
modCount++;
return true;
}

笔记:addAll方法单独列出来,是因为它是有参构造器需要调用的方法;

  1. 将集合c转换成集合a
  2. 遍历a集合,将集合中的元素一一封装成节点newNode,利用pred变量,一一地接起来
  3. 是否是开始节点?是否是最后节点?这样细节问题根据具体条件进行操作

迭代器(iterator&ListIterator)实现

Iterator

    /**
* @since 1.6
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
} /**
* Adapter to provide descending iterators via ListItr.previous
*
* 通过ListItr.previous提供降序迭代器
*/
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();
}
}
  • Iterator返回的是DescendingIterator本质上就是ListItr,区别在于DescendingIterator的hasNext方法相当于ListItr的hasPrevious方法,next方法是ListItr的previous方法

ListIterator

    /**
* Returns a list-iterator of the elements in this list (in proper
* sequence), starting at the specified position in the list.
* Obeys the general contract of {@code List.listIterator(int)}.<p>
*
* 从列表中的指定位置开始,返回此列表中元素的列表迭代器(按正确顺序)。
*
* The list-iterator is <i>fail-fast</i>: if the list is structurally
* modified at any time after the Iterator is created, in any way except
* through the list-iterator's own {@code remove} or {@code add}
* methods, the list-iterator will throw a
* {@code ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
*/
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;
// 存储即将返回的元素的index
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++;
return 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;
else
nextIndex--;
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);
else
linkBefore(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();
}
}

笔记:

  1. ListItr是双向的,有next相关方法,也有previous相关方法
  2. index == size时,next为null,previous()方法返回的就是last节点的元素
  3. remove(),set(E e)方法必须要在next()或previous()之后执行,不然会报IllegalStateException();remove()/set(E e)方法移除/设置的元素就是其前面的next()或previous()返回的这个元素;
  4. add(E e)方法,会将lastReturned = null;
  5. checkForComodification()方法是校验是否存在并发修改的风险,存在则fast-fail

小言

  源码版本为JDK1.8,只是对日常使用的基本操作的源码进行了分析,对于1.8的新特性并没有涉及,等将主要集合类源码分析完后,以后会专门出一篇分析一下1.8中集合的新特性;

  有建议或着问题的,请在文末留言,本人水平有限,有错误或理解偏差,还请各位多多指导和见谅,如若转载,请表明出处;

LinkedList源码阅读笔记(1.8)的更多相关文章

  1. LinkedList源码阅读笔记

    LinkedList LinkedList是双向链表,不循环(1.6之前循环),继承AbstractSequentialList类,实现了List, Deque, Cloneable接口. 链表的特点 ...

  2. LinkedList源码阅读笔记(基于JDK1.8)

    LinkedList是List接口的一个有序链表实现,存储节点是内部类Node,Node中有两个属性prev和next,负责连接前后两个元素.由于不是使用数组进行存储,所以查询需要遍历链表一半的元素( ...

  3. CI框架源码阅读笔记5 基准测试 BenchMark.php

    上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...

  4. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...

  5. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  6. CI框架源码阅读笔记2 一切的入口 index.php

    上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...

  7. 源码阅读笔记 - 1 MSVC2015中的std::sort

    大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格 ...

  8. Three.js源码阅读笔记-5

    Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...

  9. PHP源码阅读笔记一(explode和implode函数分析)

    PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...

随机推荐

  1. Python3学习之路~9.1 paramiko模块:实现ssh执行命令以及传输文件

    我们一般使用linux的时候,都是在Windows上安装一个ssh客户端连接上去.那么从一台linux如何连接到另一条linux呢?使用ssh命令即可,因为每台linux机器自己都有一个ssh客户端. ...

  2. Java开发规范总结

     Service / DAO 层方法命名规约: 1 ) 获取单个对象的方法用 get 做前缀.2 ) 获取多个对象的方法用 list 做前缀.3 ) 获取统计值的方法用 count 做前缀.4 ) 插 ...

  3. Centos7 初始化硬盘分区、挂载

    1.通过命令fdisk-l查看硬盘信息 可以看到有两块硬盘/dev/vda和/dev/vdb,启动vda是系统盘vdb是我们新增的数据盘. 2.执行以下命令,进入fdisk模式,开始对新增数据盘执行分 ...

  4. No input file specified. phpStudy nginx报错解决方案

    1.首先观察路径是否存在, 2.在vhsos.conf文件中 先科普下: 在Windows系统中,正斜杠 / 表示除法,用来进行整除运算:反斜杠 \ 用来表示目录. 在Unix系统中,/ 表示目录:\ ...

  5. DDD领域驱动

    DDD领域驱动领域驱动模型.模型驱动代码接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离.DDD让你首先考虑的是业务语言而不 ...

  6. adobe air for ios 应用完成appstore评论

    1,跳转到App Store:NSString *str = [NSString stringWithFormat:@"http://itunes.apple.com/us/app/id%d ...

  7. 命令行下执行python找不包的解决方法

    首先我们来了解一下,为什么会出现这样的问题,以及python搜索包的机制是怎么样的 1.为什么会出现这样的问题? 包是向下搜索机制. 2.为什么ide中执行没有报找不到包的问题? python搜索机制 ...

  8. Firefox 安装 Adobe Flashplayer

    3. 安装Adobe Flash Player: Adobe Flash Player的安装比较容易,只要将对应的文档复制到正确的的位置即可,具体的操作 如下: (1) 将libflashplayer ...

  9. Unity XLua之协程

    如何使用xlua实现协程,示例代码如下: 转载请注明出处:https://www.cnblogs.com/jietian331/p/10735773.html local unpack = unpac ...

  10. vue-cli教程

    转:https://jspang.com/post/vue-cli2.html#toc-5ca