LinkedList 源码分析

1. 迭代器Iterator

在 Java 中,Iterator(迭代器)是一个用于遍历集合元素的接口。它提供了一种统一的方式来访问集合中的元素,而不需要了解集合的内部实现细节。

一、主要方法

  1. hasNext():判断是否还有下一个元素可遍历。
    • 示例:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    // 有下一个元素时执行这里的代码
}
  1. next():返回下一个元素。
    • 示例:
while (iterator.hasNext()) {
    String element = iterator.next();
    System.out.println(element);
}
  1. remove():移除当前由next()方法返回的元素。这个方法只能在调用next()方法之后调用一次。
    • 示例:
while (iterator.hasNext()) {
    String element = iterator.next();
    if (someCondition(element)) {
        iterator.remove();
    }
}

二、使用场景

  1. 遍历集合:
    • 可以方便地遍历各种集合类型,如ArrayListLinkedListHashSet等。
    • 使得遍历代码更加通用,不依赖于特定集合的实现细节。
  1. 动态操作集合:
    • 在遍历过程中可以使用remove()方法安全地删除元素,而不会导致ConcurrentModificationException异常。

三、与增强 for 循环的比较

增强 for 循环(也称为“foreach”循环)在语法上更加简洁,但它不能像Iterator那样在遍历过程中删除元素。

增强 for 循环示例:

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

四、实现原理

不同的集合类对Iterator的实现方式不同,但总体来说,Iterator通过维护一个指向集合内部元素的指针来实现遍历。每次调用next()方法时,指针向前移动一位并返回当前位置的元素。hasNext()方法通过检查指针是否到达集合末尾来判断是否还有下一个元素。

例如,对于ArrayList,它的iterator()方法返回一个内部类Itr的实例,这个内部类实现了Iterator接口,并通过维护一个cursor变量来记录当前遍历的位置。

五、注意事项

  1. 在使用Iterator遍历集合时,不要在遍历过程中通过集合自身的方法修改集合的结构(如添加或删除元素),除非使用Iteratorremove()方法。否则可能会导致不可预测的结果或抛出ConcurrentModificationException异常。
  2. 在调用remove()方法之前,必须先调用next()方法,否则会抛出IllegalStateException异常。

2. 源码分析

2.1. LinkedList 简介

LinkedList 是一个基于双向链表实现的集合类,经常被拿来和 ArrayList 做比较。关于 LinkedListArrayList的详细对比,我们 Java 集合常见面试题总结(上)有详细介绍到。

双向链表

不过,我们在项目中一般是不会使用到 LinkedList 的,需要用到 LinkedList 的场景几乎都可以使用 ArrayList 来代替,并且,性能通常会更好!就连 LinkedList 的作者约书亚 · 布洛克(Josh Bloch)自己都说从来不会使用 LinkedList

另外,不要下意识地认为 LinkedList 作为链表就最适合元素增删的场景。我在上面也说了,LinkedList 仅仅在头尾插入或者删除元素的时候时间复杂度近似 O(1),其他情况增删元素的平均时间复杂度都是 O(n) 。

2.1.1. LinkedList 插入和删除元素的时间复杂度?

  • 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
  • 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
  • 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,不过由于有头尾指针,可以从较近的指针出发,因此需要遍历平均 n/4 个元素,时间复杂度为 O(n)。

2.1.2. LinkedList 为什么不能实现 RandomAccess 接口?

RandomAccess 是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引快速访问元素)。由于 LinkedList 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess 接口。

2.2. LinkedList 源码分析

这里以 JDK1.8 为例,分析一下 LinkedList 的底层核心源码。

LinkedList 的类定义如下:

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

LinkedList 继承了 AbstractSequentialList ,而 AbstractSequentialList 又继承于 AbstractList

阅读过 ArrayList 的源码我们就知道,ArrayList 同样继承了 AbstractList , 所以 LinkedList 会有大部分方法和 ArrayList 相似。

