JDK源码解析-LinkedList

news2024/11/26 11:57:45

1. LinkedList类

1.1 LinkedList类定义&数据结构

定义

LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。

数据结构

基础知识补充

  • 单向链表:

    element:用来存放元素

    next:用来指向下一个节点元素

    通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

'xxs'

  • 单向循环链表

    element、next 跟前面一样

    在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环

    'xxs'

  • 双向链表

    element:存放元素

    pre:用来指向前一个元素

    next:指向后一个元素

    双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

'xxs'

  • 双向循环链表

    element、pre、next 跟前面的一样

    第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。

    'xxs'

  • LinkedList的数据结构

    'xxs'

如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。

特性

Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).

linkedList是一个双向链表,并且实现了List和Deque接口中所有的列表操作,并且能存储任何元素,包括null,这里我们可以知道linkedList除了可以当链表使用,还可以当作队列使用,并能进行相应的操作。

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.

这个告诉我们,linkedList在执行任何操作的时候,都必须先遍历此列表来靠近通过index查找我们所需要的的值。

1.2 LinkedList 类结构图

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

'xxs'

和 ArrayList 集合一样,LinkedList 集合也实现了Cloneable接口和Serializable接口,分别用来支持克隆以及支持序列化。List 接口也不用多说,定义了一套 List 集合类型的方法规范。

注意,相对于 ArrayList 集合,LinkedList 集合多实现了一个 Deque 接口,这是一个双向队列接口,双向队列就是两端都可以进行增加和删除操作。

1.3 字段属性

//链表元素(节点)的个数
transient int size = 0;

/**
 *指向第一个节点的指针
 */
transient Node<E> first;

/**
 *指向最后一个节点的指针
 */
transient Node<E> last;

'xxs'

注意这里出现了一个 Node 类,这是 LinkedList 类中的一个内部类,其中每一个元素就代表一个 Node 类对象,LinkedList 集合就是由许多个 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;
    }
}

'xxs'

上图的 LinkedList 是有四个元素,也就是由 4 个 Node 对象组成,size=4,head 指向第一个elementA,tail指向最后一个节点elementD。

1.4 类构造器

public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList 有两个构造函数,第一个是默认的空的构造函数,第二个是将已有元素的集合Collection 的实例添加到 LinkedList 中,调用的是 addAll() 方法

**注意:**LinkedList 是没有初始化链表大小的构造函数,因为链表不像数组,一个定义好的数组是必须要有确定的大小,然后去分配内存空间,而链表不一样,它没有确定的大小,通过指针的移动来指向下一个内存地址的分配。

1.5 添加元素

① addFirst(E e)

将指定元素添加到链表头

//将指定的元素附加到链表头节点
public void addFirst(E e) {
    linkFirst(e);
}
private void linkFirst(E e) {
    final Node<E> f = first;//将头节点赋值给 f
    final Node<E> newNode = new Node<>(null, e, f);//将指定元素构造成一个新节点,此节点的指向下一个节点的引用为头节点
    first = newNode;//将新节点设为头节点,那么原先的头节点 f 变为第二个节点
    if (f == null)//如果第二个节点为空,也就是原先链表是空
        last = newNode;//将这个新节点也设为尾节点(前面已经设为头节点了)
    else
        f.prev = newNode;//将原先的头节点的上一个节点指向新节点
    size++;//节点数加1
    modCount++;//和ArrayList中一样,iterator和listIterator方法返回的迭代器和列表迭代器实现使用。
}

②addLast(E e)和add(E e)

将指定元素添加到链表尾

源码:

//将元素添加到链表末尾
public void addLast(E e) {
    linkLast(e);
}
//将元素添加到链表末尾
public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;//将l设为尾节点
    final Node<E> newNode = new Node<>(l, e, null);//构造一个新节点,节点上一个节点引用指向尾节点l
    last = newNode;//将尾节点设为创建的新节点
    if (l == null)//如果尾节点为空,表示原先链表为空
        first = newNode;//将头节点设为新创建的节点(尾节点也是新创建的节点)
    else
        l.next = newNode;//将原来尾节点下一个节点的引用指向新节点
    size++;//节点数加1
    modCount++;//和ArrayList中一样,iterator和listIterator方法返回的迭代器和列表迭代器实现使用。
}

③ add(int index, E element)

