本节目标:

  1. 认识并且能够实现一个双链表
  2. 认识LinkedList类并且知道如何去使用

1.双链表

概念

在数据结构中,双链表(Doubly Linked List) 是一种常见的线性数据结构,它由一系列节点组成,每个节点不仅包含数据域,还包含两个指针域,分别指向其前驱节点(前一个节点) 和后继节点(后一个节点)。这种结构相比单链表(仅含后继指针),在节点访问和操作上更加灵活,可以表示成这样:

相较于单链表,双链表多了一个前驱节点prev,它储存的是这个节点的前一个节点的地址,即它指向这个节点的前一个节点

实现双链表

在上一节中,我们已经实现不带头不循环的单链表了,那么这里实现的是不带头不循环的双链表。以下是这个双链表包含的一些操作方法:

// 不带头双向链表实现

public class MyLinkedList {

        //头插法

        public void addFirst(int data){ }

        //尾插法

        public void addLast(int data){}

        //任意位置插入,第一个数据节点为0号下标

        public void addIndex(int index,int data){}

        //判断双链表当中是否有包含key节点

        public boolean contains(int key){

                return false;

        }

        //删除第一次出现关键字为key的节点

        public void remove(int key){}

        //删除所有值为key的节点

        public void removeAllKey(int key){}

        //得到双链表的长度

        public int size(){

                return -1;

        }

        //打印双链表中的数据

        public void display(){}

        //清空双链表

        public void clear(){}

在实现这些操作之前,先创建节点类 ListNode:

public class ListNode {public int val;public ListNode prev;public ListNode next;public ListNode(int val) {this.val = val;}
}

还是老规矩,实现顺序由简到繁。

(1) 打印双链表中的数据

要求:将双链表中的数据一个个打印出来。

思路:实现方式与单链表的打印一样,通过遍历的方式,一个个去打印。

public class MyLinkedList {ListNode head;ListNode last;//打印双链表中的数据public void display() {ListNode cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}
}

因为双链表中前驱结点指向该节点的前一个节点,因此它能够从后往前访问,所以用 last

来表示双链表的尾节点。

(2) 得到双链表的长度

要求:通过这个方法获得双链表的长度。

思路:定义一个变量用于计数,通过遍历的方式进行计数,最后返回这个变量。

//得到双链表的长度public int size() {ListNode cur = this.head;int size = 0; while (cur != null) {size++;cur = cur.next;}return size;}

(3) 判断双链表当中是否有包含key节点

要求:判断双链表中是否有包含key的节点,如果有就返回true,否则返回false。

思路:依旧通过遍历的方式去一个个判断。

//判断双链表当中是否有包含key节点public boolean contains(int key){ListNode cur = this.head;while (cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;}

(4) 头插法

要求:在双链表的头部位置插入一个新节点。

思路:这里与单链表不一样,需要考虑两种情况:1.插入前链表是空的;2.插入前链表不为空。

即下图所示:

//头插法public void addFirst(int data){ ListNode cur = new ListNode(data);if (this.head == null) {this.head = this.last = cur;}else {cur.next = this.head;this.head.prev = cur;this.head = cur;}}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addFirst(1);myLinkedList.addFirst(2);myLinkedList.addFirst(3);myLinkedList.display();}
}//运行结果
3 2 1 

符合预期。

(5) 尾插法

要求:在双链表的尾部插入一个新节点。

思路:这里也分两种情况,与头插法一样。需要注意的是,在链表不为空的情况下,我们应该对尾节点last进行操作,而不是头节点head。

//尾插法public void addLast(int data){ListNode cur = new ListNode(data);if (this.head == null) {this.head = this.last = cur;}else {this.last.next = cur;cur.prev = this.last;this.last = cur;}}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addLast(1);myLinkedList.addLast(2);myLinkedList.addLast(3);myLinkedList.display();}
}//运行结果
1 2 3 

符合预期。

(6) 任意位置插入,第一个数据节点为0号下标

要求:在双链表中合法的任一位置插入一个新节点,第一个节点为0号下标。

思路:可以分3种情况处理:头插、尾插和中间插入。头插与尾插已经解决了,因此现在只需要解决中间插入即可。如下图所示:

当然,在进行插入操作前,需要判断插入位置是否合法。

插入位置非法异常类:

public class InsertIllegalException extends RuntimeException {public InsertIllegalException() {}public InsertIllegalException(String str) {super(str);}
}

判断插入位置是否合法:

