深入研究Java的String常量池

news2024/10/1 3:32:19

文章目录

  • 一、StringTable
      • 分析一段代码
      • 示例一
      • 示例二
      • 示例三
  • 二、 intern
    • 1、StringTable位置
    • 2、StringTable 性能调优
    • 3、intern深入分析
      • 3.1 思考
      • 3.2 JDK6中的解释
      • 3.3 JDK7中的解释
      • 3.4 详细分析
      • 3.5 intern正确使用的例子
      • 3.6 intern使用不当的例子

一、StringTable

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象,就是说在生成字节码文件的时候并不会将常量写入串池中,而是执行到了才会写入(懒加载
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder + new String(xxx);
  • 对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。Javac 编译器会进行一个叫做 常量折叠的代码优化。常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一

  • 对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";

    并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

    • 基本数据类型( bytebooleanshortcharintfloatlongdouble)以及字符串常量。
    • final 修饰的基本数据类型和字符串变量
    • 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )

分析一段代码

public static void main(String[] args) throws ExecutionException, InterruptedException {
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    String s5 = "a" + "b";

    System.out.println(s3 == s4); // false
    System.out.println(s3 == s5); // true
}

1、初始的时候,StringTable【串池】为空,他是一个HashTable结构,不能扩容。

2、常量池中的信息,都会被加载到运行时常量池,这时abab都是常量池中的符号,还没有变为java字符串对象

3、执行String s1 = "a",字节码ldc #2会把符号a变为"a"字符串对象,然后看StringTable中是否存在字符串"a",如果不存在,就将这个字符串放入串池,如果存在,就直接使用串池中的字符串对象

4、执行String s2 = "b",字节码ldc #3会把符号b变为"b"字符串对象,然后看StringTable中是否存在字符串"b",如果不存在,就将这个字符串放入串池,如果存在,就直接使用串池中的字符串对象

5、执行String s3 = "ab",字节码ldc #4 会把符号ab变为字符串"ab"对象,然后看StringTable中是否存在字符串"ab",如果不存在,就将这个字符串放入串池,如果存在,就直接使用串池中的字符串对象

6、String s4 = s1 + s2,底层字节码执行的就是new StringBuilder().append("a").append("b").toString(),这里toString()就是执行的new String("ab"),因此s4指向的是堆区地址,而s3指向的是串池中的地址,因此s3s4不相等

7、String s5 = "a" + "b",字符串常量拼接,编译器底层会进行优化,javac在编译期间,结果就被确定为ab,在常量池中找到"ab"存在,所以s5指向的也是串池中的地址



示例一

1、当执行到ldc #2时,会把符号 a 变为"a"字符串对象,并放入串池中(hashtable结构 不可扩容)

2、当执行到 ldc #3 时,会把符号 b 变为 "b"字符串对象,并放入串池中

3、当执行到 ldc #4 时,会把符号 ab 变为 "ab" 字符串对象,并放入串池中

最终StringTable ["a", "b", "ab"]

注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且串池中不存在的时候(如 ldc #2)时,该字符串才会被创建并放入串池中。

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}

常量池中的信息,都会被加载到运行时常量池中,但这是abab 仅是常量池中的符号,还没有成为java字符串

0: ldc           #2                  // String a
2: astore_1
3: ldc           #3                  // String b
5: astore_2
6: ldc           #4                  // String ab
8: astore_3
9: return


示例二

1、通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()

2、最后的toString方法的返回值是一个新的字符串,但字符串的值和拼接的字符串一致,但是两个不同的字符串,一个存在于串池之中,一个存在于堆内存之中