将指定的元素插入此列表中的指定位置

//将指定的元素插入此列表中的指定位置
public void add(int index, E element) {
    //判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
    checkPositionIndex(index);

    if (index == size)//如果索引值等于链表大小
        linkLast(element);//将节点插入到尾节点
    else
        linkBefore(element, node(index));
}

void linkLast(E e) {
    final Node<E> l = last;//将l设为尾节点
    final Node<E> newNode = new Node<>(l, e, null);//构造一个新节点,节点上一个节点引用指向尾节点l
    last = newNode;//将尾节点设为创建的新节点
    if (l == null)//如果尾节点为空,表示原先链表为空
        first = newNode;//将头节点设为新创建的节点(尾节点也是新创建的节点)
    else
        l.next = newNode;//将原来尾节点下一个节点的引用指向新节点
    size++;//节点数加1
    modCount++;//和ArrayList中一样,iterator和listIterator方法返回的迭代器和列表迭代器实现使用。
}

Node<E> node(int index) {
    if (index < (size >> 1)) {//如果插入的索引在前半部分
        Node<E> x = first;//设x为头节点
        for (int i = 0; i < index; i++)//从开始节点到插入节点索引之间的所有节点向后移动一位
            x = x.next;
        return x;
    } else {//如果插入节点位置在后半部分
        Node<E> x = last;//将x设为最后一个节点
        for (int i = size - 1; i > index; i--)//从最后节点到插入节点的索引位置之间的所有节点向前移动一位
            x = x.prev;
        return x;
    }
}

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;//将pred设为插入节点的上一个节点
    final Node<E> newNode = new Node<>(pred, e, succ);//将新节点的上引用设为pred,下引用设为succ
    succ.prev = newNode;//succ的上一个节点的引用设为新节点
    if (pred == null)//如果插入节点的上一个节点引用为空
        first = newNode;//新节点就是头节点
    else
        pred.next = newNode;//插入节点的下一个节点引用设为新节点
    size++;
    modCount++;
}

④ addAll(Collection<? extends E> c)

按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾

addAll有两个重载函数,addAll(Collection<? extends E>)型和addAll(int, Collection<? extends E>)型,我们平时习惯调用的addAll(Collection<? extends E>)型会转化为addAll(int, Collection<? extends E>)型

(1)addAll©;

//按照指定集合的••迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
//真正核心的地方就是这里了,记得我们传过来的是size,c
public boolean addAll(int index, Collection<? extends E> c) {
    //检查index这个是否为合理。这个很简单,自己点进去看下就明白了。
    checkPositionIndex(index);
    //将集合c转换为Object数组 a
    Object[] a = c.toArray();
    //数组a的长度numNew,也就是由多少个元素
    int numNew = a.length;
    if (numNew == 0)
        //集合c是个空的,直接返回false,什么也不做。
        return false;
    //集合c是非空的,定义两个节点(内部类),每个节点都有三个属性,item、next、prev。
    Node<E> pred, succ;
    //构造方法中传过来的就是index==size
    if (index == size) {
//linkedList中三个属性:size、first、last。 size:链表中的元素个数。 first:头节点  last:尾节点,就两种情况能进来这里

//情况一、:构造方法创建的一个空的链表,那么size=0,last、和first都为null。linkedList中是空的。什么节点都没有。succ=null、pred=last=null

//情况二、:链表中有节点,size就不是为0,first和last都分别指向第一个节点,和最后一个节点,在最后一个节点之后追加元素,就得记录一下最后一个节点是什么,所以把last保存到pred临时节点中。
        succ = null;
        pred = last;
    } else {
//情况三、index!=size,说明不是前面两种情况,而是在链表中间插入元素,那么就得知道index上的节点是谁,保存到succ临时节点中,然后将succ的前一个节点保存到pred中,这样保存了这两个节点,就能够准确的插入节点了
//举个简单的例子,有2个位置,1、2、如果想插数据到第二个位置,双向链表中,就需要知道第一个位置是谁,原位置也就是第二个位置上是谁,然后才能将自己插到第二个位置上。如果这里还不明白,先看一下开头对于各种链表的删除,add操作是怎么实现的。
        succ = node(index);
        pred = succ.prev;
    }
//前面的准备工作做完了,将遍历数组a中的元素,封装为一个个节点。
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
//pred就是之前所构建好的,可能为null、也可能不为null,为null的话就是属于情况一、不为null则可能是情况二、或者情况三
        Node<E> newNode = new Node<>(pred, e, null);
//如果pred==null,说明是情况一,构造方法,是刚创建的一个空链表,此时的newNode就当作第一个节点,所以把newNode给first头节点
        if (pred == null)
            first = newNode;
        else
//如果pred!=null,说明可能是情况2或者情况3,如果是情况2,pred就是last,那么在最后一个节点之后追加到newNode,如果是情况3,在中间插入,pred为原index节点之前的一个节点,将它的next指向插入的节点,也是对的
            pred.next = newNode;
//然后将pred换成newNode,注意,这个不在else之中,请看清楚了。
        pred = newNode;
    }
    if (succ == null) {
//如果succ==null,说明是情况一或者情况二,
//情况一、构造方法,也就是刚创建的一个空链表,pred已经是newNode了,last=newNode,所以linkedList的first、last都指向第一个节点。
//情况二、在最后节后之后追加节点,那么原先的last就应该指向现在的最后一个节点了,就是newNode。
        last = pred;
    } else {
//如果succ!=null,说明可能是情况三、在中间插入节点,举例说明这几个参数的意义,有1、2两个节点,现在想在第二个位置插入节点newNode,根据前面的代码,pred=newNode,succ=2,并且1.next=newNode,1已经构建好了,pred.next=succ,相当于在newNode.next = 2; succ.prev = pred,相当于 2.prev = newNode, 这样一来,这种指向关系就完成了。first和last不用变,因为头节点和尾节点没变
        pred.next = succ;
//。。
        succ.prev = pred;
    }
