LinkedList 源码解析(JDK1.8)

news2024/11/15 23:37:32

目录

一. 前言

二. 常用方法

三. 源码解析

3.1. 属性和内部类

3.2. 构造函数

3.3. 添加元素

 3.4. 获取元素

3.5. 删除元素

3.6. 迭代器

3.6.1. 头到尾方向的迭代

3.6.2. 尾到头方向的迭代

3.6.3. add() 插入元素

3.6.4. remove() 移除元素


一. 前言

    LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作是一个顺序容器,又可以看作是一个队列(Queue),同时又可以看作一个栈(Stack)。这样看来,LinkedList简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用LinkedList,一方面是因为Java官方已经声明不建议使用Stack类,更遗憾的是,Java里根本没有一个叫做Queue的类(它是个接口名字)。关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。

特点:
1. 底层是一个双向链表结构:增删快,查询慢。
2. 包含大量操作首尾元素的方法。
3. 线程不安全。如果需要多个线程并发访问,可以先采用Collections.synchronizedList()方法对其进行包装。

LinkedList 继承了 AbstractSequentialList,而 AbstractSequentialList 又继承于 AbstractList 。详见 ArrayList 的源码,阅读后我们知道,ArrayList 同样继承了 AbstractList , 所以LinkedList会有大部分方法和 ArrayList 相似。

简单介绍一下家族成员:
List:表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
Deque:继承自 Queue 接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。
Cloneable:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
Serializable:表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
补充:
序列化是将对象转换成二进制流,持久化是将对象转换成介质数据(先转成二进制再持久化)。
序列化跟反序列化,发生在OSI的表示层。

二. 常用方法

方法描述
public E getFirst()返回此列表中的第一个元素。
public E getLast()返回此列表中的最后一个元素。
public E removeFirst()移除并返回此列表中的第一个元素。
public E removeLast()移除并返回此列表中的最后一个元素。
public void addFirst(E e)在此列表的开始处插入指定的元素。
public void addLast(E e)将指定的元素添加到列表的结束位置。
public boolean contains(Object o)返回 true如果这个列表包含指定元素。
public int size()返回此列表中元素的数目。
public boolean add(E e)将指定的元素添加到列表的结束位置。
public boolean remove(Object o)从该列表中移除指定元素的第一个。
public void clear()从此列表中移除所有的元素。
public E get(int index)返回此列表中指定位置的元素。
public E set(int index, E element)用指定元素替换此列表中指定位置的元素。
public void add(int index, E element)在列表中指定的位置上插入指定的元素。
public E remove(int index)移除此列表中指定位置的元素。所有后续元素左移(下标减1)。返回从列表中删除的元素。
public int indexOf(Object o)返回此列表中指定元素的第一个出现的索引,-1:如果此列表不包含元素。
public int lastIndexOf(Object o)返回此列表中指定元素的最后一个发生的索引,-1:如果此列表不包含元素。
public E peek()返回此列表的头部。
public E poll()返回并删除此列表的第一个元素。
public E remove()返回并删除此列表的第一个元素。
public boolean offer(E e)将指定的元素添加到列表的尾部(最后一个元素)。
public boolean offerFirst(E e)在列表的前面插入指定的元素。
public boolean offerLast(E e)在列表的结尾插入指定的元素。
public E peekFirst()返回列表的第一个元素。
public E peekLast()返回列表的最后一个元素。
public E pollFirst()删除并返回列表的第一个元素。
public E pollLast()删除并返回列表的最后一个元素。
public void push(E e)将一个元素推到由该列表所表示的堆栈上。换句话说,在这个列表的前面插入元素。相当于addFirst(E)。
public E pop()从该列表所表示的堆栈中弹出一个元素。换言之,移除并返回此列表的第一个元素。

三. 源码解析

3.1. 属性和内部类

/**
 * 集合大小
 * 当前有多少个节点
 */
transient int size = 0;

