TreeMap 源码解析

news2024/11/18 7:34:52

目录

一. 前言

二. 源码解析

2.1. 成员属性

2.2. 构造方法

2.3. 添加元素

2.4. 获取元素

2.5. 是否包含key

2.6. 删除元素

三. 总结


一. 前言

    TreeMap 基于红黑树实现,这为 TreeMap 保持键的有序性打下了基础。总的来说,TreeMap 的核心是红黑树,TreeMap因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序。

    Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序,key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)。

    TreeSet和TreeMap在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说TreeSet里面有一个TreeMap(适配器模式)。

二. 源码解析

TreeMap底层通过红黑树(Red-Black tree)实现,有以下两个理由:
有序性:红黑树是一种二叉查找树,父节点的 key 大于左子节点的 key ,小于右子节点的 key 。这样,就完成了 TreeMap 的有序的特性。
高性能:红黑树会进行自平衡,避免树的高度过高,导致查找性能下滑。这样,红黑树能够提供 logN 的时间复杂度。

2.1. 成员属性

// 排序器
private final Comparator<? super K> comparator;
  
// 红黑树根节点
private transient Entry<K,V> root;

/**
 * The number of entries in the tree
 * key-value 键值对数量
 */
private transient int size = 0;

/**
 * The number of structural modifications to the tree.
 * 修改次数
 */
private transient int modCount = 0;

root 属性,红黑树的根节点。其中,Entry 是 TreeMap 的内部静态类,代码如下:

/**
 * 颜色 - 红色
 */
private static final boolean RED = false;

/**
 * 颜色 - 黑色
 */
private static final boolean BLACK = true;

static final class Entry<K,V> implements Map.Entry<K,V> {
    /**
     * key 键
     */
    K key;
    /**
     * value 值
     */
    V value;
    /**
     * 左子节点
     */
    Entry<K,V> left;
    /**
     * 右子节点
     */
    Entry<K,V> right;
    /**
     * 父节点
     */
    Entry<K,V> parent;
    /**
     * 颜色
     */
    boolean color = BLACK;
}

2.2. 构造方法

public TreeMap() {
    comparator = null;
}

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

public TreeMap(SortedMap<K, ? extends V> m) {
	comparator = m.comparator();
	try {
		buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
	} catch (java.io.IOException cannotHappen) {
	} catch (ClassNotFoundException cannotHappen) {
	}
}

public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    // 添加所有元素
    putAll(m);
}

TreeMap():默认构造方法,不使用自定义排序,所以此时 comparator 为空。
TreeMap(Comparator<? super K> comparator):可传入 comparator 参数,自定义 key 的排序规则。

2.3. 添加元素

public V put(K key, V value) {
    // 记录当前根节点
    Entry<K,V> t = root;
    // <1> 如果无根节点,则直接使用 key-value 键值对,创建根节点
    if (t == null) {
        // <1.1> 校验 key 类型。
        compare(key, key); // type (and possibly null) check

        // <1.2> 创建 Entry 节点
        root = new Entry<>(key, value, null);
        // <1.3> 设置 key-value 键值对的数量
        size = 1;
        // <1.4> 增加修改次数
        modCount++;
        return null;
    }
    // <2> 遍历红黑树
    int cmp; // key 比父节点小还是大
    Entry<K,V> parent; // 父节点
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) { // 如果有自定义 comparator ,则使用它来比较
        do {
            // <2.1> 记录新的父节点
            parent = t;
            // <2.2> 比较 key
            cmp = cpr.compare(key, t.key);
            // <2.3> 比 key 小,说明要遍历左子树
            if (cmp < 0)
                t = t.left;
            // <2.4> 比 key 大,说明要遍历右子树
            else if (cmp > 0)
                t = t.right;
            // <2.5> 说明,相等,说明要找到的 t 就是 key 对应的节点,直接设置 value 即可。
            else
                return t.setValue(value);
        } while (t != null);  // <2.6>
    } else { // 如果没有自定义 comparator ,则使用 key 自身比较器来比较
        if (key == null) // 如果 key 为空,则抛出异常
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            // <2.1> 记录新的父节点
            parent = t;
            // <2.2> 比较 key
            cmp = k.compareTo(t.key);
            // <2.3> 比 key 小,说明要遍历左子树
            if (cmp < 0)
                t = t.left;
            // <2.4> 比 key 大,说明要遍历右子树
            else if (cmp > 0)
                t = t.right;
            // <2.5> 说明,相等,说明要找到的 t 就是 key 对应的节点,直接设置 value 即可。
            else
                return t.setValue(value);
        } while (t != null); // <2.6>
    }
    // <3> 创建 key-value 的 Entry 节点
    Entry<K,V> e = new Entry<>(key, value, parent);
    // 设置左右子树
    if (cmp < 0) // <3.1>
        parent.left = e;
    else // <3.2>
        parent.right = e;
    // <3.3> 插入后,进行自平衡
    fixAfterInsertion(e);
    // <3.4> 设置 key-value 键值对的数量
    size++;
    // <3.5> 增加修改次数
    modCount++;
    return null;
}