//判断插入位置是否合法private void isIllegal(int index) {if (index < 0 || index > size()) {throw new InsertIllegalException("插入位置不合法!");}}

插入操作:

//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data){try {isIllegal(index);int len = size();//头插if (index == 0) {addFirst(data);return;}//尾插if (index == len) {addLast(data);return;}//中间插入ListNode newN = new ListNode(data);ListNode cur = this.head;while (index != 0) {cur = cur.next;index--;}newN.next = cur;cur.prev.next = newN;newN.prev = cur.prev;cur.prev = newN;}catch (InsertIllegalException e) {e.printStackTrace();}}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addLast(1);myLinkedList.addLast(2);myLinkedList.addLast(3);myLinkedList.display();myLinkedList.addIndex(1,100);myLinkedList.display();}
}//运行结果
1 2 3 
1 100 2 3 

符合预期。

(7) 删除第一次出现关键字为key的节点

要求:将双链表中第一次出现包含key的节点删掉。

思路:这里要分为三种情况:key在链表头部、key在链表尾部和key在链表中间,但在开始前需要判断链表是否为空。分析如下图所示:

//删除第一次出现关键字为key的节点public void remove(int key){if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;this.head.prev = null;}else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;}else {//位于中间cur.next.prev = cur.prev;}}//删除完成!return;}cur = cur.next;}if (mark == false) {System.out.println("链表中没有" + key);}}

但是这里还有一个不足的地方,就是当链表中仅有一个节点时(即head == last且该节点的值为key),这时候 head.prev就会引发空指针异常,所以要对key在头节点的情况进行补充处理:

//删除第一次出现关键字为key的节点public void remove(int key){if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;//当删除后链表不为空,即链表不仅仅有一个节点if (this.head != null) {this.head.prev = null;}else {//删除后链表为空,即链表仅有一个节点,这里更新尾节点lastthis.last = null;}}else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;}else {//位于中间cur.next.prev = cur.prev;}}//删除完成!return;}cur = cur.next;}if (mark == false) {System.out.println("链表中没有" + key);}}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addLast(1);myLinkedList.addLast(2);myLinkedList.addLast(3);myLinkedList.addLast(1);myLinkedList.display();myLinkedList.remove(1);myLinkedList.display();}
}//运行结果:
1 2 3 1 
2 3 1 

符合预期。

(8) 删除所有值为key的节点

要求:将链表中所有值为key的节点都删掉。

思路:只需要在删除第一次出现关键字为key的节点的方法的基础上进行修改即可,把return去掉,使得循环继续进行,继续找key的节点,继续删除,直到遍历结束,此时已将所有值为key的节点删除。

//删除所有值为key的节点public void removeAllKey(int key) {if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;//当删除后链表不为空,即链表不仅仅有一个节点if (this.head != null) {this.head.prev = null;} else {//删除后链表为空,即链表仅有一个节点,这里更新尾节点lastthis.last = null;}} else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;} else {//位于中间cur.next.prev = cur.prev;}}}cur = cur.next;}if (mark == false) {System.out.println("链表中没有" + key);}}

不过这里可能会有一个潜在的问题,就是当我们删除某一个节点,cur指向的节点已经被移除,此时执行cur = cur.next可能会出现问题。举个例子:

现在有一个双链表:

2 <-> 3 <-> 2 <-> 4

现在要删掉所有的2

1.首先,cur指向第一个2,删除后head变为3节点;

2.接着执行cur = cur.next时,此时的cur仍然指向已经被删除的第一个 2 节点;

3.这个节点的next虽然可能还指向 3,但这是不安全的(已删除节点的引用应该被视为无效)

因此出于安全考虑,可以用一个变量记录cur的=指向的下一个节点。

//删除所有值为key的节点public void removeAllKey(int key) {if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {ListNode curN = cur.next;//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;//当删除后链表不为空,即链表不仅仅有一个节点if (this.head != null) {this.head.prev = null;} else {//删除后链表为空,即链表仅有一个节点,这里更新尾节点lastthis.last = null;}} else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;} else {//位于中间cur.next.prev = cur.prev;}}}cur = curN;}if (mark == false) {System.out.println("链表中没有" + key);}}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addLast(2);myLinkedList.addLast(1);myLinkedList.addLast(2);myLinkedList.addLast(3);myLinkedList.addLast(2);myLinkedList.display();myLinkedList.removeAllKey(2);myLinkedList.display();}
}//运行结果:
2 1 2 3 2 
1 3 