LinkedList 实现了以下接口:

  • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
  • Deque :继承自 Queue 接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。需要注意,Deque 的发音为 "deck" [dɛk],这个大部分人都会读错。
  • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
  • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。

LinkedList 类图

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;
    }
}

2.2.1. 初始化

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

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

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

2.2.2. 插入元素

LinkedList 除了实现了 List 接口相关方法,还实现了 Deque 接口的很多方法,所以我们有很多种方式插入元素。

我们这里以 List 接口中相关的插入方法为例进行源码讲解,对应的是add() 方法。

add() 方法有两个版本:

  • add(E e):用于在 LinkedList 的尾部插入元素,即将新元素作为链表的最后一个元素,时间复杂度为 O(1)。
  • add(int index, E element):用于在指定位置插入元素。这种插入方式需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。
// 在链表尾部插入元素
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;
    // 判断前驱节点是否为空,为空表示 succ 是第一个节点
    if (pred == null)
        // 新节点成为第一个节点
        first = newNode;
    else
        // succ 节点前驱的后继引用指向新节点
        pred.next = newNode;
    size++;
    modCount++;
}

2.2.3. 获取元素

LinkedList获取元素相关的方法一共有 3 个:

  1. getFirst():获取链表的第一个元素。
  2. getLast():获取链表的最后一个元素。
  3. 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;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

get(int index)remove(int index) 等方法内部都调用了该方法来获取对应的节点。

从这个方法的源码可以看出,该方法通过比较索引值与链表 size 的一半大小来确定从链表头还是尾开始遍历。如果索引值小于 size 的一半,就从链表头开始遍历,反之从链表尾开始遍历。这样可以在较短的时间内找到目标节点,充分利用了双向链表的特性来提高效率。

2.2.4. 删除元素

LinkedList删除元素相关的方法一共有 5 个:

  1. removeFirst():删除并返回链表的第一个元素。
  2. removeLast():删除并返回链表的最后一个元素。
  3. remove(E e):删除链表中首次出现的指定元素,如果不存在该元素则返回 false。
  4. remove(int index):删除指定索引处的元素,并返回该元素的值。
  5. void clear():移除此链表中的所有元素。
// 删除并返回链表的第一个元素
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. 判断待删除节点是否为头节点或尾节点:
    • 如果 x 是头节点,则将 first 指向 x 的后继节点 next
    • 如果 x 是尾节点,则将 last 指向 x 的前驱节点 prev
    • 如果 x 不是头节点也不是尾节点,执行下一步操作
  1. 将待删除节点 x 的前驱的后继指向待删除节点的后继 next,断开 x 和 x.prev 之间的链接;
  2. 将待删除节点 x 的后继的前驱指向待删除节点的前驱 prev,断开 x 和 x.next 之间的链接;
  3. 将待删除节点 x 的元素置空,修改链表长度。

可以参考下图理解(图源:LinkedList 源码分析(JDK 1.8)):

unlink 方法逻辑

2.2.5. 遍历链表

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

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

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

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 中的核心方法进行详细介绍。

我们先来看下从头到尾方向的迭代:

// 判断还有没有下一个节点
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;
}

再来看一下从尾到头方向的迭代:

// 判断是否还有前一个节点
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;
}

如果需要删除或插入元素,也可以使用迭代器进行操作。

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);
}

迭代器对应的移除元素的方法如下:

// 从列表中删除上次被返回的元素
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++;
}

2.3. LinkedList 常用方法测试

代码:

// 创建 LinkedList 对象
LinkedList<String> list = new LinkedList<>();

// 添加元素到链表末尾
list.add("apple");
list.add("banana");
list.add("pear");
System.out.println("链表内容:" + list);

// 在指定位置插入元素
list.add(1, "orange");
System.out.println("链表内容:" + list);

// 获取指定位置的元素
String fruit = list.get(2);
System.out.println("索引为 2 的元素:" + fruit);