因为红黑树是二叉查找树,所以我们可以使用二分查找的方式遍历红黑树。循环遍历红黑树的节点,根据不同的结果,进行处理:
1. 如果当前节点比 key 小,则遍历左子树。
2. 如果当前节点比 key 大,则遍历右子树。
3. 如果当前节点比 key 相等,则直接设置该节点的 value 即可。
4. 如果遍历到叶子节点,无法满足上述情况,则说明我们需要给 key-value 键值对,创建 Entry 节点。如果比叶子节点小,则作为左子树;如果比叶子节点大,则作为右子树。
5. 如果无根节点,则直接使用 key-value 键值对,创建根节点。

public void putAll(Map<? extends K, ? extends V> map) {
	// 给size赋值
    int mapSize = map.size();
    // 这里首先判断映射是不是有序的
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
    	// 获取有序映射的比较器
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        // 这里的判断一般是false,因为c = null
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    super.putAll(map);
}

因为 TreeMap 是基于树的结构实现,所以无需考虑扩容问题。

2.4. 获取元素

public V get(Object key) {
    // 获得 key 对应的 Entry 节点
    Entry<K,V> p = getEntry(key);
    // 返回 value 值
    return (p == null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) { // 不使用 comparator 查找
    // Offload comparator-based version for sake of performance
    // 如果自定义了 comparator 比较器,则基于 comparator 比较来查找
    if (comparator != null)
        return getEntryUsingComparator(key);
    // 如果 key 为空,抛出异常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    // 遍历红黑树
    Entry<K,V> p = root;
    while (p != null) {
        // 比较值
        int cmp = k.compareTo(p.key);
        // 如果 key 小于当前节点,则遍历左子树
        if (cmp < 0)
            p = p.left;
        // 如果 key 大于当前节点,则遍历右子树
        else if (cmp > 0)
            p = p.right;
        // 如果 key 相等,则返回该节点
        else
            return p;
    }
    // 查找不到,返回 null
    return null;
}

final Entry<K,V> getEntryUsingComparator(Object key) { // 使用 comparator 查找
    @SuppressWarnings("unchecked")
    K k = (K) key;
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        // 遍历红黑树
        Entry<K,V> p = root;
        while (p != null) {
            // 比较值
            int cmp = cpr.compare(k, p.key);
            // 如果 key 小于当前节点,则遍历左子树
            if (cmp < 0)
                p = p.left;
            // 如果 key 大于当前节点,则遍历右子树
            else if (cmp > 0)
                p = p.right;
            // 如果 key 相等,则返回该节点
            else
                return p;
        }
    }
    // 查找不到,返回 null
    return null;
}