符合预期。

(9) 清空双链表

要求:将双链表清空。

思路:粗暴的方式就不介绍了(直接将head和last置空),温柔的方式:通过遍历,一个个将节点的prev和next置空,最后将head和last置空。

//清空双链表public void clear() {ListNode cur = this.head;while (cur != null) {ListNode curN = cur.next;cur.prev = null;cur.next = null;cur = curN;}this.head = null;this.last = null;}

进行测试:

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.addLast(2);myLinkedList.addLast(1);myLinkedList.addLast(2);myLinkedList.addLast(3);myLinkedList.addLast(2);myLinkedList.display();myLinkedList.clear();myLinkedList.display();}
}//运行结果:
2 1 2 3 2 

符合预期。

到此,我们就成功实现了一个不带头不循环的双链表。

完整代码

public class MyLinkedList {ListNode head;ListNode last;//打印双链表中的数据public void display() {ListNode cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}//得到双链表的长度public int size() {ListNode cur = this.head;int size = 0;while (cur != null) {size++;cur = cur.next;}return size;}//判断双链表当中是否有包含key节点public boolean contains(int key){ListNode cur = this.head;while (cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;}//头插法public void addFirst(int data){ListNode cur = new ListNode(data);if (this.head == null) {this.head = this.last = cur;}else {cur.next = this.head;this.head.prev = cur;this.head = cur;}}//尾插法public void addLast(int data){ListNode cur = new ListNode(data);if (this.head == null) {this.head = this.last = cur;}else {this.last.next = cur;cur.prev = this.last;this.last = cur;}}//判断插入位置是否合法private void isIllegal(int index) {if (index < 0 || index > size()) {throw new InsertIllegalException("插入位置不合法!");}}//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data){try {isIllegal(index);int len = size();//头插if (index == 0) {addFirst(data);return;}//尾插if (index == len) {addLast(data);return;}//中间插入ListNode newN = new ListNode(data);ListNode cur = this.head;while (index != 0) {cur = cur.next;index--;}newN.next = cur;cur.prev.next = newN;newN.prev = cur.prev;cur.prev = newN;}catch (InsertIllegalException e) {e.printStackTrace();}}//删除第一次出现关键字为key的节点public void remove(int key){if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;//当删除后链表不为空,即链表不仅仅有一个节点if (this.head != null) {this.head.prev = null;}else {//删除后链表为空,即链表仅有一个节点,这里更新尾节点lastthis.last = null;}}else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;}else {//位于中间cur.next.prev = cur.prev;}}//删除完成!return;}cur = cur.next;}if (mark == false) {System.out.println("链表中没有" + key);}}//删除所有值为key的节点public void removeAllKey(int key) {if (this.head == null) {System.out.println("链表为空!");return;}ListNode cur = this.head;boolean mark = false;  //用于标记链表中是否有key,有的话置为truewhile (cur != null) {ListNode curN = cur.next;//先找keyif (cur.val == key) {mark = true;//当cur位于头节点if (cur == this.head) {this.head = this.head.next;//当删除后链表不为空,即链表不仅仅有一个节点if (this.head != null) {this.head.prev = null;} else {//删除后链表为空,即链表仅有一个节点,这里更新尾节点lastthis.last = null;}} else {//位于中间或者尾节点cur.prev.next = cur.next;//位于尾节点if (cur.next == null) {this.last = this.last.prev;} else {//位于中间cur.next.prev = cur.prev;}}}cur = curN;}if (mark == false) {System.out.println("链表中没有" + key);}}//清空双链表public void clear() {ListNode cur = this.head;while (cur != null) {ListNode curN = cur.next;cur.prev = null;cur.next = null;cur = curN;}this.head = null;this.last = null;}
}

2.LinkedList

2.1 概念

LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList也实现了List接口,具体如下:

【说明】

  1. LinkedList实现了List接口
  2. LinkedList的底层使用了双向链表
  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
  5. LinkedList比较适合任意位置插入的场景

2.2 使用

构造方法

LinkedList的构造方法:

方法解释
LinkedList()无参构造
public LinkedList(Collection <? extends E> c)使用其他集合容器中元素构造List