public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; //new StringBuilder().append("a").append("2").toString()  new String("ab")
        System.out.println(s3 == s4); //false
		// 结果为false,因为s3是存在于串池之中,s4是由StringBuffer的toString方法所返回的一个对象,存在于堆内存之中
    }
}
Code:
  stack=2, locals=5, args_size=1
   0: ldc           #2              // String a
   2: astore_1
   3: ldc           #3              // String b
   5: astore_2
   6: ldc           #4              // String ab
   8: astore_3
   9: new           #5              // class java/lang/StringBuilder
  12: dup
  13: invokespecial #6              // Method java/lang/StringBuilder."<init>":()V
  16: aload_1
  17: invokevirtual #7              // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  20: aload_2
  21: invokevirtual #7              // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  24: invokevirtual #8              // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  27: astore        4
  29: return


示例三

1、使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已在编译期确定为ab,而创建ab的时候已经在串池中放入了"ab",所以s5直接从串池中获取值,和s3相等

2、使用拼接字符串变量的方法来创建新的字符串时,因为内容是变量,只能在运行期确定它的值,所以需要使用StringBuilder来创建

public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; //new StringBuilder().append("a").append("2").toString()  new String("ab")
        String s5 = "a" + "b";
        System.out.println(s5 == s3); //true
    }
}
Code:
stack=2, locals=6, args_size=1
   0: ldc           #2              // String a
   2: astore_1
   3: ldc           #3              // String b
   5: astore_2
   6: ldc           #4              // String ab
   8: astore_3
   9: new           #5              // class java/lang/StringBuilder
  12: dup
  13: invokespecial #6              // Method java/lang/StringBuilder."<init>":()V
  16: aload_1
  17: invokevirtual #7              // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  20: aload_2
  21: invokevirtual #7              // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  24: invokevirtual #8              // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  27: astore        4				 
  29: ldc           #4              // String ab  
  31: astore        5				// s5的ab初始化时直接从串池中获取字符串
  33: return


二、 intern

JDK1.8

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,就在常量池中创建一个指向该字符串对象的引用
  • 如果有该字符串对象,即字符串常量池中保存了对应的字符串对象的引用,则放入失败

无论放入是否成功,都会返回串池中的字符串对象


示例

1、String x = "ab",创建字符串"ab",将该字符串放入串池中,此时串池内容为["ab"]

2、String s = new String("a") + new String("b"),堆区创建字符串new String("a")new String("b")new String("ab"),将“a”"b"放入串池,此时串池内容["ab", "a", "b"]

3、String s2 = s.intern(),尝试将字符串s放入串池,但是现在串池已经有ab这个字符串了,所以放入失败,s指向堆区,但是返回的s2是串池的字符串"ab"

4、对于new String("a")这个语句创建了两个对象,第一个对象是"a"字符串存储在常量池,第二个对象是在Java堆中的String对象

public static void main(String[] args) {
    String x = "ab";
    String s = new String("a") + new String("b");

    String s2 = s.intern(); // 将这个字符串对象尝试放入串池,现在串池有,放入失败
    System.out.println(s2 == x); // true
    System.out.println(s == x); // false
}
public static void main(String[] args) {
    String s = new String("a") + new String("b");

    String s2 = s.intern(); // 将这个字符串对象尝试放入串池,现在串池没有,放入成功
    System.out.println(s2 == "ab"); // true
    System.out.println(s == "ab"); // true
}


JDK1.6

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,会将该字符串对象复制一份,再放入到串池中【因为JDK1.6中,字符串常量池在方法区】,该对象仍然指向堆区地址
  • 如果有该字符串对象,则放入失败

无论放入是否成功,都会返回串池中的字符串对象



JDK1.8测试

public static void main(String[] args) {
    String s1 = "a";
    String s2 = "b";
    String s3 = "a" + "b";
    String s4 = s1 + s2;
    String s5 = "ab";
    String s6 = s4.intern();
    System.out.println(s3 == s4); // false
    System.out.println(s3 == s5); // true
    System.out.println(s3 == s6); // true

    String x2 = new String("c") + new String("d");
    String x1 = "cd";
    x2.intern();
    System.out.println(x1 == x2); // false

    String x4 = new String("e") + new String("f");
    x4.intern();
    String x3 = "ef";
    System.out.println(x3 == x4); // true
}


1、StringTable位置