//增加了几个元素,就把 size = size +numNew 就可以了
    size += numNew;
    modCount++;
    return true;
}

说明:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插入。

在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回,具体代码如下:

Node<E> node(int index) {
   // 判断插入的位置在链表前半段或者是后半段
   if (index < (size >> 1)) { // 插入位置在前半段
       Node<E> x = first; 
       for (int i = 0; i < index; i++) // 从头结点开始正向遍历
           x = x.next;
       return x; // 返回该结点
   } else { // 插入位置在后半段
       Node<E> x = last; 
       for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
           x = x.prev;
       return x; // 返回该结点
   }
}

说明:在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。

1.6 修改元素

通过调用 set(int index, E element) 方法,用指定的元素替换此列表中指定位置的元素。

public E set(int index, E element) {
	//判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
	checkElementIndex(index);
	Node<E> x = node(index);//获取指定索引处的元素
	E oldVal = x.item;
	x.item = element;//将指定位置的元素替换成要修改的元素
	return oldVal;//返回指定索引位置原来的元素
}

这里主要是通过 node(index) 方法获取指定索引位置的节点,然后修改此节点位置的元素即可。

1.7 查找元素

① getFirst()

返回此列表中的第一个元素

public E getFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return f.item;
}

② getLast()

返回此列表中的最后一个元素

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

③、get(int index)

返回指定索引处的元素

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
/**
 * Returns the (non-null) Node at the specified element index.
 */