如果未自定义 comparator 比较器,则调用 #getEntry(Object key) 方法,使用 key 自身的排序,进行比较二分查找。
如果有自定义 comparator 比较器,则调用 #getEntryUsingComparator(Object key) 方法,使用 comparator 的排序,进行比较二分查找。

2.5. 是否包含key

public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

2.6. 删除元素

情况一,无子节点:直接删除父节点对其的指向即可。
例如说,叶子节点 5、11、14、18 。

情况二,只有左子节点:将删除节点的父节点,指向删除节点的左子节点。
例如说,节点 20 。可以通过将节点 15 的右子节点指向节点 19 。

情况三,只有右子节点:和情况二的处理方式一致。将删除节点的父节点,指向删除节点的右子节点。
例如说,节点 25。

情况四,有左子节点 + 右子节点。
这种情况,相对会比较复杂,因为无法使用子节点替换掉删除的节点。所以此时有一个巧妙的思路。我们结合删除节点 15 来举例。
1、先查找节点 15 的右子树的最小值,找到是节点 17 。
2、将节点 17 设置到节点 15 上。因为节点 17 是右子树的最小值,能够满足比节点 15 的左子树都大,右子树都小。这样,问题就可以变成删除节点 17 。
3、删除节点 17 的过程,满足情况三。将节点 19 的左子节点指向节点 18 即可。

public V remove(Object key) {
    // <1> 获得 key 对应的 Entry 节点
    Entry<K,V> p = getEntry(key);
    // <2> 如果不存在,则返回 null ,无需删除
    if (p == null)
        return null;

    V oldValue = p.value;
    // <3> 删除节点
    deleteEntry(p);
    return oldValue;
}

<1> 处,调用 #getEntry(Object key) 方法,获得 key 对应的 Entry 节点。
<2> 处,如果不存在,则返回 null ,无需删除。
<3> 处,调用 #deleteEntry(Entry<K,V> p) 方法,删除该节点。