JDK1.6 时,StringTable是属于常量池的一部分,在方法区中,但是方法区只有在fullgc时垃圾才会被回收,回收效率太低。因此从JDK1.7往后,StringTable放在中,MinorGC就会触发垃圾回收,减轻无用字符串对内存的占用。StringTable在内存紧张时,会发生垃圾回收



2、StringTable 性能调优

  • StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间【避免了Hash冲突】

    -XX:StringTableSize=桶个数(最少设置为 1009 以上)

  • 考虑是否需要将字符串对象入池,可以通过 intern 方法减少重复入池,保证相同字符串在StringTable只保存一份


3、intern深入分析

以下内容参考自美团技术博客:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

3.1 思考

String s = new String("abc")这个语句创建了几个对象?

答案:上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。

看一段代码:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);
    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

打印结果

  • jdk6 下false false
  • jdk7 下false true

然后将s3.intern();语句下调一行,放到String s4 = "11";后面。将s.intern(); 放到String s2 = "1";后面

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);
    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
}

打印结果

  • jdk6 下false false
  • jdk7 下false false

3.2 JDK6中的解释

  1. jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA 堆 区域是完全分开的。
  2. 上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

3.3 JDK7中的解释

  1. 在 Jdk6 以及以前的版本中,字符串的常量池放在 Perm 区(JDK7及之前方法区的实现),Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError: PermGen space错误的。
  2. 所以在 jdk7 的版本中,字符串常量池已经从 Perm 区移到 Java 堆 区域。因为Perm 区域太小,同时在Perm区的字符串需要等到FullGC才能被回收。在JDK8中,方法区的实现采用了元空间matespace,放在了本地内存。

3.4 详细分析

  • 在第一段代码中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2个对象,是字符串常量池中的“1” 和 堆中 中的 s3引用指向的对象。中间还有2个匿名的new String(“1”) 我们不去讨论它们。此时s3引用对象内容是”11”,但此时常量池中是没有 “11”对象的。执行s3.intern()将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,在jdk6中,会在常量池中生成一个 “11” 的对象,但是在 jdk7 中常量池不在 Perm 区域了,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。 最后String s4 = "11"; 这句代码中”11”是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。

  • 再看 s 和 s2 对象。 String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同

  • 第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

  • 再看s3和s4,执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。


3.5 intern正确使用的例子

public static void main(String[] args) throws Exception {
        Integer[] DB_DATA = new Integer[10];
        Random random = new Random(10 * 10000);
        for (int i = 0; i < DB_DATA.length; i++) {
            DB_DATA[i] = random.nextInt();
        }
        long t = System.currentTimeMillis();
        for (int i = 0; i < MAX; i++) {
            //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])); 
            arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
        }
        System.out.println((System.currentTimeMillis() - t) + "ms");
        System.gc();
    }

分析结果

在这里插入图片描述

在这里插入图片描述

  • 通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。
  • arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern(); 在堆区创建一个字符串后,然后将字符串放入常量池,不管是否放进去,最后返回的都是常量池的对象,因此新创建的堆区对象是没有引用的,所以在youngGc的时候就会被回收。但如果不使用intren,那么每个在堆区创建的字符串对象都被引用的,在youngGC时也就不会被回收掉

3.6 intern使用不当的例子

在使用 fastjson 进行接口读取的时候,我们发现在读取了近70w条数据后,我们的日志打印变的非常缓慢,每打印一次日志用时30ms左右,如果在一个请求中打印2到3条日志以上会发现请求有一倍以上的耗时。在重新启动 jvm 后问题消失。继续读取接口后,问题又重现。

这是因为 fastjson 中对所有的 json 的 key 使用了 intern 方法,缓存到了字符串常量池中,这样每次读取的时候就会非常快,大大减少时间和空间。而且 json 的 key 通常都是不变的。这个地方没有考虑到大量的 json key 如果是变化的,那就会给字符串常量池带来很大的负担。因为字符串常量池大小是固定的为1009,如果放入的字符串过多,会造成hash冲突,导致之后的查询速度缓慢。
这个问题 fastjson 在1.1.24版本中已经将这个漏洞修复了。程序加入了一个最大的缓存大小,超过这个大小后就不会再往字符串常量池中放了。

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

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

