第十三章 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"会被存放在堆中常量池之外的地方。
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 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:字符串,使用一对 “” 引起来表示 // 两种定义方式 String s1 "atguigu"; // 字面量的定义方式 String s2 new String("hello");String 声明为 final 的,不可被继承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(20240704)1 GDS简介2 GDS架构2.1 全局数据服务池2.2 全局数据服务域2.3 全局服务管理2.4 全局数据服务目录2.5 Oracle通知服务 3 GDS简图3.1 负载均衡3.2 只读服务失败转移3.3 多主复制…...
搭建基础库~
前言 项目中会用到工具库、函数库以及一些跟框架绑定的组件,如果这些基础模块每个项目都实现一套,维护起来那真的头大,你说呢😉 搭建流程 准备工作 创建文件夹myLib、安装Git以及pnpm 目录大概就系这样子: myLib ├…...
深入了解Linux中的udhcpc:动态主机配置协议客户端
目录 什么是udhcpc?安装udhcpc配置网络接口使用udhcpc获取IP地址配置静态IP地址自定义udhcpc脚本高级选项udhcpc常见问题及排查方法1. 无法获取IP地址2. DNS配置不正确3. IP地址冲突4. 无法连接到默认网关5. 无法执行自定义脚本 在Linux系统中,网络配置是…...
O2OA(翱途) 开发平台之HTTP端口规划
O2OA(翱途) 开发平台[下称O2OA开发平台或者O2OA]采用相对灵活的系统架构,支持三种服务器运行的方式。本篇主要阐述合并服务运行独立服务运行代理端口运行三种服务器运行方式。 一、先决条件: 1、O2Server服务器正常运行,系统安装部署请参考文…...
以创新思维驱动下的盲盒小程序:重塑用户体验
一、引言 在数字化浪潮的推动下,小程序以其便捷、高效、无需下载安装的特性,迅速成为移动互联网的新宠。其中,盲盒小程序以其独特的玩法和惊喜感,吸引了大量用户的关注和参与。然而,随着市场竞争的加剧,如…...
设计资料:520-基于ZU15EG 适配AWR2243的雷达验证底板 高速信号处理板 AWR2243毫米波板
基于ZU15EG 适配AWR2243的雷达验证底板 一、板卡概述 本板卡系北京太速科技自主研发,基于MPSOC系列SOC XCZU15EG-FFVB1156架构,搭载两组64-bit DDR4,每组容量32Gb,最高可稳定运行在2400MT/s。另有1路10G SFP光纤接口、1路40G…...
晋级国赛!卓翼飞思技术引领,助力辽宁赛区机器人及人工智能大赛圆满收官
近日,第二十六届中国机器人及人工智能大赛—辽宁赛区选拔赛在大连海事大学圆满收官。本次大赛吸引来自辽宁工业大学、大连理工大学等知名高校的10余支队伍参与,充分展现各高校在机器人及人工智能领域的深厚实力和创新精神。其中,由卓翼飞思实…...
react ts 封装3D柱状图,支持渐变
留档,以防忘记 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.什么是伪元素 伪元素不是真正的页面元素,html没有对应的元素,但是其所有用法和表现行为与真正的页面元素一样,可以对其使用如页面元素一样的CSS样式,表面上看上去貌似是页面的某些元素来展现,实际上CSS样式展现的行…...
下载后端返回的图片,而不是打开图片
使用 window.location.href 和 window.open 后都是打开图片,原因是,当浏览器发现是浏览器支持的文件类型,例如 jpg、png、svg 等,默认是浏览器打开。 解决 fetch createObjectURL fetch 转换为 blob 对象 createObjectURL() 静…...
ELISA实验前,需要做好哪些准备?
进行ELISA试剂盒实验前,需要进行周密的准备工作以确保实验的顺利进行和实验的准确性。那么,具体应该做哪些准备呢?欣博盛生物为您总结了一些关键的准备工作步骤: 1. 阅读说明书 仔细阅读ELISA试剂盒的说明书,了解试剂…...
浅谈 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 分辨率的规范分辨率簡介:1.5.2 分辨率單位 1.6 …...
python数据分析入门学习笔记
目录 一、 数据分析有关的python库简介 (一)numpy (二)pandas (三)matplotlib (四)scipy (五)statsmodels (六)scikit-learn 二、 数据的导入和导出 三、 数据筛选 四、 数据描述 五、 数据处理 六、 统计分析 七、 可视化 八、 其它
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