// 修改指定位置的元素
list.set(3, "grape");
System.out.println("链表内容:" + list);

// 删除指定位置的元素
list.remove(0);
System.out.println("链表内容:" + list);

// 删除第一个出现的指定元素
list.remove("banana");
System.out.println("链表内容:" + list);

// 获取链表的长度
int size = list.size();
System.out.println("链表长度:" + size);

// 清空链表
list.clear();
System.out.println("清空后的链表:" + list);

输出:

索引为 2 的元素:banana
链表内容:[apple, orange, banana, grape]
链表内容:[orange, banana, grape]
链表内容:[orange, grape]
链表长度:2
清空后的链表:[]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么安装行星减速电机才是正确的

行星减速电机由于其高效、精密的传动能力&#xff0c;广泛应用于自动化设备、机器人、机床以及其他需要精准控制的领域。正确的安装行星减速电机对于确保设备的性能与延长使用寿命至关重要。 一、前期准备 在进行行星减速电机的安装之前&#xff0c;必须做好充分的前期准备工作…

springcloud通过MDC实现分布式链路追踪

在DDD领域驱动设计中&#xff0c;我们使用SpringCloud来去实现&#xff0c;但排查错误的时候&#xff0c;通常会想到Skywalking&#xff0c;但是引入一个新的服务&#xff0c;增加了系统消耗和管理学习成本&#xff0c;对于大型项目比较适合&#xff0c;但是小的项目显得太过臃…

SAP RFC 用户安全授权

一、SAP 通讯用户 对于RFC接口的用户&#xff0c;使用五种用户类型之一的“通讯”类型&#xff0c;这种类型的用户没有登陆SAPGUI的权限。 二、对调用的RFC授权 在通讯用户内部&#xff0c;权限对象&#xff1a;S_RFC中&#xff0c;限制进一步可以调用的RFC函数授权&#xff…

Spring IoC——依赖注入

1. 依赖注入的介绍 DI&#xff0c;也就是依赖注入&#xff0c;在容器中建立的 bean &#xff08;对象&#xff09;与 bean 之间是有依赖关系的&#xff0c;如果直接把对象存在 IoC 容器中&#xff0c;那么就都是一个独立的对象&#xff0c;通过建立他们的依赖关系&#xff0c;…

IDEA连接EXPRESS版本的SQL server数据库

我安装的版本是SQL2019-SSEI-Expr.exe 也就是EXPRESS版本的SQL Server安排非常简单和快速。 但是默认没有启动sa用户。 启动sa用户名密码登录 默认安装完以后没有启用。 使用Miscrosoft SQL Server Management Studio 使用Windows身份连接后。 在安全性》登录名中找到sa并修改…