相关文章

清华计算几何-ElementUniqueness, MinMap, MaxGap

ElementUniqueness问题(EU) 给出一组数给出一组数据,, 判断每个数都是唯一性的或者说判断是否存在重复的. 算法思路很简单, 快速排序 遍历判断: Max(O(nlogn) O(n)) O(nlogn)算法复杂度 代码实现 bool IsEelementUniqueness(const vector<float>& Elemnts) {vect…

【数学建模】 机器学习与统计模型

文章目录 机器学习与统计模型1. 统计分布与假设检验1.1 统计量与常见统计分布常见统计分布Python代码示例 1.2 正态性检验Shapiro-Wilk检验Python代码示例 1.3 独立性检验卡方检验Python代码示例 1.4 两组样本的差异性检验独立样本t检验Python代码示例 1.5 方差分析与事后多重比…

基于Django+MySQL球馆场地预约系统的设计与实现(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

学习STM32(1)--Keil软件安装与基本操作和Keil 软件高级应用

目录 1 引 言 2 实验目的 3 实验内容 3.1 认识单片机和STM32 3.2 安装、认识软件Keil和硬件STM32F103开发板 3.3 学习调试工程 3.4 Keil工程软件的配置 4 深入解析 思考一 1.以项目“12-GPIO输出—使用固件库点亮LED”为例子&#xff0c;认识本地工程文件夹&#xf…

2024下《信息安全工程师》案例简答题,刷这些就够了!

距离2024下半年软考已经越来越近了&#xff0c;不知道今年备考软考信息安全工程师的同学们开始准备了吗&#xff1f; 简答题一直是信安拿分的重点区域&#xff0c;对于许多考生来说&#xff0c;也往往是最具挑战性的部分。今天我就把那些重要的案例简答题类型整理汇总给大家&am…

招聘网站的头像如何上传?

1、某招聘网站 显示的是圆形的&#xff0c;但是上传却需要正方形的300300像素。 2、某某招聘网站 仅支持jpg格式&#xff0c;不支持png 3、某某招聘网站 支持1mb以内图片&#xff0c;格式gif,jpg,png都支持。比较友好 4、某招聘网站 这里仅支持200200像素&#xff0c;图片格…

贪心系列专题篇四

​​​​​​​ 目录 整数替换 解法一 解法二 俄罗斯套娃信封问题 堆箱子 可被三整除的最大和 距离相等的条形码 重构字符串 声明&#xff1a;接下来主要使用贪心法来解决问题&#xff01;&#xff01;&#xff01; 整数替换 题目 思路 下面将使用两种方法来解决这…

【论文阅读】—RTDETR

《DETRs Beat YOLOs on Real-time Object Detection》 最近&#xff0c;基于端到端DETR取得了显著的性能。然而&#xff0c;DETR的高计算成本限制了它们的实际应用&#xff0c;并阻碍了它们充分利用无后处理&#xff08;如非最大抑制&#xff08;NMS&#xff09;&#xff09;的…

Windows下如何像linux一样查看GPU使用情况

在linux下&#xff0c;只要使用 nvidia-smi即可看到服务器中每块卡的使用情况 但是在windows下该如何查看显卡的使用情况呢 通过网上学习发现&#xff0c;windows下有一个叫nvidia-smi.exe的程序 找到它所在的路径&#xff0c;然后在命令行中进入到这个路径&#xff0c;然后…

文件上传漏洞-HackBar使用

介绍 HackBar 是一个用于浏览器的扩展插件&#xff0c;主要用于进行网络渗透测试和安全评估。它提供了一系列方便的工具和功能&#xff0c;可以帮助用户执行各种网络攻击和测试&#xff0c;包括 XSS、SQL 注入、CSRF、路径穿越等 下载地址 可以到github上面去下载&#xff0…

傅里叶级数的C语言验证

目录 概述 1 收敛性 1.1 收敛定理 1.2 理解收敛定理 2 傅里叶级数的应用 2.1 问题描述 2.2 实现方法 3 方波函数的傅里叶验证&#xff08;C语言实现&#xff09; 3.1 方波函数 3.1.1 编写方波函数 3.1.2 程序函数验证 3.2 傅里叶级数函数实现 3.2.1 编写傅里叶级…

Android 实现左侧导航栏:NavigationView是什么?NavigationView和Navigation搭配使用

目录 1&#xff09;左侧导航栏效果图 2&#xff09;NavigationView是什么&#xff1f; 3&#xff09;NavigationView和Navigation搭配使用 4&#xff09;NavigationView的其他方法 一、实现左侧导航栏 由于Android这边没有直接提供左侧导航栏的控件&#xff0c;所以我尝试了…

Frida Hook String构造函数

前言 在实际Android应用开发中&#xff0c;无论是使用多么复杂的算法对字符串进行加密&#xff0c;然而开发者常常会构造出字符串的实例。因此&#xff0c;我们可以通过使用Frida hook String类的构造函数来追踪这些实例的构造位置&#xff0c;然后可以通过构造实例的地方栈回…

Java GUI制作双人对打游戏源码以及应用程序分享

文章目录 前言一、可执行程序二、源码以及图片资源总结 前言 在之前的文章展示了如何构建一款双人对打游戏&#xff0c;博文链接如下&#xff1a; https://blog.csdn.net/qq_43646281/article/details/137748943?spm1001.2014.3001.5501 感兴趣的小伙伴可以再去回顾一下: 原文…

一分钟了解请求转发与重定向

目录 请求转发 重定向 请求转发 为了更好的了解请求转发&#xff0c;我们可以画出关系图&#xff1a; 请求转发的特征: 地址栏的地址不发生改变, 是请求的资源的url请求转发中, 是一次请求request域有效请求转发是在服务器端转发的, 请求转发资源只能是服务器内部资源 重定…

Pointnet++改进即插即用系列:全网首发FMB自调制特征聚合|即插即用,提升特征提取模块性能

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入FMB,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三 1.理论介…

DS18B20数字温度传感器操作解析

文章目录 引言特点工作原理引脚说明配置寄存器温度寄存器时序初始化时序写时序读时序 引言 DS18B20 是一种广泛使用的数字温度传感器&#xff0c;具有高精度和易用性。是Dallas Semiconductor公司&#xff08;现为Maxim Integrated公司&#xff09;生产的单总线数字温度传感器…

Typora+PicGo-Core(command line)+Gitee 实现上传图片到图床(看这一文就够)

前言 ​ 对于喜欢写Markdown文档的人来说&#xff0c;Typora无疑是一个写作利器&#xff0c;它有别于其他的Markdown软件&#xff0c;不是一边编辑一边渲染&#xff0c;而是即写即渲染&#xff0c;这样对于浏览md文件也非常友好。此外Typora还支持更换主题&#xff0c;在其官网…

机器学习 第10章-降维与度量学习

机器学习 第10章-降维与度量学习 10.1 k近邻学习 k近邻(k-Nearest Neighbor,简称kNN)学习是一种常用的监督学习方法其工作机制非常简单:给定测试样本&#xff0c;基于某种距离度量找出训练集中与其最靠近的k个训练样本&#xff0c;然后基于这k个“邻居”的信息来进行预测。通…

YOLOv5改进 | 多尺度特征提取 | 结合多样分支块及融合的高级设计(CVPR2021)

YOLOv5改进 | 多尺度特征提取 | 结合多样分支块及融合的高级设计&#xff09; 本文介绍论文原理介绍网络代码多种yaml设置网络测试及实验结果 本文介绍 YOLOv5&#xff08;You Only Look Once&#xff09;以其高效、准确的实时目标检测性能被广泛应用。然而&#xff0c;随着视觉…