举例:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class Test {public static void main(String[] args) {//使用第一种构造方法,构造一个空的LinkedListLinkedList<Integer> linkedList = new LinkedList<>();//List<Integer> linkedList = new LinkedList<>();这种写法等于上一条语句,//因此LinkedList实现了List接口,因此可以用List去接收//可以插入数据linkedList.addFirst(1);linkedList.addFirst(2);linkedList.addFirst(3);System.out.println(linkedList);System.out.println("=============");//使用第二种构造方法List<Integer> list = new ArrayList<>();  //创建一个ArrayList,并往里面插入一些数据list.add(1);list.add(2);list.add(3);System.out.println(list);System.out.println("=============");//可以ArrayList构造LinkedListList<Integer> list1 = new LinkedList<>(list);System.out.println(list1);}
}//运行结果:
[3, 2, 1]
=============
[1, 2, 3]
=============
[1, 2, 3]

其他常用方法

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection <? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List subList(int fromIndex, int toIndex)截取部分 list

LinkedList的遍历

LinkedList的遍历与ArrayList的遍历一样,有三种方法,分别是通过for遍历、通过for-each遍历和通过迭代器遍历。具体请看:

数据结构 ArrayList与顺序表-CSDN博客

2.3 ArrayList与LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持:O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

到此,双链表与LinkedList的内容完结。感谢您的观看,如有不对的地方还请指出,谢谢!

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何解决 JetBrains IntelliJ IDEA 2024.2 和 2025.2 新版本区域选择问题:key is invalid

如何解决 JetBrains IntelliJ IDEA 2024.2 和 2025.2 新版本区域选择问题&#xff1a;key is invalid 在 JetBrains 发布的 IntelliJ IDEA、PyCharm 2024.2 和 2025.2 新版本中&#xff0c;增加了一个新的功能——区域选择。在设置菜单中&#xff0c;你可以找到这一选项&#…

GSON 框架下百度天气 JSON 数据转 JavaBean 的实战攻略

目录 前言 一、百度天气JSON 1、请求参数 2、返回参数 3、属性映射 二、GSON属性映射实战 1、类对象映射 2、属性字段映射 3、日期数据映射 三、天气接口对象展示 1、接口调用 2、Java属性打印输出 四、总结 前言 在当今数字化时代&#xff0c;数据的高效处理与转换…

NAS技术在县级融媒体中心的架构设计与安全运维浅析

NAS技术在县级融媒体中心的架构设计与安全运维浅析 ——原理剖析、应用实践与防御体系建设作者&#xff1a;高级网络安全工程师 吉林•镇赉融媒 刘晓伟 最后更新&#xff1a;2025年8月 适用对象&#xff1a;媒体行业网络安全从业者一、NAS技术核心原理剖析 1. 基础架构 NAS&am…

CobaltStrike的搭建和使用

下载CobaltStrike环境建议使用jdk17&#xff0c;其他java版本有些功能可能无法使用通过网盘分享的文件&#xff1a;CS4.7key-mht.zip 链接: https://pan.baidu.com/s/1CRd1x4r6EIk14BD3UCLgxw?pwdevf4 提取码: evf4将下载的文件分别放在服务器和 本地/kali 上 也就是服务器为…

【Altium designer】一键给多个器件添加参数

目的: 一键给N个元器件/Part添加参数和修改参数值,比如一键给多个电阻添加“备注”并赋予备注的内容为“不焊接”,或者更改“备注”的内容为“不焊接”或空。 背景: 刚入门用AD画原理图,因为原理图的电阻、电容和芯片等等的冗余/兼容设计太多,增加备注不焊的元器件位号…

熟练掌握switch语句:技巧与运用

目录 一、switch语句基础 基本语法结构&#xff1a; 在C/C中&#xff1a; 注意事项&#xff1a; 二、if与switch语句对比 示例&#xff1a;计算整数除以3的余数 使用if语句实现&#xff1a; 使用switch语句实现&#xff1a; 三、break语句的作用 示例&#xff08;无br…

【03】厦门立林科技——立林科技 嵌入式 校招笔试,题目记录及解析

厦门立林科技——立林科技 嵌入式 校招笔试&#xff0c;题目记录及解析 1.下面的程序的输出是&#xff08;&#xff09;。2.在头文件中#ifndef/#define/#endif的作用是4.执行下面程序中的输出语句后,输出的结果是()6.在32位处理器上,运行如下程序后p的值为()。10.设有两字符串“…

C++算法(数据结构)版

C算法&#xff08;数据结构&#xff09;版 有些题目不是完整的题目&#xff0c;如需查看完整的题目请移步到acwing的算法基础课中 文章目录C算法&#xff08;数据结构&#xff09;版单链表思路&#xff1a;双链表思路&#xff1a;栈思路&#xff1a;队列思路&#xff1a;单调栈…

算法训练营DAY57 第十一章:图论part07

prim算法精讲 53. 寻宝&#xff08;第七期模拟笔试&#xff09; 题目描述&#xff1a; 在世界的某个区域&#xff0c;有一些分散的神秘岛屿&#xff0c;每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路&#xff0c;方便运输。 不同岛屿之间&#xff0c;…

最短路问题从入门到负权最短路

一&#xff0c;BFS层次最短路/*题目描述 题目描述 给出一个 N 个顶点 M 条边的无向无权图&#xff0c;顶点编号为 1∼N。 问从顶点 1 开始&#xff0c;到其他每个点的最短路有几条。 输入格式 第一行包含 2 个正整数 N,M&#xff0c;为图的顶点数与边数。 接下来 M 行&#xff…

AI智能体小白入门指南

AI智能体小白入门指南 ——什么是AI智能体&#xff1f;它们如何工作&#xff1f; 一、AI智能体是什么&#xff1f; AI智能体&#xff08;AI Agent&#xff09;是能感知环境、自主决策并执行动作的人工智能系统。 类比理解&#xff1a;像一个“虚拟机器人”或“数字助手”&#…

《设计模式》策略模式

1.策略模式定义 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一组算法&#xff0c;将每个算法封装起来&#xff0c;并使它们可以相互替换&#xff0c;从而让算法的变化独立于使用它的客户&#xff08;Client&#xff09;。 换…

AWS DMS 深度解析:从迁移任务到复制任务 - 全流程指南与最佳实践

AWS Database Migration Service (DMS) 是一项强大的云服务,用于在源数据库和目标数据库之间安全地迁移数据。其核心优势在于支持几乎零停机时间的迁移,这主要归功于其“变更数据捕获 (CDC)”功能。理解迁移任务 (Migration Task) 和复制任务 (Replication Task) 的关系与操作…

国企社招 | 中国邮政2025年社会招聘开启

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 原文链接&#xff1a;“邮”你“政”好 | 广东邮政2025年社会…

linux添加自启动

linux添加自启动 配置步骤&#xff1a; 创建systemd服务文件 sudo nano /etc/systemd/system/tme-vod.service将下面artifact中的内容复制到该文件中。 [Unit] DescriptionTME VOD Service Afternetwork.target[Service] Typesimple Userroot Grouproot WorkingDirectory/data/…

轻量级解决方案:如何高效处理Word转PDF?

文档格式转换时&#xff0c;手动逐个处理总显得效率低下。它的体积小巧&#xff0c;不到1MB&#xff0c;且无界面设计&#xff0c;运行极简&#xff1a;将其与Word文件放入同一目录&#xff0c;双击启动&#xff0c;程序便会自动完成所有文档的PDF转换。操作零复杂度&#xff0…

Redis 数据倾斜

Redis 数据倾斜指的是在 Redis 集群模式下&#xff0c;数据&#xff08;以及相应的访问请求和负载&#xff09;在各个分片&#xff08;Shard&#xff09;之间分布严重不均匀的现象。这会导致部分节点成为热点或超载&#xff0c;而其他节点资源闲置&#xff0c;最终引发性能瓶颈…

Java基础-TCP通信(多发多收和一发一收)

目录 案例要求&#xff1a; 实现思路&#xff1a; 代码&#xff1a; User:客户端 Client:服务端 总结&#xff1a; 案例要求&#xff1a; 实现TCP通信的多发多收和一发一收,多发多收去掉各自的while循环就是一发一收,本文只模拟一发一收 实现思路&#xff1a; 客户端(U…

WinForm 对话框的 Show 与 ShowDialog:阻塞与非阻塞的抉择

目录 核心概念&#xff1a;阻塞与非阻塞 Show 与 ShowDialog 的详细对比 代码示例&#xff1a;两种方式的实现差异 使用 Show () 显示非模态对话框 使用 ShowDialog () 显示模态对话框 适用场景分析 适合使用 Show () 的场景 适合使用 ShowDialog () 的场景 最佳实践与…

晓知识: 动态代理与静态代理的区别

动态代理与静态代理的区别 代理模式是一种常见的设计模式&#xff0c;用于在不修改原始类的情况下扩展其功能。代理分为静态代理和动态代理两种&#xff0c;它们在实现方式、适用场景和灵活性上有显著差异。 静态代理 静态代理在编译时就已经确定代理类和被代理类的关系。代理类…