当前位置: 首页 > news >正文

第十三章 StringTable

String 的基本特性

  • String:字符串,使用一对 “” 引起来表示
// 两种定义方式
String s1 = "atguigu"; // 字面量的定义方式
String s2 = new String("hello");
  • String 声明为 final 的,不可被继承
  • String 实现了 Serializable 接口:表示字符串支持序列化的。
  • String 实现了 Comparable 接口:表示 String 可以比较大小
  • String 在 JDK 8 及以前内部定义了 final char[] value 用于存储字符串数据。JDK 9 时改为了 byte[]
  • String 代表不可变的字符序列。简称:不可变性
    • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行复制。
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值
    • 当调用 String 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值
  • 通过字面量的方式(区别于 new)给一个字符串复制,此时的字符串值声明在字符串常量池中。
  • 字符串常量池中是不会存储相同内容的字符串的。
    • String 的 String Pool 是一个固定大小的 Hashtable,默认值大小长度是 1009.如果放进 String Pool 的 String 非常多,就会造成 Hash 冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 String.intern 时性能会大幅下降。
    • 使用 -XX:StringTableSize 可设置 StringTable 的长度
    • 在 JDK 6 中 StringTable 是固定的,就是 1009 的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize 设置没有要求。
    • 在 JDK 7 中,StringTable 的长度默认值是 60013
    • JDK 8 开始,设置 StringTable 的长度的话,1009 是可设置的最小值

[外链图片转存中…(img-9uBEhwJD-1720061415931)]
[外链图片转存中…(img-P7npCvJT-1720061415932)]
String 在 JDK 9 中存储结构的变更
官网说明:JEP 254: Compact Strings
:::warning

Motivation

The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

动机