​Java面试经典 150 题.P13. 罗马数字转整数(012)​

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public int romanToInt(String s) {int sum…

大数据-204 数据挖掘 机器学习理论 - 混淆矩阵 sklearn 决策树算法评价

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Windows11下将某个程序添加到鼠标右键快捷菜单

经常看log&#xff0c;最喜欢用的txt查看和编辑工具是EditPlus&#xff0c;好像是个韩国软件&#xff0c;最大的优势是打开大文件&#xff0c;好几G的log文件也很轻松&#xff0c;速度快&#xff0c;然后还有各种高亮设置查找文件等&#xff0c;非常方便。但是不知道为什么&…

宏处理将多个excel文件的指定sheet页合并到一个excel文件中

背景了解&#xff1a;有个同事问我&#xff1a;现在他要处理一千多个文件&#xff0c;每个excel文件都有3个sheet页签&#xff0c;想把所有的excel文件的第二个sheet页签复制一份放到一个新的excel文件中。如果是手动去操作一个个文件的复制&#xff0c;也没什么不可&#xff0…

什么是散度,什么是旋度,分别反映什么现象,磁场和静电场分别是什么场?

散度和旋度是矢量场中重要的微分运算概念&#xff0c;用来描述矢量场的局部特性&#xff0c;广泛应用于物理学、流体力学和电磁学中。 1. 散度&#xff08;Divergence&#xff09; 散度描述的是一个矢量场在某一点的“发散”或“汇聚”程度&#xff0c;简单来说就是在该点附近…

web——warmup——攻防世界

这道题还是没有做出来。。&#xff0c;来总结一下 1.ctrlU显示源码 2.看见body里有source.php 打开这个source.php 看见了源码 highlight_file(FILE); 这行代码用于高亮显示当前文件的源码&#xff0c;适合调试和学习&#xff0c;但在生产环境中通常不需要。 class emmm 定义…

vue3项目history模式部署404处理,使用 historyApiFallback 中间件支持单页面应用路由

vue3项目history模式部署404处理&#xff0c;使用 historyApiFallback 中间件支持单页面应用路由 在现代的 web 开发中&#xff0c;单页面应用&#xff08;SPA&#xff09;变得越来越流行。这类应用通常依赖于客户端路由来提供流畅的用户体验&#xff0c;但在服务器端&#xf…

跨境电商平台系统开发

随着全球化的不断深入&#xff0c;跨境电商作为新兴的商业模式&#xff0c;越来越受到企业和消费者的关注。跨境电商平台的系统开发不仅涉及技术层面的挑战&#xff0c;更涉及到法律、物流、支付等多方面的因素。商淘云将分享跨境电商平台系统开发的主要环节&#xff0c;包括需…

《Web性能权威指南》-WebRTC-读书笔记

本文是《Web性能权威指南》第四部分——WebRTC的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP&#xff1b; 第四部分——浏览…

.NET 8 Web API 中的身份验证和授权

本次介绍分为3篇文章&#xff1a; 1&#xff1a;.Net 8 Web API CRUD 操作.Net 8 Web API CRUD 操作-CSDN博客 2&#xff1a;在 .Net 8 API 中实现 Entity Framework 的 Code First 方法https://blog.csdn.net/hefeng_aspnet/article/details/143229912 3&#xff1a;.NET …

android定时器循环实现轮播图

说明&#xff1a; android定时器加for循环实现轮播图 效果&#xff1a; step1: package com.example.iosdialogdemo;import android.os.Bundle; import android.os.Handler; import android.widget.ImageView; import android.widget.TextView;import androidx.appcompat.ap…

基于Node.js+Vue+MySQL实现的(Web)图书管理系统

1 需求分析 本图书管理系统主要实现对图书馆的管理&#xff1a;图书、读者、管理员、借阅。由此&#xff0c;结构可分为&#xff1a;图书管理、读者管理、管理员管理、借还管理、罚单管理、还书信息。 1.1 需求定义 1.1.1 图书管理 可对图书信息进行浏览、编辑&#xff08;…

计算机网络803-(5)运输层

目录 一.运输层的两个主要协议&#xff1a;TCP 与 UDP 1.TCP/IP 的运输层有两个不同的协议&#xff1a; 2.端口号(protocol port number) &#xff08;1&#xff09;软件端口与硬件端口 &#xff08;2&#xff09;TCP 的端口 &#xff08;3&#xff09;三类端口 二.用户…

机器学习之fetch_olivetti_faces人脸识别--基于Python实现

fetch_olivetti_faces 数据集下载 fetch_olivetti_faceshttps://github.com/jikechao/olivettifaces sklearn.datasets.fetch_olivetti_faces(*, data_homeNone, shuffleFalse, random_state0, download_if_missingTrue, return_X_yFalse, n_retries3, delay1.0)[source] L…

嵌入式硬件电子电路设计(三)电源电路之负电源

引言&#xff1a;在对信号线性度放大要求非常高的应用需要使用双电源运放&#xff0c;比如高精度测量仪器、仪表等;那么就需要给双电源运放提供正负电源。 目录 负电源电路原理 负电源的作用 如何产生负电源 负电源能作功吗&#xff1f; 地的理解 负电压产生电路 BUCK电…