//这里查询使用的是先从中间分一半查找
Node<E> node(int index) {
    // assert isElementIndex(index);
	//"<<":*2的几次方 “>>”:/2的几次方,例如:size<<1:size*2的1次方,
	//这个if中就是查询前半部分
     if (index < (size >> 1)) {//index<size/2
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {//前半部分没找到,所以找后半部分
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

④、indexOf(Object o)

返回此列表中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1。

//返回此列表中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1。
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {//如果查找的元素为null(LinkedList可以允许null值)
        for (Node<E> x = first; x != null; x = x.next) {//从头结点开始不断向下一个节点进行遍历
            if (x.item == null)
                return index;
            index++;
        }
    } else {//如果查找的元素不为null
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;//找不到返回-1
}

1.8 删除元素

删除元素和添加元素一样,也是通过更改指向上一个节点和指向下一个节点的引用即可

①、remove()和removeFirst()

从此列表中移除并返回第一个元素

//从此列表中移除并返回第一个元素
public E remove() {
    return removeFirst();
}
//从此列表中移除并返回第一个元素
public E removeFirst() {
    final Node<E> f = first;//f设为头结点
    if (f == null)
        throw new NoSuchElementException();//如果头结点为空,则抛出异常
    return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;//next 为头结点的下一个节点
    f.item = null;
    f.next = null; // 将节点的元素以及引用都设为 null,便于垃圾回收
    first = next; //修改头结点为第二个节点
    if (next == null)//如果第二个节点为空(当前链表只存在第一个元素)
        last = null;//那么尾节点也置为 null
    else
        next.prev = null;//如果第二个节点不为空,那么将第二个节点的上一个引用置为 null
    size--;
    modCount++;
    return element;
}

②、removeLast()

从该列表中删除并返回最后一个元素

//从该列表中删除并返回最后一个元素
public E removeLast() {
    final Node<E> l = last;
    if (l == null)//如果尾节点为空,表示当前集合为空,抛出异常
        throw new NoSuchElementException();
    return unlinkLast(l);
}

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; //将节点的元素以及引用都设为 null,便于垃圾回收
    last = prev;//尾节点为倒数第二个节点
    if (prev == null)//如果倒数第二个节点为null
        first = null;//那么将节点也置为 null
    else
        prev.next = null;//如果倒数第二个节点不为空,那么将倒数第二个节点的下一个引用置为 null
    size--;
    modCount++;
    return element;
}

③、remove(int index)

删除此列表中指定位置的元素

//删除此列表中指定位置的元素
public E remove(int index) {
    //判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
    checkElementIndex(index);
    return unlink(node(index));
}
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) {//如果删除节点位置的上一个节点引用为null(表示删除第一个元素)
        first = next;//将头结点置为第一个元素的下一个节点
    } else {//如果删除节点位置的上一个节点引用不为null
        prev.next = next;//将删除节点的上一个节点的下一个节点引用指向删除节点的下一个节点(去掉删除节点)
        x.prev = null;//删除节点的上一个节点引用置为null
    }

    if (next == null) {//如果删除节点的下一个节点引用为null(表示删除最后一个节点)
        last = prev;//将尾节点置为删除节点的上一个节点
    } else {//不是删除尾节点
        next.prev = prev;//将删除节点的下一个节点的上一个节点的引用指向删除节点的上一个节点
        x.next = null;//将删除节点的下一个节点引用置为null
    }

    x.item = null;//删除节点内容置为null,便于垃圾回收
    size--;
    modCount++;
    return element;
}

1.9 遍历集合

①、普通 for 循环

LinkedList<String> linkedList = new LinkedList<>();
    linkedList.add("A");
    linkedList.add("B");
    linkedList.add("C");
    linkedList.add("D");

for(int i = 0 ; i < linkedList.size() ; i++){
    System.out.print(linkedList.get(i)+" ");//A B C D
}

分析:

利用 LinkedList 的 get(int index) 方法,遍历出所有的元素。

但是需要注意的是, get(int index) 方法每次都要遍历该索引之前的所有元素

②、迭代器

在LinkedList中除了有一个Node的内部类外,应该还能看到另外两个内部类,那就是ListItr,还有一个是DescendingIterator。

ListItr内部类

private class ListItr implements ListIterator<E> 

看一下他的继承结构,发现只继承了一个ListIterator,到ListIterator中一看:

'xxs'

看到方法名之后,就发现不止有向后迭代的方法,还有向前迭代的方法,所以我们就知道了这个ListItr这个内部类干嘛用的了,就是能让linkedList不光能像后迭代,也能向前迭代。

看一下ListItr中的方法,可以发现,在迭代的过程中,还能移除、修改、添加值得操作。

'xxs'

DescendingIterator内部类