String类的当前实现将字符存储在字符数组中,每个字符使用两个字节(十六位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,此外,大多数String对象仅包含Latin-1 字符。此类字符只需要一个字节的存储空间,因此此类String对象的内部字符数组中一半的空间将未使用。

Description

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.
String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic string operations.
This is purely an implementation change, with no changes to existing public interfaces. There are no plans to add any new public APIs or other interfaces.

描述

我们建议将String类的内部表示形式从UTF-16字符数组更改为字节数组加上编码标志字段。新的字符串类将根据字符串的内容存储编码为ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。(即可以根据字符串的内容存储编码来使用不同的存储方式)
AbstractStringBuilder、StringBuilder和StringBuffer等字符串相关类将更新为使用相同的表示形式,HotSpot VM的内在字符串操作也是如此。
这纯粹是一种实现更改,对现有的公共接口没有更改。没有计划添加任何新的公共API或其他接口。
:::
结论:
String 再也不用 char[] 来存储了,改成了 byte[] 加上编码标记,节约了一些空间。而StringBuilder和StringBuffer等字符串相关类也更新为使用相同的表示形式。HotSpot VM的内在字符串操作也是如此。

package chapter13;import org.junit.Test;public class StringTest1 {@Testpublic void test1() {String s1 = "abc"; // 字面量定义的方式,"abc"存储在字符串常量池中String s2 = "abc";System.out.println(s1 == s2); // trueSystem.out.println(s1);System.out.println(s2);}
}

测试结果:
[外链图片转存中…(img-QpfZ90OA-1720061415932)]

package chapter13;import org.junit.Test;public class StringTest1 {@Testpublic void test1() {String s1 = "abc"; // 字面量定义的方式,"abc"存储在字符串常量池中String s2 = "abc";s1 = "hello";System.out.println(s1 == s2); // 判断地址:falseSystem.out.println(s1);System.out.println(s2);}@Testpublic void test2() {String s1 = "abc";String s2 = "abc";s2 += "def";System.out.println(s2); // abcSystem.out.println(s1); // abcdef}@Testpublic void test3() {String s1 = "abc";String s2 = s1.replace('a', 'm');System.out.println(s1); // abc,体现了 String 的不可变性System.out.println(s2); // mbc}
}

例题:

package chapter13;public class StringExer {String str = new String("good");char[] ch = {'t','e','s','t'};public void change(String str, char[] ch){str = "test ok";ch[0] = 'b';}public static void main(String[] args) {StringExer ex = new StringExer();ex.change(ex.str, ex.ch);System.out.println(ex.str); // goodSystem.out.println(ex.ch); // best}
}

结果输出:
[外链图片转存中…(img-zkC9T4MG-1720061415933)]
当主线程执行到方法 ex.chang(ex.str, ex.ch) 时,change 方法入栈,其局部变量表中包含两个变量,分别是 str 和 ch,主线程将成员变量 str 的地址值复制了一份传递给了 change 方法的局部变量 str,此时如果输出打印 str,其值将会和成员变量 str 一样,均为 good。但是 change 方法又重新将 “test ok” 赋值给了 str,由于“test ok”之前在字符串常量池中并不存在,所以虚拟机会将“test ok”添加到字符串常量池中,并将地址赋值给 str,此时 str 的值就变成了 “test ok”。随着 change 方法执行结束,虚拟机栈会将其出栈,此时的局部变量 str 也就失去了作用,所以在打印成员变量 str 的值时没有发生改变。要想改变成员变量 str 的值,则 str = “test ok”; 应该改为 this.str = “test ok”;。

String 的内存分配

  • 在 Java 语言中有 8 中基本数据类型和一种比较特殊的类型 String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
  • 常量池就类似于一个 Java 系统级别提供的缓存。8 中基本数据类型的常量池都是系统协调的,String 类型的常量池比较特殊,它的主要使用方法有两种。
    • 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
      • 比如:String info = “atguigu.com”;
    • 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern() 方法
    • 使用 new String(“hello”) 方式创建的对象,"hello"会被存放在堆中常量池之外的地方。
  • Java 6 及以前,字符串常量池存放在永久代
  • Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到 Java 堆中。
    • 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时进需要调整堆大小就可以了。
    • 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由重新考虑在 Java 7 中使用 String.iintern()。
  • Java 8 元空间,字符串常量在堆。

StringTable 为什么要调整?
官网:Java SE 7 Features and Enhancements
:::warning
Area: HotSpot
Synopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
RFE: 6962931
:::

  • PermSize(永久代)比较小,如果大量创建字符串,永久代很容易报 OOM
  • 永久代垃圾回收频率低

String 的基本操作

案例一

package chapter13;public class StringTest4 {public static void main(String[] args) {System.out.println();System.out.println("1");System.out.println("2");System.out.println("3");System.out.println("4");System.out.println("5");System.out.println("6");System.out.println("7");System.out.println("8");System.out.println("9");System.out.println("10"); //System.out.println("1");System.out.println("2");System.out.println("3");System.out.println("4");System.out.println("5");System.out.println("6");System.out.println("7");System.out.println("8");System.out.println("9");System.out.println("10"); //}
}

[外链图片转存中…(img-r7is8Wqd-1720061415933)]
以 debug 模式启动程序:
第一个断点处:
[外链图片转存中…(img-W6SeIfEo-1720061415934)]
第二个断点处:
[外链图片转存中…(img-Iy4R4zel-1720061415934)]
继续单步执行
[外链图片转存中…(img-kxdJeTuy-1720061415934)]
直接跳到下一个断点:
[外链图片转存中…(img-Q97JAr8m-1720061415935)]
继续单步执行
[外链图片转存中…(img-jM3dWsPA-1720061415935)]
继续单步执行
[外链图片转存中…(img-3VBlVvZq-1720061415935)]
直接跳至最后一个断点
[外链图片转存中…(img-joBE9YkB-1720061415935)]
代码执行结束后,字符串个数仍为 1160,未再发生变化:
[外链图片转存中…(img-h4tofune-1720061415936)]

总结:Java 语言规范里要求完全相同的字符串字面量,应该包含同样的 Unicode 字符序列(包含同一份码点序列的常量),并且必须是指向同一个 String 类实例。

案例二

[外链图片转存中…(img-z8uLgJqC-1720061415936)]
内存分析:
[外链图片转存中…(img-hIbjFcT9-1720061415937)]
[外链图片转存中…(img-rI8hY8To-1720061415937)]
第 7 行代码创建了一个字符串。该字符串被存放在堆空间中字符串常量池中,并且在 foo() 栈空间中创建了一个指向该字符串的一个引用。

字符串拼接操作

1、常量与常量的拼接结果在常量池,原理是编译器优化
2、常量池中不会存在相同内容的常量
3、只要其中有一个是变量,结果就在堆中。变量拼接的原理是 StringBuilder
4、如果拼接的结果是调用 intern() 方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。

package chapter13;import org.junit.Test;public class StringTest5 {@Testpublic void test1() {String s1 = "a" + "b" + "c";String s2 = "abc"; // "abc" 一定是放在字符串常量池中的,将此地址赋值给 s2/*** 最终 .java 编译成 .class,再执行 .class* 编译期就已经确定下来* 验证方式:可以直接在idea中查看编译后.class文件,其显示的内容如下:* String s1 = "abc";* String s2 = "abc";* 也可以通过 jclasslib 进行验证 */System.out.println(s1 == s2); // trueSystem.out.println(s1.equals(s2)); // true}@Testpublic void test2() {String s1 = "javaEE";String s2 = "hadoop";String s3 = "javaEEhadoop";String s4 = "javaEE" + "hadoop";// 如果拼接符号的前后出现了 new,则需要在堆空间中 new String(),具体的内容为拼接后的结果String s5 = s1 + "hadoop";String s6 = "javaEE" + s2;String s7 = s1 + s2;System.out.println(s3 == s4); // trueSystem.out.println(s3 == s5); // falseSystem.out.println(s3 == s6); // falseSystem.out.println(s3 == s7); // falseSystem.out.println(s5 == s6); // falseSystem.out.println(s5 == s7); // falseSystem.out.println(s6 == s7); // false// intern():判断字符串常量池中是否存在 javaEEhadoop 值,如果存在,则返回常量池中 javaEEhadoop 的地址值// 如果字符串常量池中不存在 javaEEhadoop,则向字符串常量池中添加 javaEEhadoop,并返回 javaEEhadoop 的地址值String s8 = s6.intern();System.out.println(s3 == s8); // true}@Testpublic void test3() {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4); // false}@Testpublic void test4() {final String s1 = "a";final String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4); // true}
}

test1() 方法分析:
直接在idea中查看编译后的 SpringTest5.class文件
[外链图片转存中…(img-E2vqETLQ-1720061415937)]
使用 jclsslib,可以看到两组命令完全一致。
[外链图片转存中…(img-FGk2v9nw-1720061415937)]
test3() 分析:
[外链图片转存中…(img-TWh5g7kR-1720061415938)]
s1 + s2 的执行细节如下:
:::warning
StringBuilder s = new StringBuilder(); (这里变量 s 是为了方便理解而临时定义的)
s.append(“a”);
s.append(“b”);
s.toString() --> 约等于 new String(“ab”);
:::
补充:在 JDK5.0 以后使用的 StringBuilder,在 JDK5.0 之前使用的 StringBuffer
test4() 分析:
字符串拼接操作不一定使用的是 StringBuilder !
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非 StringBuilder 的方式。
在定义类、方法、基本数据类型、引用数据类型时,能使用 final 建议使用。
变量 s1、s2 被 final 修饰后则变成了常量, final 在编译的时候就会分配了,准备阶段会显示初始化。

拼接操作与 append 操作的效率对比

package chapter13;import org.junit.Test;public class StringTest5 {@Testpublic void test6() {long start = System.currentTimeMillis();
//        method1(100000); //1804method2(100000); // 3long end = System.currentTimeMillis();System.out.println("花费的时间为:" + (end - start));}private void method2(int highLevel) {StringBuilder src = new StringBuilder();for (int i = 0; i < highLevel; i++) {src.append("a");// 每次循环都会创建一个 StringBuilder、String}}private void method1(int highLevel) {String src = "";for (int i = 0; i < highLevel; i++) {src = src + "a";}}
}

测试 method1 :将 test6() 方法中的 method2() 注释掉
[外链图片转存中…(img-uYU75FcF-1720061415938)]
测试 method12:将 test6() 方法中的 method1() 注释掉
[外链图片转存中…(img-dhPHwtsv-1720061415938)]
总结:
通过 StringBuilder 的 append() 的方式添加字符串的效率要远高于使用 String 的字符串拼接方式。
原因:

  • StringBuilder 的 append() 的方式,自始至终只创建了一个 StringBuilder 的对象。使用 String 的字符串拼接方式,创建了多个 StringBuilder 和 String 对象
  • 使用 String 的字符串拼接方式,内存中由于创建了较多的 StringBuilder 和 String 的对象,内存占用更大;如果进行 GC,需要花费额外的时间。

使用 StringBuilder 的 append() 的方式可继续改进:
在实际开发中,如果基本确定需要添加所有的字符串长度不高于某个限定值 highLevel 的情况下,建议使用构造器 StringBuilder s = new StringBuilder(highLevel); // new char[highLevel]。
StringBuilder 底层是使用得 char 型数组存储字符串的,如果使用如果使用空参构造器,append() 方法会确认 append 进来的字符串是否超出了当前字符数组的容量,如果超出了就需要进行扩容。扩容次数过多了,效率就会下降。所以在一开始就设置好字符数组的大小,不用每次进行扩容。即使设置的容量不够,append 也会自动进行扩容的。

intern() 的使用

概述

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

  • 比如:String myInfo = new String(“I love atguigu”).intern();

也就是说,如果在任意字符上调用 String.intern 方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是 true:
:::warning
(“a” + “b” + “c”).intern() == “abc”
:::
通俗点讲,Interned String 就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)。
如何保证变量 s 指向的是字符串常量池中的数据呢?
有两种方式:

  • 方式一:字面量定义的方式
    • String s = “hello”;
  • 方式二:调用 intern 方法
    • String s = new String(“hello”).intern();
    • String s = new StringBuilder(“hello”).toString().intern();

JDK6 VS JDK7/8 中的intern

示例一

问题:

  • new String(“ab”) 会创建几个对象?
package test.chapater13;public class StringNewTest {public static void main(String[] args) {String str = new String("ab");}
}

两个。一个对象是 new 关键字在堆空间中创建的。另一个对象是字符串常量池中的对象,字节码指令 ldc。 [外链图片转存中…(img-1YCwyQ4C-1720061415938)]

  • new String(“a”) + new String(“b”) 会创建几个对象?
package chapter13;public class StringNewTest {public static void main(String[] args) {String str = new String("a") + new String("b");}
}

[外链图片转存中…(img-fGlwdgiK-1720061415938)]
根据字节码文件分析:
对象 1 : new StringBuilder()
对象 2 :new String(“a”)
对象 3 :字符串常量池中的 “a”
对象 4 :new Stirng(“b”)
对象 5 :字符串常量池中的 “b”
对象 6 :StringBuilder 的 toString() 方法–> new String(“ab”)
StringBuilder 的 toString() 方法的调用并没有在字符串常量池中生成 “ab”
[外链图片转存中…(img-G6X5i813-1720061415938)]

示例二

package test.chapater13;public class StringIntern1 {public static void main(String[] args) {String s = new String("1");s.intern(); // 调用此方法之前,字符串常量池中已经存在”1“String s2 = "1";System.out.println(s == s2); // jdk6: false  jdk8: falseString s3 = new String("1") + new String("1");// s3变量记录的地址为:new String("11")// 执行完上一行代码以后,字符串常量池中,并不存在"11"s3.intern(); // 在字符串常量池中生成"11"。// jdk6 : 创建了一个新的对象"11",也就有了新的地址// jdk7 : 此时常量池中并没有创建"11",而是创建了一个指向那个堆空间中new String("11")的地址String s4 = "11"; // s4 变量记录的地址:使用的是上一行代码执行时,在常量池中生成的"11"的地址System.out.println(s3 == s4); // jdk6: false jdk8: true}
}

JDK6 环境下测试结果:
[外链图片转存中…(img-L6gOWZkY-1720061415939)]
变量 s 指向的是堆空间中的 String 对象,由于字符串常量池中并没有对象 “1”,new 在执行时,同时向字符串常量池中添加了对象"1",变量 s2 指向的是字符串常量池中的对象"1"。所以 s == s2 的结果为 false。s.intern() 方法加不加无所谓, new String() 操作会将创建的字符串添加到字符串常量池中。
变量
s3 最终指向了堆中对象"11",根据示例一可以得知,new String(“1”) + new String(“1”); 代码最终并没有把对象"11" 添加进字符串常量池中。s3.intern() 则会将 “11” 添加进字符串常量池。s4 指向的是字符串常量池中的对象 “11”。所以 s3 == s4 的结果为false。
[外链图片转存中…(img-RltUbUdW-1720061415939)]
JDK8 环境下测试结果:
[外链图片转存中…(img-uyLuzPrh-1720061415939)]
jdk8 中 s3.intern() 会将堆中 “11” 对象的地址拷贝一份到字符串常量池中。s4 = “11” 检测到字符串常量池中已经存在 “11”,会把字符串常量池中的 “11” 的地址赋给变量 s4,即 s4 最终会指向堆中的 “11”。
[外链图片转存中…(img-kI9hv89K-1720061415939)]

示例三

package chapter13;public class StringIntern1 {public static void main(String[] args) {String s3 = new String("1") + new String("1");// 执行完上一行代码以后,字符串常量池中,不存在"11"String s4 = "11"; // 在字符串常量池中生成对象"11"String s5 = s3.intern();System.out.println(s3 == s4);System.out.println(s5 == s4);}
}

执行结果:
[外链图片转存中…(img-K9POizS2-1720061415939)]

总结 String 的 intern() 的使用

  • JDK 1.6 中,将这个字符串对象尝试放入字符串常量池。
    • 如果字符串常量池中有,则并不会放入,返回已有的字符串常量池中的对象的地址
    • 如果没有,会把此对象复制一份,放入字符串常量池,并返回字符串常量池中的对象地址。
  • JDK 1.7 起,将这个字符串对象尝试放入字符串常量池
    • 如果字符串常量池中有,则并不会放入,返回已有的字符串常量池中的对象的地址
    • 如果没有,则会把对象的引用地址复制一份,放入字符串常量池,并返回字符串常量池中的引用地址

[外链图片转存中…(img-lRIJJP59-1720061415939)]
s2 == “ab” ,最终比较的是地址值,而"ab"的地址值则是指向了字符串常量池中的"ab"。

Intern 的空间效率测试

package chapter13;public class StringIntern2 {static final int MAX_COUNT = 1000 * 1000;static final String[] arr = new String[MAX_COUNT];public static void main(String[] args) {Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};long start = System.currentTimeMillis();for (int i = 0; i < MAX_COUNT; i++) {
//            arr[i] = new String(String.valueOf(data[i % data.length]));arr[i] = new String(String.valueOf(data[i % data.length])).intern();}long end = System.currentTimeMillis();System.out.println("花费的时间为:" + (end - start));try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}
}

未使用 intern 方法:
[外链图片转存中…(img-Lc0O47Qp-1720061415939)]
使用 intern 方法:
[外链图片转存中…(img-VhAPUHrW-1720061415940)]
可以看到在未使用 intern 方法的情况,内存的占用率要远大于使用 intern 方法的。这是因为数组 data 中的值是固定的,for 循环体是不断向字符数组中赋重复值,只不过数组索引位置不同而已。也就是说,字符串常量池中早就已经生成了"1"~"10"的对象。

arr[i] = new String(String.valueOf(data[i % data.length])).intern();

这段代码的执行逻辑是这样的,首先在堆中生成 new String(String.valueOf(data[i % data.length]) 的对象,生成后判断字符串常量池中是否存在,如果存在,则将字符串常量池中的对象地址返回给 arr[i]。此时, new String(String.valueOf(data[i % data.length]) 这个对象便没有了引用,于是垃圾回收器便可以将其回收。这就是为什么使用 intern 方法后,内存空间占用较小的原因。
String str = new String(“abc”):str直接用new String(“abc”)创建,"abc"这字符串在一出现就自动创建成对象存放到常量池中,所以常量池里面存放的是"abc"字符串的引用,并不是str创建的对象的引用。

结论:对于程序中大量存在的字符串,尤其其中存在很多复杂字符串时,使用 intern() 可以节省内存空间。

.StringTable 的垃圾回收

参数设置:
:::warning
-Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
:::

G1 中的 String 去重操作

  • 背景:对许多 Java 应用(有大的也有小的)做的测试得出以下结果:
    • 堆存活数据集合里面 String 对象占了 25%
    • 堆存活数据集合里面重复的 String 对象有 13.5%
    • String 对象的平均长度是 45
  • 许多大规模的 Java 应用的瓶颈在于内存,测试表明,在这些类型的应用里面,Java 堆中存活的数据集合差不多 25% 是 String 对象。更进一步,这里面差不多一半 String 对象是重复的,重复的意思是说:string1.equals(string2)=true。堆上存在重复的 String 对象必然是一种内存的浪费。这个项目将在 G1 垃圾收集器中实现自动持续对重复的 String 对象进行去重,这样就能避免浪费内存。
  • 实现
    • 当垃圾收集器工作的时候,会访问堆上存活的对象。对每一个访问的对象都会检查是否是候选的要去重的 String 对象
    • 如果是,把这个对象的一个引用插入到队列中等待后续的处理。一个驱虫的线程在后台运行,处理这个队列。处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的 String 对象。
    • 使用一个 hashtable 来记录所有的被 String 对象使用的不重复的 char 数组。当去重的时候,会查这个 hashtable,来看堆上是否已经存在一个一模一样的 char 数组。
    • 如果存在,String 对象会被调整引用那个数组,释放对原来的数组的引用,最终会被垃圾收集器回收掉。
    • 如果查找失败,char 数组会被插入到 hashtable,这样以后的时候就可以共享这个数组了。
  • 命令行选项
    • UseStringDeduplication(bool):开启 String 去重,默认是不开启的,需要手动开启。
    • PrintStringDeduplicationStatistics(bool):打印详细的去重统计信息
    • StringDeduplicationAgeThreshold(uintx);达到这个年龄的 String 对象被认为是去重的候选对象

相关文章:

第十三章 StringTable

String 的基本特性 String&#xff1a;字符串&#xff0c;使用一对 “” 引起来表示 // 两种定义方式 String s1 "atguigu"; // 字面量的定义方式 String s2 new String("hello");String 声明为 final 的&#xff0c;不可被继承String 实现了 Serializ…...

Adobe Acrobat添加时间戳服务器

文章目录 前言一、Adobe Acrobat添加时间戳服务器1.打开Adobe Acrobat软件2.点击【菜单】→ 【首选项】3.点击【安全性】→【更多】4.点击【新建】5.输入【名称】→【服务器URL】 前言 一、Adobe Acrobat添加时间戳服务器 1.打开Adobe Acrobat软件 2.点击【菜单】→ 【首选项…...

数据库管理-第217期 Oracle的高可用-02(20240704)

数据库管理217期 2024-07-04 数据库管理-第217期 Oracle的高可用-02&#xff08;20240704&#xff09;1 GDS简介2 GDS架构2.1 全局数据服务池2.2 全局数据服务域2.3 全局服务管理2.4 全局数据服务目录2.5 Oracle通知服务 3 GDS简图3.1 负载均衡3.2 只读服务失败转移3.3 多主复制…...

搭建基础库~

前言 项目中会用到工具库、函数库以及一些跟框架绑定的组件&#xff0c;如果这些基础模块每个项目都实现一套&#xff0c;维护起来那真的头大&#xff0c;你说呢&#x1f609; 搭建流程 准备工作 创建文件夹myLib、安装Git以及pnpm 目录大概就系这样子&#xff1a; myLib ├…...

深入了解Linux中的udhcpc:动态主机配置协议客户端

目录 什么是udhcpc&#xff1f;安装udhcpc配置网络接口使用udhcpc获取IP地址配置静态IP地址自定义udhcpc脚本高级选项udhcpc常见问题及排查方法1. 无法获取IP地址2. DNS配置不正确3. IP地址冲突4. 无法连接到默认网关5. 无法执行自定义脚本 在Linux系统中&#xff0c;网络配置是…...

O2OA(翱途) 开发平台之HTTP端口规划

O2OA(翱途) 开发平台[下称O2OA开发平台或者O2OA]采用相对灵活的系统架构&#xff0c;支持三种服务器运行的方式。本篇主要阐述合并服务运行独立服务运行代理端口运行三种服务器运行方式。 一、先决条件&#xff1a; 1、O2Server服务器正常运行&#xff0c;系统安装部署请参考文…...

以创新思维驱动下的盲盒小程序:重塑用户体验

一、引言 在数字化浪潮的推动下&#xff0c;小程序以其便捷、高效、无需下载安装的特性&#xff0c;迅速成为移动互联网的新宠。其中&#xff0c;盲盒小程序以其独特的玩法和惊喜感&#xff0c;吸引了大量用户的关注和参与。然而&#xff0c;随着市场竞争的加剧&#xff0c;如…...

设计资料:520-基于ZU15EG 适配AWR2243的雷达验证底板 高速信号处理板 AWR2243毫米波板

基于ZU15EG 适配AWR2243的雷达验证底板 一、板卡概述 本板卡系北京太速科技自主研发&#xff0c;基于MPSOC系列SOC XCZU15EG-FFVB1156架构&#xff0c;搭载两组64-bit DDR4&#xff0c;每组容量32Gb&#xff0c;最高可稳定运行在2400MT/s。另有1路10G SFP光纤接口、1路40G…...

晋级国赛!卓翼飞思技术引领,助力辽宁赛区机器人及人工智能大赛圆满收官

近日&#xff0c;第二十六届中国机器人及人工智能大赛—辽宁赛区选拔赛在大连海事大学圆满收官。本次大赛吸引来自辽宁工业大学、大连理工大学等知名高校的10余支队伍参与&#xff0c;充分展现各高校在机器人及人工智能领域的深厚实力和创新精神。其中&#xff0c;由卓翼飞思实…...

react ts 封装3D柱状图,支持渐变

留档&#xff0c;以防忘记 bar3D.tsx import React, { useEffect, useRef, useState } from react; import * as echarts from echarts; import echarts/lib/chart/bar; import echarts/lib/chart/pictorialBar; import echarts/lib/component/grid; import echarts/lib/comp…...

css---before和after伪元素

1.什么是伪元素 伪元素不是真正的页面元素&#xff0c;html没有对应的元素&#xff0c;但是其所有用法和表现行为与真正的页面元素一样&#xff0c;可以对其使用如页面元素一样的CSS样式&#xff0c;表面上看上去貌似是页面的某些元素来展现&#xff0c;实际上CSS样式展现的行…...

下载后端返回的图片,而不是打开图片

使用 window.location.href 和 window.open 后都是打开图片&#xff0c;原因是&#xff0c;当浏览器发现是浏览器支持的文件类型&#xff0c;例如 jpg、png、svg 等&#xff0c;默认是浏览器打开。 解决 fetch createObjectURL fetch 转换为 blob 对象 createObjectURL() 静…...

ELISA实验前,需要做好哪些准备?

进行ELISA试剂盒实验前&#xff0c;需要进行周密的准备工作以确保实验的顺利进行和实验的准确性。那么&#xff0c;具体应该做哪些准备呢&#xff1f;欣博盛生物为您总结了一些关键的准备工作步骤&#xff1a; 1. 阅读说明书 仔细阅读ELISA试剂盒的说明书&#xff0c;了解试剂…...

浅谈 Linux 中的 core dump 分析方法

文章目录 一、什么是 core dump二、发生 core dump 的原因1. 空指针或非法指针引起 core dump2. 数组越界或指针越界引起的 core dump3. 数据竞争导致 core dump4. 代码不规范 三、core dump 分析方法1. 启用 core dump2. 触发 core dump2-1. 因空指针解引用而崩溃2-2. 通过 SI…...

自研直播系统-直播系统实战

文章目录 1 流媒体基础本文教程下载地址1.1 流媒体1.2 流式传输方式1.2.1 顺序流式传输1.2.2 实时流式传输 1.3 流媒体传输协议1.3.1 rtmp协议1.3.2 HLS协议1.3.3 RTSP协议1.3.4 视频流的对比 1.4 视频编码(codec)1.5 分辨率的规范分辨率簡介&#xff1a;1.5.2 分辨率單位 1.6 …...

python数据分析入门学习笔记

目录 一、 数据分析有关的python库简介 (一)numpy (二)pandas (三)matplotlib (四)scipy (五)statsmodels (六)scikit-learn 二、 数据的导入和导出 三、 数据筛选 四、 数据描述 五、 数据处理 六、 统计分析 七、 可视化 八、 其它![](https://…...

SyntaxError: invalid character in identifier 解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

装箱问题汇总

一维装箱 整数规划建模 参考 二维装箱 数学建模 参考1 参考2...

你的B端系统考虑移动化了?还没?那就小心out了。

B端系统移动化的趋势是不可阻挡的。随着移动设备的普及和移动互联网的发展&#xff0c;越来越多的企业和组织意识到移动化对于提高工作效率、拓展市场和提供更好的用户体验的重要性。以下是一些导致B端系统移动化趋势不可阻挡的原因&#xff1a; 移动办公需求&#xff1a;越来越…...

大数据招商的应用场景及实施路径有哪些?

当下&#xff0c;我国已经进入数字经济与实体经济融合发展的新阶段&#xff0c;数字技术和数字化转型落地日臻成熟&#xff0c;数据要素价值释放深入到了我国各个领域的发展&#xff0c;招商引资也不例外&#xff0c;在传统招商模式效果日渐甚微的大环境下&#xff0c;大数据招…...

【C++】 C/C++预处理器介绍

C预处理器&#xff08;Preprocessor&#xff09;是编译过程中的一个阶段&#xff0c;它在编译器进行实际编译之前对源代码进行处理。预处理器提供了一系列的指令&#xff0c;用于条件编译、文件包含、宏定义等操作。以下是一些常见的预处理器指令&#xff1a; 宏定义&#xff…...

MySQL—创建查看删除备份恢复数据库

创建数据库 创建数据库 LLF_DB01CREATE DATABASE LLF_DB01删除数据库DROP DATABASE LLF_DB01创建一个使用utf8字符集的数据库并带校对规则的数据库CREATE DATABASE hsp_db03 CHARACTER SET utf8 COLLATE utf8_bin 查看、删除数据库 显示所有的数据库SHOW DATABASES显示数据库…...

1.4 ROS2集成开发环境搭建

1.4.1 安装VSCode VSCode全称Visual Studio Code&#xff0c;是微软推出的一款轻量级代码编辑器&#xff0c;免费、开源而且功能强大。它支持几乎所有主流的程序语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比Diff、GIT 等特性&#xff0c;支持插件…...

数组和对象在内存中的区别

一、内存分配方式 数组在内存中是一段连续的存储空间&#xff0c;每个元素占据一个位置&#xff0c;这种连续存储方式使得数组的访问速度更快。对象在内存中是以键值对的形式存储的&#xff0c;每个键值对都需要单独的内存空间&#xff0c;这种非连续的存储方式可能会导致访问…...

智能胎教仪,科技与教育的融合-N9301胎教仪语音方案

随着科学技术的不断进步&#xff0c;人们对婴幼儿教育的认知也日趋成熟和全面。其中&#xff0c;胎教作为一种重要的早期教育方式&#xff0c;近年来备受瞩目。而胎教仪语音芯片的研发&#xff0c;正是为了满足这一需求&#xff0c;为胎儿的健康成长提供更加便捷的胎教方案。 一…...

代码随想录2链表

2 移除元素 Leetcode203 设置虚拟头节点 dummyHead...

Java8新特性常见用法

Java8新特性 示例类Stream API 使用示例forEach:遍历Stream:创建流map:转换元素filter:过滤元素collect(收集元素) 和 Collectors(分组、连接)sorted 和 comparing(搭配排序)toMap:转换Map元素collectingAndThen:过滤掉相同数据toUpperCase:转大写distinct:去重c…...

Web3 前端攻击:原因、影响及经验教训

DeFi的崛起引领了一个创新和金融自由的新时代。然而&#xff0c;这种快速增长也吸引了恶意行为者的注意&#xff0c;他们试图利用漏洞进行攻击。尽管很多焦点都集中在智能合约安全上&#xff0c;但前端攻击也正在成为一个重要的威胁向量。 前端攻击的剖析 理解攻击者利用前端漏…...

C++ 如何解决回调地狱问题

“地狱回调”&#xff08;Callback Hell&#xff09;是指在编程中使用过多嵌套回调函数&#xff0c;导致代码难以阅读和维护。C 提供了多种方法来解决这个问题&#xff0c;包括以下几种常见的方法&#xff1a; 使用 Lambda 表达式和标准库的 std::function使用 std::future 和…...

普利姆最小生成树算法 c++

普里姆(Prim)算法是一种用于在加权连通无向图中查找最小生成树(MST, Minimum Spanning Tree)的贪心算法。最小生成树是一个子图,它包括图中的所有顶点,并且边的总权重最小。该算法的基本思想是从一个顶点开始,逐步扩展生成树,直到包括所有顶点。 算法步骤 初始化: 从…...

Golang 依赖注入设计哲学|12.6K 的依赖注入库 wire

一、前言 线上项目往往依赖非常多的具备特定能力的资源&#xff0c;如&#xff1a;DB、MQ、各种中间件&#xff0c;以及随着项目业务的复杂化&#xff0c;单一项目内&#xff0c;业务模块也逐渐增多&#xff0c;如何高效、整洁管理各种资源十分重要。 本文从“术”层面&#…...

ubuntu 23 连接正点imx6ull的uboot网络设置(nfs和tftp)

由于使用ubuntu23&#xff0c;无法连接正点的imx6ull的uboot&#xff0c;因为这个uboot里面的nfs是v2&#xff0c;ubuntu23内核是6.5不支持uboot v2。配置/etc/default/nfs-kernel-server sudo vim /etc/default/nfs-kernel-server 更改以下参数&#xff1a; RPCNFSDCOUNT"…...

CC6利用链分析

CC1的两条利用链&#xff0c;在JDK 8u71之后已修复&#xff0c;不可利用。 学一下不受版本限制的CC6利用链 分析版本 Commons Collections 3.2.1 JDK 8u65 环境配置参考JAVA安全初探(三):CC1链全分析 分析过程 我的Github主页Java反序列化学习同步更新&#xff0c;有简单…...

多线程编程的基本概念,C++标准库中的多线程支持(std::thread,std::async),如何处理线程同步和并发问题。

多线程编程在现代计算机系统中非常重要&#xff0c;因为它能够使程序同时执行多个操作&#xff0c;提高计算效率。以下是多线程编程的基本概念及如何在C标准库中使用std::thread和std::async进行多线程编程&#xff0c;同时处理线程同步和并发问题。 多线程编程的基本概念 线程…...

Linux的Socket开发概述

套接字&#xff08;socket&#xff09;是 Linux 下的一种进程间通信机制&#xff08;socket IPC&#xff09;&#xff0c;在前面的内容中已经给大家提到过&#xff0c;使用 socket IPC 可以使得在不同主机上的应用程序之间进行通信&#xff08;网络通信&#xff09;&#xff0c…...

LLM调优,大模型怎么学

背景 LLM Transparency Tool 是一个用于深入分析和理解大型语言模型&#xff08;LLM&#xff09;工作原理的工具&#xff0c;旨在增加这些复杂系统的透明度。它提供了一个交互式界面&#xff0c;用户可以通过它观察、分析模型对特定输入&#xff08;prompts&#xff09;的反应…...

XLSX + LuckySheet + LuckyExcel实现前端的excel预览

文章目录 功能简介简单代码实现效果参考 功能简介 通过LuckyExcel的transformExcelToLucky方法&#xff0c; 我们可以把一个文件直接转成LuckySheet需要的json字符串&#xff0c; 之后我们就可以用LuckySheet预览excelLuckyExcel只能解析xlsx格式的excel文件&#xff0c;因此对…...

在Ubuntu上创建和启用交换文件的简单步骤

文章目录 为什么使用交换文件&#xff1f;步骤 1&#xff1a;创建交换文件步骤 2&#xff1a;设置正确的权限步骤 3&#xff1a;将文件格式化为交换空间步骤 4&#xff1a;启用交换文件步骤 5&#xff1a;验证交换文件步骤 6&#xff1a;永久启用交换文件步骤 7&#xff1a;调整…...

Java [ 基础 ] HashMap详解 ✨

目录 ✨探索Java基础 HashMap详解✨ 总述 主体 1. HashMap的基本概念 2. HashMap的工作原理 3. HashMap的常用操作 4. HashMap的优缺点 总结 常见面试题 常见面试题解答 1. HashMap的底层实现原理是什么&#xff1f; 2. 如何解决HashMap中的哈希冲突&#xff1f;…...

vue2项目迁移vue3与gogocode的使用

#背景 公司有个项目使用vue2jswebpack框架开发的&#xff0c;由于该项目内部需要安扫&#xff0c;导致很多框架出现了漏洞需要升级&#xff0c;其中主要需要从vue2升vue3,但是重新搭框架推翻重做成本太高&#xff0c;于是找到了gogocode。 #升级步骤踩坑 1. 安装 gogocode插…...

【Python123题库】#数列求和 #百分制成绩转换五分制(循环) #正负交错数列前n项和 #求数列前n项的平方和

禁止转载&#xff0c;原文&#xff1a;https://blog.csdn.net/qq_45801887/article/details/140079866 参考教程&#xff1a;B站视频讲解——https://space.bilibili.com/3546616042621301 有帮助麻烦点个赞 ~ ~ Python123题库 数列求和百分制成绩转换五分制(循环)正负交错数列…...

Edge浏览器选中后,出现AI智能生成 AI专业写作

这个是扩展里边的“ 网页万能复制 & ChatGPT AI写作助手”造成的&#xff0c;这个拓展增加了AI写作功能。关闭这个拓展就解决了。...

c++习题08-计算星期几

目录 一&#xff0c;问题 二&#xff0c;思路 三&#xff0c;代码 一&#xff0c;问题 二&#xff0c;思路 首先&#xff0c;需要注意到的是3^2000这个数值很大&#xff0c;已经远远超过了long long 数据类型能够表示的范围&#xff0c;如果想要使用指定的数据类型来保存…...

单目相机减速带检测以及测距

单目相机减速带检测以及测距项目是一个计算机视觉领域的应用&#xff0c;旨在使用一个摄像头&#xff08;单目相机&#xff09;来识别道路上的减速带&#xff0c;并进一步估计车辆与减速带之间的距离。这样的系统对于智能驾驶辅助系统&#xff08;ADAS&#xff09;特别有用&…...

Xilinx FPGA:vivado实现乒乓缓存

一、项目要求 1、用两个伪双端口的RAM实现缓存 2、先写buffer1&#xff0c;再写buffer2 &#xff0c;在读buffer1的同时写buffer2&#xff0c;在读buffer2的同时写buffer1。 3、写端口50M时钟&#xff0c;写入16个8bit 的数据&#xff0c;读出时钟25M&#xff0c;读出8个16…...

解决 VM 虚拟机网络连接异常导致的 Finalshell 无法连接及 ifconfig 中 ens33 丢失问题

在使用 VM 虚拟机的过程中&#xff0c;遇到了一个颇为棘手的网络连接问题。平时虚拟机都能够正常启动并使用&#xff0c;但昨天在启用虚拟机时更换了一下网络节点&#xff0c;结果今天打开虚拟机后。Finalshell 无法连接上虚拟机&#xff0c;并且输入 ifconfig 命令后也没有 en…...

深入Django(三)

Django视图&#xff08;Views&#xff09;详解 引言 在前两天的博客中&#xff0c;我们介绍了Django的基本概念和模型系统。今天&#xff0c;我们将深入探讨Django的视图&#xff08;Views&#xff09;&#xff0c;它们是处理用户请求和返回响应的地方。 什么是Django视图&a…...

观测云赋能「阿里云飞天企业版」,打造全方位监控观测解决方案

近日&#xff0c;观测云成功通过了「阿里云飞天企业版」的生态集成认证测试&#xff0c;并荣获阿里云颁发的产品生态集成认证证书。作为监控观测领域的领军者&#xff0c;观测云一直专注于提供统一的数据视角&#xff0c;助力用户构建起全球范围内的端到端全链路可观测服务。此…...

51单片机第27步_单片机工作在睡眠模式

重点学习51单片机工作在睡眠模式。 1、进入“睡眠模式”的方法 通过将PCON寄存器中的PDWN置1&#xff0c;则CPU会进入“睡眠模式”。在“睡眠模式”中,晶振将停止工作&#xff0c;因此&#xff0c;定时器和串口都将停止工作&#xff0c;只有外部中断继续工作。如果单片机电源…...

互联网应用主流框架整合之SpringCloud微服务治理

微服务架构理念 关于微服务的概念、理念及设计相关内容&#xff0c;并没有特别严格的边界和定义&#xff0c;某种意义上说&#xff0c;适合的就是最好的&#xff0c;在之前的文章中有过详细的阐述&#xff0c;微服务[v1.0.0][Spring生态概述]、微服务[设计与运行]、微服务[v1.…...