Java高频面试之集合篇

Java 中常用的容器有哪些?

ArrayList 和 LinkedList 的区别?

ArrayList 是基于数组实现的,LinkedList 是基于链表实现的.
ArrayList实现了RandomAccess接口,可基于下标访问.
LinkedList 实现了Deque /dek/,可以当做双端队列使用.
插入效率对比
如果从头部插入,LinkedList更快
如果从中间插入ArrayList更快
如果从尾部插入,在不扩容的情况下ArrayList略快,扩容次数多时LinkedList更快
get(int index) ArrayList更快
遍历ArrayList更快
ArrayList新循环(迭代器)和普通循环遍历效率差不多
LinkedList 新循环(迭代器)比普通循环效率高很多(每次get(int index)都要找一次)

从头开始插

public static void addFromHeaderTest4ArrayList(int num) {ArrayList<String> list = new ArrayList<>(num);int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {list.add(0, i + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("ArrayList从集合头部位置新增元素花费的时间" + (timeEnd - timeStart));
}
public static void addFromHeaderTest4LinkedList(int num) {LinkedList<String> list = new LinkedList<String>();int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {list.addFirst(i + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("LinkedList从集合头部位置新增元素花费的时间" + (timeEnd - timeStart));
}
ArrayList从集合头部位置新增元素花费的时间415
LinkedList从集合头部位置新增元素花费的时间12

从中间开始插

public static void addFromMidTest4ArrayList(int num) {ArrayList<String> list = new ArrayList<>(num);int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {int temp = list.size();list.add(temp / 2 + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("ArrayList从集合中间位置新增元素花费的时间" + (timeEnd - timeStart));
}
public static void addFromMidTest4LinkedList(int num) {LinkedList<String> list = new LinkedList<>();int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {int temp = list.size();list.add(temp / 2, i + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("LinkedList从集合中间位置新增元素花费的时间" + (timeEnd - timeStart));
}
ArrayList从集合中间位置新增元素花费的时间19
LinkedList从集合中间位置新增元素花费的时间28610

从尾部开始插

public static void addFromTailTest4ArrayList(int num) {ArrayList<String> list = new ArrayList<>(num);int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {list.add(i + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("ArrayList从集合尾部位置新增元素花费的时间" + (timeEnd - timeStart));
}
public static void addFromTailTest4LinkedList(int num) {LinkedList<String> list = new LinkedList<>();int i = 0;long timeStart = System.currentTimeMillis();while (i < num) {list.add(i + "王大锤");i++;}long timeEnd = System.currentTimeMillis();System.out.println("LinkedList从集合尾部位置新增元素花费的时间" + (timeEnd - timeStart));
}
ArrayList从集合尾部位置新增元素花费的时间17
LinkedList从集合尾部位置新增元素花费的时间13

遍历

/*** 10000000* arrayList遍历耗时:50* linkedList遍历耗时:172*/
private static void extracted7() {int count = 10000000;List<String> arrayList = new ArrayList<>();List<String> linkedList = new LinkedList<>();for (int i = 0; i < count; i++) {arrayList.add("lxw" + i);linkedList.add("lxw" + i);}long startA = System.currentTimeMillis();for (String s : arrayList) {}long endA = System.currentTimeMillis();for (String s : linkedList) {}long endL = System.currentTimeMillis();System.out.println("arrayList通过新循环遍历耗时:" + (endA - startA));System.out.println("linkedList通过新循环遍历耗时:" + (endL - endA));
}

ArrayList添加元素流程

list.add(Object e)

  1. 先判断是否需要扩容,如果需要则扩容
    minCapacity = size+要添加元素的个数 add(e) 为1, addAll(sublist) 为sublist.size()
    minCapacity > elementData.length
  2. 通过索引将元素添加到末尾
    elementData[size++] = e

list.add(int index, Object e) 性能比较差

  1. 检查插入的位置是否在合理的范围之内
    不合理( index > size || index < 0)则抛异常IndexOutOfBoundsException
  2. 先判断是否需要扩容,如果需要则进行扩容
  3. 把index以及之后的元素复制到index之后
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
  4. 将新元素添加到index位置
    elementData[index] = e

LinkedList 添加元素流程

list.add(Object e)

直接将元素添加到队尾

list.add(int index, Object e)

  1. 检查插入的位置是否在合理的范围之内
index >= 0 && index <= size
  1. 如果插入的位置是队尾,则尾插

  2. 如果不是队尾, 寻找元素 Node node(int index) 如果index < (size >> 1) 从头开始找,否则从末尾往前找(size >> 1 size的一半向下取整)

  3. 将e连接到node前面

ArrayList 实现 RandomAccess 接口有何作用?为何 LinkedList 却没实现这个接口?

ArrayList 的扩容机制?

minCapacity=size+要添加元素的个数(add(E e)与addAll(Collection<? extends E> c))

扩容条件 minCapacity>elementData.length

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;// 扩容1.5倍(向下取整)int newCapacity = oldCapacity + (oldCapacity >> 1);// 扩容后新容量还存不下 list.addAll(subList) 原始容量 10 扩容后newCapacity = 15 minCapacity = 100if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

Array 和 ArrayList 有何区别?什么时候更适合用 Array?

  • Array:申明数组的时候就要初始化并确定长度,长度不可变,而且它只能存储同一类型的数据,(Array可以存储基本类型)只对外提供了一个length属性.

  • ArrayList 是基于Array实现的可以动态扩容的集合类,提供了更丰富的方法.ArrayList 不能存储基本类型(包装类)

  • 当能确定长度并且数据类型一致的时候就可以用数组,其他时候使用ArrayList

HashMap 的实现原理/底层数据结构?JDK1.7 和 JDK1.8

HashMap 的 put 方法的执行过程?

http://150.158.33.191/myblog/#/BlogArticleDetails?id=83

HashMap 的 get 方法的执行过程?

HashMap 的 resize 方法的执行过程?

/*** 1.计算新容量* 2.计算新阈值* 3.根据新容量创建新数组,并将新数组赋值给table* 4.将老数组中的元素转移到新数组*/
final HashMap.Node<K, V>[] resize() {HashMap.Node<K, V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {// 不再扩容,修改阈值threshold = Integer.MAX_VALUE;return oldTab;} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold} else if (oldThr > 0) // initial capacity was placed in threshold 初始化容量被临时存储在阈值里面了// 指定容量的情况 会初始化阈值 临时将容量存到阈值里, 否则没法区分是初始化扩容还是初始化后的扩容newCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float) newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?(int) ft : Integer.MAX_VALUE);}// 给新阈值赋值threshold = newThr;@SuppressWarnings({"rawtypes", "unchecked"})// 创建新数组HashMap.Node<K, V>[] newTab = (HashMap.Node<K, V>[]) new HashMap.Node[newCap];// 重新给table赋值table = newTab;// 将oldTab里面的元素赋值给tabif (oldTab != null) {for (int j = 0; j < oldCap; ++j) {HashMap.Node<K, V> e;if ((e = oldTab[j]) != null) {// 转移 类似于剪贴oldTab[j] = null;// hash桶中只有一个元素if (e.next == null)// 相当于rehashnewTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)((HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);else { // preserve order// loHead 低位的头元素 loTail低位的为元素HashMap.Node<K, V> loHead = null, loTail = null;// hiHead高位的头元素 hiTail高位的尾元素HashMap.Node<K, V> hiHead = null, hiTail = null;// HashMap.Node<K, V> next;// 将原链表拆成2个子链表do {// 为了在while条件中用next = e.next;// (e.hash & oldCap) == 0 计算高低位 if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;} else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;// 将低位链表复制给新数组的j位置newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;// 将高位链表赋值给新数组的j+oldCapnewTab[j + oldCap] = hiHead;}}}}}return newTab;
}

HashMap 的 size 为什么必须是 2 的整数次方?

为了利用&运算计算节点在数组中的下标

HashMap 多线程死循环问题?

HashMap 的 get 方法能否判断某个元素是否在 map 中?

不能,HashMap允许value为null,返回null无法区分是不存在还是value为null

HashMap 与 HashTable 的区别是什么?

1. HashTable 继承自 Dictionary 类实现了Map接口, HashMap 继承自AbstractMap实现了Map接口。
2. HashTable 是线程安全的,HashMap 不是。
3. HashMap允许将 null key和null value,而 Hashtable 不允许null key和null value。
4. HashMap 只有containsKey(Object key)和containsValue(Object value), Hashtable 额外提供了contains(Object value)( contains 方法容易让人引起误解)。

HashMap 与 ConcurrentHashMap 的区别是什么?

1. HashMap 线程不安全,ConcurrentHashMap 线程安全.
2. HashMap允许null key和null value,ConcurrentHashMap不允许null key和null value

HashTable 和 ConcurrentHashMap 的区别?

1. 底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采用 分段数组+链表 实现,而 JDK1.8 的 ConcurrentHashMap 实现跟 HashMap1.8 的数据结构一样,都是 数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似,都是采用 数组+链表 的形式。数组是 HashMap 的主体,链表则是为了解决哈希冲突而存在的;
2. 实现线程安全的方式: ① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段( Segment ),每一把锁只锁容器其中的一部分数据,这样多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高了并发访问率。 到了 JDK1.8,摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作,(JDK1.6 以后对 synchronized 锁做了很多的优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable (同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。一个线程访问同步方法时,当其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程就不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈,效率就越低。

ConcurrentHashMap 的实现原理是什么?

  1. HashSet 的实现原理?

HashSet 怎么保证元素不重复的?

HashSet 保证元素不重复是利用HashMap 的put 方法实现的,在存储之前先根据key 的hashCode 和equals 判断是否已存在,如果存在就不在重复插入了,这样就保证了元素的不重复。

LinkedHashMap 的实现原理?

LinkedHashMap 继承自HashMap,重写了newNode,afterNodeAccess,afterNodeInsertion,afterNodeRemoval等方法.
在newNode方法中维护了双向链表
默认是插入属性,也可以通过LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder)的accessOrder指定访问属性.
LinkedHashMap里并没有重写put方法,只是重写了put中调用的部分方法(模板方法模式)

Iterator 怎么使用?有什么特点?

Iterator 和 ListIterator 有什么区别?

ListIterator 继承自Iterator.
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。 
Iterator对集合只能是单向移动前向遍历,ListIterator既可以前向也可以后向。
ListIterator 在迭代过程中不光可以删除元素,还能新增元素,修改元素.Iterator 只能删除元素
public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");ListIterator<String> listIterator = list.listIterator();while (listIterator.hasPrevious()){String previous = listIterator.previous();System.out.println("previous = " + previous);}System.out.println("------------");while (listIterator.hasNext()){String next = listIterator.next();if ("1".equals(next)) {listIterator.set("11");}if ("2".equals(next)) {listIterator.remove();}if ("3".equals(next)) {listIterator.add("4");}System.out.println("next = " + next);}System.out.println("====================");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String next = iterator.next();System.out.println("next = " + next);}
}
  1. Iterator 和 Enumeration 接口的区别?

  2. fail-fast 与 fail-safe 有什么区别?

Collection 和 Collections 有什么区别?

Collection 是集合接口
Collections 是集合的工具类

https://zhuanlan.zhihu.com/p/92481037

  1. table 的初始化时机是什么时候,初始化的 table.length 是多少、阀值(threshold)是多少,实际能容下多少元素

    new HashMap<>()
    初始化时机是第一次put元素的时候,table.length是16,threshold是12,时机能容下12个元素
  2. 什么时候触发扩容,扩容之后的 table.length、阀值各是多少?

    第一次添加元素的时候(tab == null)tab.length<64 && 链表长度>8++size > threshold
    
  3. table 的 length 为什么是 2 的 n 次幂

    为了利用&运算求key在数组中的下标
    
  4. 求索引的时候为什么是:h&(length-1),而不是 h&length,更不是 h%length

    h%length 效率不如位运算快
    h&length hash碰撞多,会导致 table 的空间得不到利用(有的桶里放了很多,有的桶里空着)、降低 table 的操作效率
    h&(length-1) hash碰撞少   
    &右边为1或者连续的1时&左边的和=右边的相同(只要左边相对分散(由hash控制),结果就相对分散)
    1&1=1;
    0&1=0;
    1&0=0;
    0&0=0;
    1010&1111=1010; => 10&15=10;
    1011&1111=1011; => 11&15=11;
    01010&10000=00000; => 10&16=0;
    01011&10000=00000; => 11&16=0;
    2^n-1 低位为连续的1,length刚好为2^n
  5. Map map = new HashMap(1000); 当我们存入多少个元素时会触发map的扩容; Map map1 = new HashMap(10000); 我们存入第 10001个元素时会触发 map1 扩容吗

    Map map = new HashMap(1000); 当我们存入多少个元素时会触发map的扩容
    此时的 table.length = 2^10 = 1024; threshold = 1024 * 0.75 = 768; 所以存入第 768 个元素时进行扩容Map map1 = new HashMap(10000); 我们存入第 10001个元素时会触发 map1 扩容吗
    此时的 table.length = 2^14 = 16384; threshold = 16384 * 0.75 = 12288; 所以存入第 10001 个元素时不会进行扩容
    
  6. 为什么加载因子的默认值是 0.75,并且不推荐我们修改

    为什么加载因子的默认值是 0.75,并且不推荐我们修改
    如果loadFactor太小,那么map中的table需要不断的扩容,扩容是个耗时的过程
    如果loadFactor太大,那么map中table放满了也不不会扩容,导致冲突越来越多,解决冲突而起的链表越来越长,效率越来越低
    而 0.75 这是一个折中的值,是一个比较理想的值
    

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

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

相关文章

Unity的滑动控制相机跟随和第三人称视角三

Unity的相机跟随和第三人称视角三 第三人称相机优化介绍讲解拖动事件相机逻辑人物移动逻辑总结 第三人称相机优化 Unity第三人称相机视角一 Unity第三人称相机视角二 介绍 之前相机视角讲过了两篇文章了&#xff0c;但是都是自动旋转视角&#xff0c;今天来了新需求&#xf…

支部管理系统微信小程序(管理端+用户端)flask+vue+mysql+微信小程序

系统架构如图所示 高校D支部管理系统 由web端和微信小程序端组成&#xff0c;由web端负责管理&#xff0c;能够收缴费用、发布信息、发布问卷、发布通知等功能 部分功能页面如图所示 微信小程序端 包含所有源码和远程部署&#xff0c;可作为毕设课设

Java 数据结构之链表

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA null || headB null) return null;ListNode pA headA, pB headB;while (pA ! pB) {pA pA null ? headB : pA.next;pB pB null ? headA : pB.next;}return pA;} public ListNode rev…

数据库系列之:什么是 SAP HANA?

数据库系列之&#xff1a;什么是 SAP HANA&#xff1f; 一、什么是 SAP HANA&#xff1f;二、什么是内存数据库&#xff1f;三、SAP HANA 有多快&#xff1f;四、SAP HANA 的十大优势五、SAP HANA 架构六、数据库设计七、数据库管理八、应用开发九、高级分析十、数据虚拟化 一、…

1-安装rabbitmq

rabbitmq官网&#xff1a; https://www.rabbitmq.com/docs/download 本机环境&#xff1a;mac&#xff0c;使用orbstack提供的docker 使用docker部署rabbitmq docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.13-management 然后报错&#xf…

信息安全服务规范包括哪些方面

信息安全服务规范是确保信息系统安全稳定运行的重要指导原则和操作准则。在信息化高速发展的今天&#xff0c;信息安全已经成为国家、企业乃至个人不可忽视的重要议题。因此&#xff0c;制定和执行信息安全服务规范对于保障信息安全、维护社会秩序具有重要意义。 信息安全服务规…

VScode+Zotero+Latex文献引用联动

一、VScodeLatex联动 1、VScode的安装 2、texlive.iso安装 可以参考以下&#xff0c;也可以忽略所有直接一步一步默认安装 https://zhuanlan.zhihu.com/p/442308176 3、Vscode的插件安装&#xff1a;【latex workshop】 4、打开设置&#xff0c;搜索json&#xff0c;然后点击…

MIT 6.S081---Lab: Multithreading

Uthread: switching between threads (moderate) 修改uthread.c&#xff0c;在thread中新增context字段&#xff1a; 修改uthread.c&#xff0c;在thread_create函数中新增以下逻辑&#xff1a; 修改uthread.c中的thread_switch函数定义&#xff1a; 修改uthread.c中的th…

你不得不知道的Python AI库

Python是人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;领域中使用最广泛的编程语言之一&#xff0c;拥有丰富的库支持各种AI和ML任务。本文介绍一些经典的Python AI库。 1. NumPy 简介&#xff1a;NumPy&#xff08;Numerical Python&#xff09;…

centos7中python3.10找不到openssl解决方案

如果有用其他方法安装了其他版本openssl&#xff0c;记得卸载其他的openssl&#xff0c;删除其他的openssl相关文件。 yum remove openssl* rm -rf ***下载最新版的openssl文件 按照官网安装方法安装openssl 官方安装地址https://docs.python.org/3/using/unix.html#on-linu…

代码随想录算法训练营第13天

239. 滑动窗口最大值 &#xff08;一刷至少需要理解思路&#xff09; 方法&#xff1a;暴力法 &#xff08;时间超出限制&#xff09; 注意&#xff1a; 代码&#xff1a; class Solution { public:vector<int> maxSlidingWindow(vector<int>& nums, int k…

数据分析方法(一)|认知数据

在进行数据分析时&#xff0c;很多人拿到数据之后没有头绪&#xff0c;在没有需求的情况下不知道从何做起&#xff0c;此时我们不妨先动起脑来理解数据。 分析数据之前&#xff0c;清晰的认识数据是非常重要的&#xff0c;通常我们可以从以下几个角度对数据进行深入了解&#x…

推荐5款极具效率的实用工具软件

​ 每次分享实用的软件,都会给人一种踏实和喜悦的感觉,这也是我热衷于搜集和推荐高效工具软件的原因。 1.个人日记软件——EDiary ​ EDiary是一款功能丰富的个人日记软件&#xff0c;用户可以在不联网的状态下使用&#xff0c;保护隐私。它支持日记、记事本、日历、事件提醒…

word如何实现不同章节显示不同页眉

一、问题描述 写论文时遇到如下情形&#xff0c;第二章页眉跟第一章一样&#xff0c;如下图 二、解决方法 在第二章前一页空白处&#xff0c;选择依次布局→分隔符→下一页&#xff0c;如下图 双击第二章页眉&#xff0c;进入页眉编辑状态&#xff0c;点击链接到前一节按钮&a…

chrome浏览器插件content.js和background.js还有popup都是什么,怎么通讯

popup 在用户点击扩展程序图标时&#xff08;下图中的下载图标&#xff09;&#xff0c;都可以设置弹出一个popup页面。而这个页面中自然是可以包含运行的js脚本的&#xff08;比如就叫popup.js&#xff09;。它会在每次点击插件图标——popup页面弹出时&#xff0c;重新载入。…

Spring之Bean详解

Spring之Bean详解 什么是Bean&#xff1f; 在Spring中&#xff0c;Bean是指由Spring容器管理的对象&#xff0c;这些对象是由Spring IoC容器负责创建、组装和管理的。Bean可以是Java类的实例&#xff0c;也可以是其他Spring管理的组件&#xff0c;例如数据源、事务管理器等。…

FPGA——三速自适应以太网设计(2)GMII与RGMII接口

FPGA——以太网设计&#xff08;2&#xff09;GMII与RGMII 基础知识&#xff08;1&#xff09;GMII&#xff08;2&#xff09;RGMII&#xff08;3&#xff09;IDDR GMII设计转RGMII接口跨时钟传输模块 基础知识 &#xff08;1&#xff09;GMII GMII:发送端时钟由MAC端提供 下…

NextJs教程系列(三):路由layout

可复用的布局 Next.js的layout是一个可复用的布局&#xff0c;不同的子页面可以共享布局容器&#xff0c;页面跳转时&#xff0c;layout容器不会重新渲染。 children props export default function RootLayout({ children }) {return (<html lang"en"><…

怎么做加密文件二维码?分享文件更安全

怎么做一个加密文件二维码&#xff1f;在日常的工作和生活中&#xff0c;通过扫描二维码来查看或者下载文件的方式&#xff0c;被越来越多的人所使用&#xff0c;一方面是二维码的成本低&#xff0c;另一方面有利于提升便捷性和用户体验。 为了保证内容的隐私性和安全性&#…

【XR806开发板试用】串口驱动JQ8900播放音乐

一、硬件连接 1.JQ8900引脚定义 通过阅读JQ8900的数据手册&#xff0c;可以了解到驱动JQ8900有许多种方式&#xff0c;IO驱动&#xff0c;一线串口驱动&#xff08;VPP&#xff09;&#xff0c;两线串口驱动&#xff08;RX&#xff0c;TX&#xff09;&#xff0c;这里我使用两…