/**
 * Adapter to provide descending iterators via ListItr.previous
   看一下这个类,还是调用的ListItr,作用是封装一下Itr中几个方法,让使用者以正常的思维去写代码,例如,在从后往前遍历的时候,也是跟从前往后遍历一样,使用next等操作,而不用使用特殊的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();
    }
}
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");

Iterator<String> listIt = linkedList.listIterator();
while(listIt.hasNext()){
    System.out.print(listIt.next()+" ");//A B C D
}

//通过适配器模式实现的接口,作用是倒叙打印链表
Iterator<String> it = linkedList.descendingIterator();
while(it.hasNext()){
    System.out.print(it.next()+" ");//D C B A
}

迭代器和for循环效率差异

LinkedList<Integer> linkedList = new LinkedList<>();
for(int i = 0 ; i < 10000 ; i++){//向链表中添加一万个元素
    linkedList.add(i);
}
long beginTimeFor = System.currentTimeMillis();
for(int i = 0 ; i < 10000 ; i++){
    System.out.print(linkedList.get(i));
}
long endTimeFor = System.currentTimeMillis();
System.out.println("使用普通for循环遍历10000个元素需要的时间:"+ (endTimeFor - beginTimeFor));

long beginTimeIte = System.currentTimeMillis();
Iterator<Integer> it = linkedList.listIterator();
while(it.hasNext()){
    System.out.print(it.next()+" ");
}
long endTimeIte = System.currentTimeMillis();
System.out.println("使用迭代器遍历10000个元素需要的时间:"+ (endTimeIte - beginTimeIte));

'xxs'

'xxs'

一万个元素两者之间都相差一倍多的时间,如果是十万,百万个元素,那么两者之间相差的速度会越来越大

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/959183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode Top 100 Liked Questions(序号141~189)

​ 141. Linked List Cycle ​ 题意&#xff1a;给你一个链表&#xff0c;判断链表有没有环 我的思路 两个指针&#xff0c;一个每次走两步&#xff0c;一个每次走一步&#xff0c;如果走两步的那个走到了NULL&#xff0c;那说明没有环&#xff0c;如果两个指针指向相等&…

取一个整数各偶数位上的数构成一个新的数字

1 题目 我可太难了&#xff0c;这题我的思路有点复杂&#xff0c;遇到的困难很多&#xff0c;总是值传递搞不清楚&#xff0c;地址传递总是写错。 从低位开始取出一个整数s的各奇数位上的数&#xff0c;剩下的偶数位的数依次构成一个新数t。 例如&#xff1a; 输入s&#xff…

VB:水仙花数问题

VB&#xff1a;水仙花数问题 Private Sub Command1_Click()Rem 水仙花数问题Dim x%, a%, b%, c%, z%n 0For x 100 To 999a Fix(x / 100) Fix函数是去尾的作用&#xff0c;只保留整数部分&#xff0c;当然也可以直接用整除(\)b Fix((x - a * 100) / 10)c x Mod 10z a ^ 3…

C语言中的分支和循环语句:从入门到精通

分支和循环语句 1. 前言2. 预备知识2.1 getchar函数2.2 putchar函数2.3 计算数组的元素个数2.4 清屏2.5 程序的暂停2.6 字符串的比较 3. 结构化3.1 顺序结构3.2 分支结构3.3 循环结构 4. 真假性5. 分支语句&#xff08;选择结构&#xff09;5.1 if语句5.1.1 语法形式5.1.2 else…

人气总冠军-商艺馨 | 第11季中国好猫步辽宁总决赛

第十一季中国好猫步 辽宁总决赛 中国好猫步少儿模特赛事活动属于CCAC大满贯赛事中的大师赛&#xff0c;一直以来&#xff0c;以华丽的舞美、创意丰富的赛制、贴心的服务、丰厚的奖励和众多媒体曝光优势&#xff0c;成为无数少儿模特梦寐以求登上的舞台&#xff01;并且多次登上…

CG MAGIC进行实体渲染后!分析渲染器CR和VR的区别之处!

新手小白来说&#xff0c;如何选择渲染器&#xff0c;都会提出疑问&#xff1f; 渲染效果图究竟用CR渲染器还是VR渲染器呢&#xff1f; 今天&#xff0c;CG MAGIC小编通过一个真实的项目场景&#xff0c;实例渲染之后&#xff0c;CR渲染器和VR渲染器区别有哪几点&#xff1f; 1…

Java里面单向链表实现

Java里面单向链表实现 说明代码 说明 这里记录下单向链表实现。并在类里面写了一些新增和删除方法。 代码 package com.example.test;//单向链表类 public class Node {//结点的值public int val;//当前结点的后继结点,当 next null 时&#xff0c;代表这个结点是所在链表的…

VB:求1000以内的质数

VB&#xff1a;求1000以内的质数 Private Sub Command1_Click() Dim m%, i%, p%, k%, n% For m 2 To 1000 求1000以内的质数&#xff0c;2是最小的质数p 1k Int(Sqr(m))For i 2 To kIf m Mod i 0 Thenp 0Exit ForEnd IfNext iIf p 1 ThenPrint Tab((n Mod 10) * 5 2);…

20. python从入门到精通——Flask框架

目录 安装虚拟环境和Flask 第一个Flask程序 Flask的调试模式 路由 变量规则&#xff1a;当在页面中输出变量的时候就需要遵循变量的规则 构造URL 在route函数中设置http方法 获取静态文件路径 蓝图 模板 Web表单 CSRF 安装虚拟环境和Flask Flask框架主要依赖两个库…

Java“牵手”1688图片识别商品接口数据,图片地址识别商品接口,图片识别相似商品接口,1688API申请指南

1688商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要通过图片地址识别获取1688商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问1688商城的网页来获取商品详情信息。以下是两种常…

layui实现数据列表的复选框回显

layui版本2.8以上 实现效果如图&#xff1a; <input type"hidden" name"id" id"id" value"{:g_val( id,0)}"> <div id"tableDiv"><table class"layui-hide" id"table_list" lay-filter…

CPSC上月召回案例(2023年7月)

每年的夏末秋初为美国产品热销节日&#xff08;感恩节、万圣节、黑五&#xff09;的备货期&#xff0c;卖家在大量备货的同时&#xff0c;务必保障自身产品通过相关安全测试&#xff0c;以免造成不必要的损失&#xff01; 以下是五祥列举出的上月被CPSC召回产品&#xff0c;作…

技术分享 | LSM,Linux 内核的安全防护盾

计算机安全是一个非常重要的概念和主题&#xff0c;它不仅仅可以保护用户个人信息和资产的安全&#xff0c;还可以影响到用户在使用过程中的体验&#xff1b;但同时&#xff0c;它也是一个很抽象的概念&#xff0c;关于其相关文献和资料不计其数&#xff0c;但它究竟是什么、包…

相机成像原理【二】

文章目录 1、小孔成像的缺陷1.1 引入透镜 2、薄透镜成像原理2.1 薄透镜工作原理2.2 光线穿过透镜如何前进2.3 光线追踪 3、薄透镜成像公式3.1 高斯成像公式3.2 物距、像距、放大率之间特殊的关系 4、透镜成像特性4.1 对焦4.2 景深 1、小孔成像的缺陷 小孔尺寸过小&#xff0c;图…

iBooker 技术评论 20230831

一、轻资产项目的五类分类 轻资产项目不需要投资&#xff0c;但也不是所有人都做得了&#xff0c;取决于个人认知和能力水平限制。 就好比以前的各科题目&#xff0c;你也不是都能做吧&#xff1f; 我以前刷题的时候&#xff0c;喜欢把题目按照难易程度分五类。现在做项目和…

【python爬虫】11.让爬虫按时向你汇报

文章目录 前言定时与邮件明确目标分析过程爬虫发送邮件定时 代码组装复习 前言 上一关我们学习了selenium&#xff0c;它有可视模式与静默模式这两种浏览器的设置方法&#xff0c;二者各有优势。 然后学习了使用.get(‘URL’)获取数据&#xff0c;以及解析与提取数据的方法。…

【校招VIP】java语言考点之关键字static

考点介绍&#xff1a; static考点是面试的高频考点&#xff0c;一般从容易到难提问&#xff0c;比如从static的含义和理解、到JVM的存储或者到线程安全性&#xff0c;再到单例模式等。 java语言考点之关键字static 相关题目及解析内容可点击文章末尾链接查看&#xff01; 一…

Leetcode1090. 受标签影响的最大值

思路&#xff1a;根据值从大到小排序&#xff0c;然后在加的时候判断是否达到标签上限即可&#xff0c;一开始想用字典做&#xff0c;但是题目说是集合却连续出现两个8&#xff0c;因此使用元组SortedList进行解决 class Solution:def largestValsFromLabels(self, values: li…

Windows docker desktop 基于HyperV的镜像文件迁移到D盘

Docker desktop的HyperV镜像文件&#xff0c;默认是在C盘下 C:\ProgramData\DockerDesktop\vm-data\DockerDesktop.vhdx如果部署的软件较多&#xff0c;文件较大&#xff0c;或者产生日志&#xff0c;甚至数据等&#xff0c;这将会使此文件越来越大&#xff0c;容易导致C盘空间…