private void deleteEntry(Entry<K,V> p) {
    // 增加修改次数
    modCount++;
    // 减少 key-value 键值对数
    size--;

    // If strictly internal, copy successor's element to p and then make p
    // point to successor.
    // <1> 如果删除的节点 p 既有左子节点,又有右子节点,
    if (p.left != null && p.right != null) {
        // <1.1> 获得右子树的最小值
        Entry<K,V> s = successor(p);
        // <1.2> 修改 p 的 key-value 为 s 的 key-value 键值对
        p.key = s.key;
        p.value = s.value;
        // <1.3> 设置 p 指向 s 。此时,就变成删除 s 节点了。
        p = s;
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // <2> 获得替换节点
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    // <3> 有子节点的情况
    if (replacement != null) {
        // Link replacement to parent
        // <3.1> 替换节点的父节点,指向 p 的父节点
        replacement.parent = p.parent;
        // <3.2.1> 如果 p 的父节点为空,则说明 p 是根节点,直接 root 设置为替换节点
        if (p.parent == null)
            root = replacement;
        // <3.2.2> 如果 p 是父节点的左子节点,则 p 的父子节的左子节指向替换节点
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        // <3.2.3> 如果 p 是父节点的右子节点,则 p 的父子节的右子节指向替换节点
        else
            p.parent.right = replacement;

        // Null out links so they are OK to use by fixAfterDeletion.
        // <3.3> 置空 p 的所有指向
        p.left = p.right = p.parent = null;

        // Fix replacement
        // <3.4> 如果 p 的颜色是黑色,则执行自平衡
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    // <4> 如果 p 没有父节点,说明删除的是根节点,直接置空 root 即可
    } else if (p.parent == null) { // return if we are the only node.
        root = null;
    // <5> 如果删除的没有左子树,又没有右子树
    } else { //  No children. Use self as phantom replacement and unlink.
        // <5.1> 如果 p 的颜色是黑色,则执行自平衡
        if (p.color == BLACK)
            fixAfterDeletion(p);

        // <5.2> 删除 p 和其父节点的相互指向
        if (p.parent != null) {
            // 如果 p 是父节点的左子节点,则置空父节点的左子节点
            if (p == p.parent.left)
                p.parent.left = null;
            // 如果 p 是父节点的右子节点,则置空父节点的右子节点
            else if (p == p.parent.right)
                p.parent.right = null;
            // 置空 p 对父节点的指向
            p.parent = null;
        }
    }
}

<1> 处,如果删除的节点 p 既有左子节点,又有右子节点,则符合我们提到的情况四。在这里,我们需要将其转换成情况三。
<1.1> 处,调用 #successor(Entry<K,V> t) 方法,获得右子树的最小值。这里,我们先不深究 #successor(Entry<K,V> t) 方法的具体代码,知道在这里的用途即可。
<1.2> 处,修改 p 的 key-value 为 s 的 key-value 键值对。这样,我们就完成 s 对 p 的替换。
<1.3> 处,设置 p 指向 s 。此时,就变成删除 s 节点了。此时,情况四就转换成了情况三了。
<2> 处,获得替换节点。此时对于 p 来说,至多有一个子节点,要么左子节点,要么右子节点,要么没有子节点。
<3> 处,有左子节点,或者右子节点的情况:
3.1 处,替换节点的父节点,指向 p 的父节点。
3.2.1> + 3.2.2> + 3.2.3> 处,将 p 的父节点的子节点,指向替换节点。
3.3> 处, 置空 p 的所有指向。
3.4> 处,如果 p 的颜色是黑色,则调用 #fixAfterDeletion(Entry<K,V> x) 方法,执行自平衡。
<4> 处,如果 p 没有父节点,说明删除的是根节点,直接置空 root 即可。
<5> 处,既没有左子树,又没有右子树的情况:
<5.1> 处,如果 p 的颜色是黑色,则调用 #fixAfterDeletion(Entry<K,V> x) 方法,执行自平衡。
<5.2> 处,删除 p 和其父节点的相互指向。

在前面,我们漏了一个 #successor(Entry<K,V> t) 静态方法,没有详细来看。获得 t 节点的后继节点,代码如下:

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    // <1> 如果 t 为空,则返回 null
    if (t == null)
        return null;
    // <2> 如果 t 的右子树非空,则取右子树的最小值
    else if (t.right != null) {
        // 先取右子树的根节点
        Entry<K,V> p = t.right;
        // 再取该根节点的做子树的最小值,即不断遍历左节点
        while (p.left != null)
            p = p.left;
        // 返回
        return p;
    // <3> 如果 t 的右子树为空
    } else {
        // 先获得 t 的父节点
        Entry<K,V> p = t.parent;
        // 不断向上遍历父节点,直到子节点 ch 不是父节点 p 的右子节点
        Entry<K,V> ch = t;
        while (p != null // 还有父节点
                && ch == p.right) { // 继续遍历的条件,必须是子节点 ch 是父节点 p 的右子节点
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

对于树来说,会存在前序遍历,中序遍历,后续遍历。对于二叉查找树来说,中序遍历恰好满足 key 顺序递增。所以,这个方法是基于中序遍历的方式,寻找传入 t 节点的后续节点,也是下一个比 t 大的节点。
<1> 处,如果 t 为空,则返回 null 。
<2> 处,如果 t 有右子树,则右子树的最小值,肯定是它的后继节点。在 #deleteEntry(Entry<K,V> p) 方法的 <1.1> 处,就走了这块代码分支逻辑。
<3> 处,如果 t 没有右子树,则需要向上遍历父节点结合 图 来理解。
简单来说,寻找第一个祖先节点 p 是其父节点的左子节点。因为是中序遍历,该节点的左子树肯定已经遍历完,在没有右子节点的情况下,需要找到其所在的“大子树”,成为左子树的情况。
例如说,节点 14 来说,需要按照 14 -> 13 -> 15 的路径,从而找到节点 15 是其后继节点。

三. 总结

TreeMap 按照 key 的顺序的 Map 实现类,底层采用红黑树来实现存储。
TreeMap 因为采用树结构,所以无需初始考虑像 HashMap 考虑容量问题,也不存在扩容问题。
TreeMap 的 key 不允许为空( null ),可能是因为红黑树是一颗二叉查找树,需要对 key 进行排序。

相比 HashMap 来说,TreeMap 不仅仅支持指定 key 的查找,也支持 key 范围的查找。当然,这也得益于 TreeMap 数据结构能够提供的有序特性。

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

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

相关文章

基础课10——人工智能的基础:大数据

人工智能和大数据是相互依存、相互促进的关系。 大数据是人工智能的重要基础&#xff0c;没有大数据&#xff0c;人工智能就难以发挥其作用。同时&#xff0c;人工智能也提供了处理和分析大数据的工具和方法&#xff0c;使得大数据能够得到更有效的利用。 在应用方面&#xf…

RK3568-适配at24c04模块

将at24c04模块连接到开发板i2c2总线上 i2ctool查看i2c2总线上都有哪些设备 UU表示设备地址的从设备被驱动占用,卸载对应的驱动后,UU就会变成从设备地址。at24c04模块设备地址 0x50和0x51是at24c04模块i2c芯片的设备地址。这个从芯片手册上也可以得知。A0 A1 A2表示的是模块对…

编程案例:ps5游戏厅计时计费管理系统软件

编程案例&#xff1a;ps5游戏厅计时计费管理系统软件 一、佳易王电玩PS5游戏厅计时计费软件部分功能简介&#xff1a; 1、计时计费功能 &#xff1a;开台时间和所用的时长直观显示&#xff0c;每3秒即可刷新一次时间。 2、销售商品功能 &#xff1a;商品可以绑定桌子最后一起…

将输入的字符串中小写字母改为大写字母

#include<stdio.h> void main() {char str[50];printf("input string:>");gets(str);int i 0;while (str[i] ! \0){if (str[i] > a && str[i] < z){str[i] str[i] - 32;}i;}printf("%s",str); } 不用scanf是因为scanf遇到空格和回…

Screw Puzzle - Nuts and Bolts

Unblock screws and untangle each twisted iron piece from the jigsaw of obstacles Support Email : 825407372qq.com

在线分享的批量智能管理技巧

在现代社会中&#xff0c;电脑已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;随着时间的推移&#xff0c;我们电脑中的文件越来越多&#xff0c;管理起来也变得越来越困难。为了提高工作效率&#xff0c;我们需要学会高效管理电脑文件。下面&#xff0c;我将分享一…

家居行业EDI:爱室丽Ashley EDI 项目案例

爱室丽Ashley&#xff08;Ashley Furniture Industries&#xff09;&#xff0c;是一家美国家居用品制造商和零售商。目前爱室丽Ashley通过两个分销渠道销售家居用品和配件&#xff1a;独立家具经销商和700多家Ashley Furniture HomeStore零售家具店&#xff0c;在中国、越南、…

【23真题】暴涨45分是专业课简单?还是太卷?

哈喽大家好&#xff0c;现在这个时间节点&#xff0c;有很多同学开始刷真题了&#xff01;所以23真题系列正式启动&#xff01;小马哥将全面发布23真题及详细解析&#xff01; 今天分享的是23年南京信息工程大学811的信号与系统试题及解析。南信大23年分数数涨45分&#xff0c…

yum--centos 和apt --ubuntu

centos安装软件 搜索语法&#xff1a;yum -y search 软件名称 安装软件前可以先去搜一下看看能用yum中有这个软件吗 安装语法&#xff1a;yum -y install 软件名称 写上 -y 意思是不用手动确认&#xff0c;直接安装 卸载语法&#xff1a;yum -y remove 软件名称 注…

BUUCTF LSB 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;解压得到一张png图片。 密文&#xff1a; 解题思路&#xff1a; 1、根据题目的提示&#xff0c;这道题涉及LSB隐写。使用StegSolve工具打开flag11.png文件&#xff0c;打开Analyse&#xff0…

数据抓取可以应用到哪些行业

随着互联网的发展&#xff0c;数据已经成为人们生活中不可或缺的一部分。数据抓取作为获取数据的重要手段之一&#xff0c;也被广泛应用于各个行业。本文将探讨数据抓取在各个行业中的应用。 首先&#xff0c;让我们来了解一下数据抓取的基本概念。数据抓取是指通过一定的技术…

产教融合共发展 | 开源网安高校合作战略再下一城

10月25日&#xff0c;开源网安继中山大学、电子科技大学、湖北大学、大连理工大学等高校之后再次建立校企合作项目&#xff0c;与绵阳城市学院签署战略合作协议&#xff0c;开源网安产学研协同发展战略迈出重要一步&#xff0c;目前已覆盖华北、东北、华南、华中、西南等区域&a…

Golang 自定义函数库(个人笔记)

1.用字符串连接切片元素&#xff08;类似php implode&#xff09; package mainimport ("fmt""strconv""strings" )func main() {data : []int{104, 101, 108, 108, 111}fmt.Println(IntSliceToString(data, ",")) }func IntSliceToS…

【开源】基于SpringBoot的海南旅游景点推荐系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

pk答题小程序怎么做?功能有哪些?为您揭秘!

“微信答题小程序&#xff08;仿头脑王者源码&#xff09;在线教育考试pk答题小程序”是一款专业性的答题小程序&#xff08;软著登记号&#xff1a;4078210号&#xff09;&#xff0c;主要功能有&#xff1a;个人每日答题、邀请好友一对一PK答题、排位升级PK答题、专题1V1pk答…

ASO优化之关于Google Play中的搜索引擎优化

在进行Google搜索时&#xff0c;被显示的结果数量淹没的情况并不少见&#xff0c;我们可以优化搜索引擎&#xff0c;排除特定单词或术语并获得更相关的结果。 1、关键词搜索。 排除与搜索查询不相关的关键词。通过使用搜索运算&#xff0c;我们可以指示Google忽略包含某些字词…

Lego-Laom算法深度解析

文章目录 参考链接系统概述1.点云分割模块1.1 生成距离图像1.2 地面点的初步筛选1.3 基于BFS的点云分割 2.特征检测2.1 特征提取2.1.1 点云去畸变 2.2 特征提取 3雷达里程计3.1 特征匹配3.2 位姿估计 4.地图构建4.1 全局地图4.2 局部地图 5.性能评价-对标LOAM算法5.1特征点数量…

Find My钱包|苹果Find My技术与钱包结合,智能防丢,全球定位

传统钱包就是人们随身携带装钱或者其他小件物品的东西。钱包可以把钱和银行卡、信用卡等货币工具收集到一起装在一个小包内&#xff0c;便于随身携带使用&#xff0c;通常有巴掌大小。如今钱包除了以上作用外还可以是随身装饰品或当家庭照片夹使用。 如今我们大多数用的是电子…

国产CAN总线收发芯片DP1042 兼容替换TJA1042

说明 1 简述 DP1042是一款应用于 CAN 协议控制器和物理总线之间的接口芯片&#xff0c;可应用于卡车、公交、小汽车、工业控制等领域&#xff0c;支持 5Mbps CAN FD 灵活数据速率&#xff0c;具有在总线与 CAN 协议控制器之间进行差分信号传输的能力&#xff0c;完全兼容“ISO…

使用BufferWriter进行文件的写入操作

public class BufferedWirter_ {public static void main(String[] args) throws IOException {String filepath "e:\\ABC.txt";//创建BufferedWrite对象BufferedWriter bufferedWriter new BufferedWriter(new FileWriter(filepath,true));//BufferedWriter 并没有…