/**
 * 头部节点
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * 尾部节点
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;
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;
    }
}

3.2. 构造函数

LinkedList 中有一个无参构造函数和一个有参构造函数。

// 创建一个空的链表对象
public LinkedList() {
}

// 接收一个集合类型作为参数,会创建一个与传入集合相同元素的链表对象
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

3.3. 添加元素

LinkedList 除了实现了List接口相关方法,还实现了 Deque 接口的很多方法,所以我们有很多种方式插入元素。我们这里以List接口中相关的插入方法为例进行源码讲解,对应的是 add()、addAll() 方法。
add(E e):用于在 LinkedList 的尾部插入元素,即将新元素作为链表的最后一个元素,时间复杂度为 O(1)。
add(int index, E element):用于在指定位置插入元素。这种插入方式需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。
addAll(int index, Collection<? extends E> c):从指定位置开始,将指定集合中的所有元素插入此列表。当不是构造方法调用时,它会在其最后节点追加新的元素。
offer(E e):内部调用 add(E e) 方法。
offerFirst(E e):内部调用 addFirst(E e) 方法。
offerLast(E e):内部调用 addLast(E e) 方法。
push(E e):内部调用addFirst方法实现

// 在链表尾部插入元素
public boolean add(E e) {
    linkLast(e);
    return true;
}

// 在链表指定位置插入元素
public void add(int index, E element) {
    // 下标越界检查
    checkPositionIndex(index);

    // 判断 index 是不是链表尾部位置
    if (index == size)
        // 如果是就直接调用 linkLast 方法将元素节点插入链表尾部即可
        linkLast(element);
    else
        // 如果不是则调用 linkBefore 方法将其插入指定元素之前
        linkBefore(element, node(index));
}

// 将元素节点插入到链表尾部
void linkLast(E e) {
    // 将最后一个元素赋值(引用传递)给节点 l
    final Node<E> l = last;
    // 创建节点,并指定节点前驱为链表尾节点 last,后继引用为空
    final Node<E> newNode = new Node<>(l, e, null);
    // 将 last 引用指向新节点
    last = newNode;
    // 判断尾节点是否为空
    // 如果 l 是 null,意味着这是第一次添加元素
    if (l == null)
        // 如果是第一次添加,将first赋值为新节点,此时链表只有一个元素
        first = newNode;
    else
        // 如果不是第一次添加,将新节点赋值给l(添加前的最后一个元素)的next
        l.next = newNode;
    size++;
    modCount++;
}

// 在指定元素之前插入元素
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;断言 succ不为 null
    // 定义一个节点元素保存 succ 的 prev 引用,也就是它的前一节点信息
    final Node<E> pred = succ.prev;
    // 初始化节点,并指明前驱和后继节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 将 succ 节点前驱引用 prev 指向新节点
    succ.prev = newNode;
    // 判断尾节点是否为空,为空表示当前链表还没有节点
    if (pred == null)
        first = newNode;
    else
        // succ 节点前驱的后继引用指向新节点
        pred.next = newNode;
    size++;
    modCount++;
}
/**
 * 从指定位置开始,将指定集合中的所有元素插入此列表。
 * 将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加其索引)。
 * 新元素将按照指定集合的迭代器返回的顺序出现在列表中。
 *
 * @param 插入指定集合中第一个元素的索引
 * @param c 包含要添加到此列表的元素的集合
 * @return {@code true} 如果此列表因调用而更改,则为true
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(int index, Collection<? extends E> c) {
	// 检查index是否越界 判断是否是有效下标 此时index必须为0
	checkPositionIndex(index);

	Object[] a = c.toArray();
	// 数组length
	int numNew = a.length;
	if (numNew == 0)
		return false;
	// 创建两个双向链表节点 pred末游节点 succ首节点 初始值为null
	Node<E> pred, succ;
	if (index == size) {
	// 初始首节点为null
		succ = null;
		// 存储第一个node
		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)// 第一个节点时 他的上一个节点时null
			first = newNode; // 此时最后一个节点就是它本身
		else
			// 此时将上一个节点的下游节点链接就是当前生成的
			pred.next = newNode;
		// 记录当前末游节点
		pred = newNode; 
	}
	// 此时数据链接完
	if (succ == null) {
		// 将最后一个节点赋值给last
		last = pred;
		// 如果存在首届
	} else {
		// 将最后一个的下游节点链接为第一个节点
		pred.next = succ;
		// 将第一个的上游节点链接为末游节点
		succ.prev = pred;
	}
	//size=length+1
	size += numNew;
	modCount++;
	return true;
}
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 void push(E e) {
    addFirst(e);
}

 3.4. 获取元素

LinkedList获取元素相关的方法一共有 3 个:
getFirst():获取链表的第一个元素。
getLast():获取链表的最后一个元素。
get(int index):获取链表指定位置的元素。

// 获取链表的第一个元素
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 get(int index) {
  // 下标检查
  checkElementIndex(index);
  // 返回链表中对应下标的元素
  return node(index).item;
}

添加元素时,都返回了node(int index)方法,我们分析一下该方法:

// 返回指定下标的非空节点
Node<E> node(int index) {
    // 断言下标未越界
    // assert isElementIndex(index);

    // 如果index小于size的二分之一  从前开始查找(向后查找),反之向前查找
    if (index < (size >> 1)) {
        Node<E> x = first;
        // 遍历,循环向后查找,直至 i == index
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        //遍历,循环向前查找,直至 i == index
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

get(int index) 或 remove(int index) 等方法内部都调用了该方法来获取对应的节点。
不难看出,该方法通过比较索引值与链表 size 的一半大小来确定从链表头还是链表尾开始遍历。
1. 如果索引值小于 size 的一半,就从链表头开始遍历
2. 如果索引值大于 size 的一半,就从链表尾开始遍历。
这样可以在较短的时间内找到目标节点,充分利用了双向链表的特性来提高效率。

3.5. 删除元素

LinkedList 删除元素相关的方法一共有 5 个:
removeFirst() :删除并返回链表的第一个元素。
removeLast() :删除并返回链表的最后一个元素。
remove(E e) :删除链表中首次出现的指定元素,如果不存在该元素则返回 false。
remove(int index) :删除指定索引处的元素,并返回该元素的值。
void clear() :移除此链表中的所有元素。
E pop():调用removeFirst()。
E poll():判断first元素是否为空,不为空则调用unlinkFirst(E e)删除。
E pollFirst:判断first元素是否为空,不为空则调用unlinkFirst(E e)删除。
E pollLast:判断last元素是否为空,不为空则调用unlinkLast(E e)删除。

// 删除并返回链表的第一个元素
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);
}

// 删除链表中首次出现的指定元素,如果不存在该元素则返回 false
public boolean remove(Object o) {
    // 如果指定元素为 null,遍历链表找到第一个为 null 的元素进行删除
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        // 如果不为 null ,遍历链表找到要删除的节点
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

// 删除链表指定位置的元素
public E remove(int index) {
    // 下标越界检查,如果越界就抛异常
    checkElementIndex(index);
    return unlink(node(index));
}

我们可以看到,均调用了 unlink(Node<E> x),对该方法解析:

E unlink(Node<E> x) {
    // 断言 x 不为 null
    // 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 { // 如果前一个节点不为空
        // 将前一个节点的 next 指针指向当前节点的下一个节点
        prev.next = next;
        // 将当前节点的 prev 指针置为 null,方便 GC 回收
        x.prev = null;
    }

    // 如果下一个节点为空,则说明当前节点是尾节点
    if (next == null) {
        // 直接让链表尾指向当前节点的前一个节点
        last = prev;
    } else { // 如果下一个节点不为空
        // 将下一个节点的 prev 指针指向当前节点的前一个节点
        next.prev = prev;
        // 将当前节点的 next 指针置为 null,方便 GC 回收
        x.next = null;
    }

    // 将当前节点元素置为 null,方便 GC 回收
    x.item = null;
    size--;
    modCount++;
    return element;
}

unlink() 方法的逻辑如下:
1. 首先获取待删除节点 x 的前驱和后继节点;
2. 判断待删除节点是否为头节点或尾节点:
    1>. 如果 x 是头节点,则将 first 指向 x 的后继节点 next。
    2>. 如果 x 是尾节点,则将 last 指向 x 的前驱节点 prev。
    3>. 如果 x 既不是头节点也不是尾节点,执行下一步操作。
3. 将待删除节点 x 的前驱的后继指向待删除节点的后继 next,断开 x 和 x.prev 之间的链接;
4. 将待删除节点 x 的后继的前驱指向待删除节点的前驱 prev,断开 x 和 x.next 之间的链接;
5. 将待删除节点 x 的元素置空,修改链表长度。

public E pop() {
    return removeFirst();
}

public E poll() {
	// 判断first元素是否为空,不为空则调用unlinkFirst(E e)删除
	final Node<E> f = first;
	return (f == null) ? null : unlinkFirst(f);
}

public E pollFirst() {
	// 判断first元素是否为空,不为空则调用unlinkFirst(E e)删除
	final Node<E> f = first;
	return (f == null) ? null : unlinkFirst(f);
}

public E pollLast() {
	// 判断last元素是否为空,不为空则调用unlinkLast(E e)删除
	final Node<E> l = last;
	return (l == null) ? null : unlinkLast(l);
}

3.6. 迭代器

推荐使用 for-each 循环来遍历 LinkedList 中的元素,for-each 循环最终会转换成迭代器形式。

LinkedList 遍历的核心就是它的迭代器的实现。

// 双向迭代器
private class ListItr implements ListIterator<E> {
    // 表示上一次调用 next() 或 previous() 方法时经过的节点;
    private Node<E> lastReturned;
    // 表示下一个要遍历的节点;
    private Node<E> next;
    // 表示下一个要遍历的节点的下标,也就是当前节点的后继节点的下标;
    private int nextIndex;
    // 表示当前遍历期望的修改计数值,用于和 LinkedList 的 modCount 比较,判断链表是否被其他线程修改过。
    private int expectedModCount = modCount;
    …………
}

下面我们对迭代器 ListItr 中的核心方法进行详细介绍。

3.6.1. 头到尾方向的迭代

// 判断还有没有下一个节点
public boolean hasNext() {
    // 判断下一个节点的下标是否小于链表的大小,如果是则表示还有下一个元素可以遍历
    return nextIndex < size;
}
// 获取下一个节点
public E next() {
    // 检查在迭代过程中链表是否被修改过
    checkForComodification();
    // 判断是否还有下一个节点可以遍历,如果没有则抛出 NoSuchElementException 异常
    if (!hasNext())
        throw new NoSuchElementException();
    // 将 lastReturned 指向当前节点
    lastReturned = next;
    // 将 next 指向下一个节点
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

3.6.2. 尾到头方向的迭代

// 判断是否还有前一个节点
public boolean hasPrevious() {
    return nextIndex > 0;
}

// 获取前一个节点
public E previous() {
    // 检查是否在迭代过程中链表被修改
    checkForComodification();
    // 如果没有前一个节点,则抛出异常
    if (!hasPrevious())
        throw new NoSuchElementException();
    // 将 lastReturned 和 next 指针指向上一个节点
    lastReturned = next = (next == null) ? last : next.prev;
    nextIndex--;
    return lastReturned.item;
}

3.6.3. add() 插入元素

LinkedList<String> list = new LinkedList<>();
list.add("apple");
list.add(null);
list.add("banana");

//  Collection 接口的 removeIf 方法底层依然是基于迭代器
list.removeIf(Objects::isNull);

for (String fruit : list) {
    System.out.println(fruit);
}

3.6.4. remove() 移除元素

// 从列表中删除上次被返回的元素
public void remove() {
    // 检查是否在迭代过程中链表被修改
    checkForComodification();
    // 如果上次返回的节点为空,则抛出异常
    if (lastReturned == null)
        throw new IllegalStateException();

    // 获取当前节点的下一个节点
    Node<E> lastNext = lastReturned.next;
    // 从链表中删除上次返回的节点
    unlink(lastReturned);
    // 修改指针
    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;
    // 将上次返回的节点引用置为 null,方便 GC 回收
    lastReturned = null;
    expectedModCount++;
}

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

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

相关文章

ATA-8202射频功率放大器参数指标及应用介绍

ATA-8202射频功率放大器简介 ATA-8202是一款射频功率放大器。其P1dB输出功率100W&#xff0c;饱和输出功率200W。增益数控可调&#xff0c;一键保存设置&#xff0c;提供了方便简洁的操作选择&#xff0c;可与主流的信号发生器配套使用&#xff0c;实现射频信号的完美放大。宽…

JavaScript 中的Hoisting是什么?

在JavaScript中,Hoisting(变量提升)是指在代码执行之前,JavaScript引擎将变量和函数的声明提升到当前作用域的顶部的行为。 具体来说,JavaScript引擎在执行代码之前会进行两个步骤:编译阶段和执行阶段。在编译阶段,JavaScript引擎会将变量声明和函数声明提升到当前作用…

智能井盖监测系统,增加城市管理便捷性

看起来小小的井盖&#xff0c;实际上折射的是一个城市&#xff0c;一个地区的发展状况。每一个城市的道路网络&#xff0c;决定了一个城市发展的缓慢程度&#xff0c;哪怕是其中的井盖&#xff0c;有着自己的职责&#xff0c;是城市生命线的不可缺少的组成部分。但是现如今传统…

Qt10-19

第一个界面的头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QMovie>//动态图片所用的类 #include<QMessageBox>//消息对话框类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget …

国产主控应用案例:汉王电子血压计-君正开发板

2023春季新品发布会上汉王科技发布柯氏音法电子血压计产品—汉王电子血压计&#xff0c;继嗅觉检测盒之后再次深度布局大健康领域。 不同于当前市面上使用示波法原理的电子血压计&#xff0c;汉王电子血压计采用血压测量金标准中的柯氏音法&#xff0c;由此引领一场电子血压计领…

出东方利中国!长城五星牵手国家宝藏,共谱丝路艺术华章

执笔 | 尼 奥 编辑 | 古利特 当中国葡萄酒与国家宝藏、非遗工艺灵魂碰撞&#xff0c;又将产生怎样的大国之美&#xff1f;长城五星艺术干红给出答案。 10月19日&#xff0c;长城五星艺术干红上市发布会在成都召开。会上&#xff0c;长城葡萄酒正式发布文化战略新品——“五…

“达观杯”智能文档版面分析赛题baseline已上线,欢迎下载报名!

由国内领先智能文本处理企业达观数据与上海市计算机学会联合举办的智能文档版面分析多模态数据处理算法竞赛正在火热报名阶段。开赛一周内&#xff0c;第七届“达观杯”已有来自国内外的近200名企业和高校算法精英参与到比赛中。作为本次赛题的出题方&#xff0c;为助力各位打榜…

taro使用defineConstants定义全局变量eslint报错该变量不存在

问题描述 在taro项目中使用defineConstants定义一些全局变量供业务代码中进行使用&#xff0c;全局变量声明config/index.js代码如下&#xff1a; module.exports {defineConstants: {LOGIN_URL: JSON.stringify(/baidu/login), },全局变量使用代码如下&#xff1a; /*** 跳…

高速DSP系统设计参考指南(四)DSP电源设计

&#xff08;四&#xff09;DSP电源设计 1.电源设计的重要性2.DSP电源架构考虑3.电源去耦技术3.1 一般经验法则解耦法3.2 解耦分析方法3.3 分析案例3.4 计算去耦电容值3.5 高频噪声隔离 1.电源设计的重要性 电源设计可能是高速 DSP 设计中控制噪声和辐射的整个过程中最具挑战性…

Affinity Photofor Mac/Windows:专业级图片编辑的终极选择

在今天的内容中&#xff0c;我们将要介绍一款备受赞誉的图片编辑软件——Affinity Photo。无论你是业余的摄影爱好者&#xff0c;还是专业的设计师&#xff0c;Affinity Photo都能为你提供丰富的编辑功能和出色的使用体验。 首先&#xff0c;让我们来简单了解一下Affinity Pho…

哪些重生奇迹mu地图适合刷玛雅宝石?

作为一款非常经典的打斗游戏&#xff0c;重生奇迹mu设计非常复杂&#xff0c;游戏玩法以及地图设计非常完善&#xff0c;而且游戏推出了最新版本&#xff0c;各方面表现更加的优秀&#xff0c;值得游戏玩家去尝试。 重生奇迹mu游戏拥有非常完善的商城系统&#xff0c;其中最重…

金融机器学习方法:决策树与随机森林

目录 1.决策树 1.1 什么是决策树&#xff1f; 1.2 决策树的优点与缺点 2.随机森林 2.1 什么是随机森林&#xff1f; 2.2 随机森林的优点与缺点 3.决策树与随机森林的联系与区别 4.案例分析 4.1 使用决策树和随机森林预测 4.2绘制ROC曲线 决策树和随机森林都是监督学…

聊聊Android线程优化这件事

一、背景 在日常开发APP的过程中&#xff0c;难免需要使用第二方库和第三方库来帮助开发者快速实现一些功能&#xff0c;提高开发效率。但是&#xff0c;这些库也可能会给线程带来一定的压力&#xff0c;主要表现在以下几个方面&#xff1a; 线程数量增多&#xff1a;一些库可…

uniapp——自定义组件插槽及使用

案例样式 自定义组件pageBox.vue <template><view><view class"bgColor" :style"{ height: bgHeight rpx }"></view><view class"main"><!-- 主要内容放这里 --><slot></slot></view>&…

虚拟/物理机备份中深度有效数据提取应用原理

当前针对虚拟机的磁盘映像备份的通用技术&#xff0c;主要通过识别虚拟机磁盘文件的元数据&#xff0c;或是借助虚拟化API&#xff08;如CBT接口&#xff09;或分区结构提取位图来进行。然而&#xff0c;由于虚拟化环境底层的限制&#xff0c;无法洞悉文件系统的内部运作&#…

idea中java类属性(字段)链式赋值

很多人看到标题可能会想到 lombok 的 Builder&#xff0c;lombok 在国内用的挺多的&#xff0c;开源的组件中 mybatis-plus 中用到了这个&#xff0c;使用这个有一个问题就是通过对应 get 和 set 方法找不到对应的赋值方法&#xff0c;因为 lombok 使用了 apt 在编译期生成了相…

超全整理,服务端性能测试-tomcat部署项目/查看日志(细致)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 tomcat部署单项目…

vue3+vite中使用Lottie动画

Lottie通过读取json文件信息实现动画效果 官方文档 Lottie官网 lottie库有众多动画 选择下载Lottie JSON到项目中 安装Lottie包 pnpm add lottie-web 模板创建 <template><div class"bg"><div id"canvas" class"canvas" ref&quo…

【Spring篇】详解AOP相关知识

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;AOP简介&#x1f33a;AOP作用&#x1f33a;AOP核心概…

【完美世界】石昊挑逗云曦,斩杀神级猿魔,吃血魂草开新挂,团灭战族追兵

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析完美世界资讯。 《完美世界》第133集更新&#xff0c;画质升级&#xff0c;渲染精美。石昊在剧中击败神级猿魔和战族追兵&#xff0c;并开发出新外挂。 石昊发现猿魔通过吃血魂草和金菩果增强实力&#xff0c;通过吃这些草…