🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] >[success] # Java -- String 1. `String `是java 定义好的一个类,定义在 **java.lang** 包中,所以使用不需要导包 2. `String `对象用于保存字符串,也就是一组字符序列,从jdk1.9开始该类的底层不使用**char\[\]** 来存储数据,而是改成 **byte\[\]** 加上编码标记,从而节约了一些空间 2.1. **java1.8** 使用**UTF-16**,一个字符(不区分字母还是汉字)占两个字节 2.2. **jdk1.9** 新增一个`coder` 来划分存储,如果其本身是一个字节就用**byte**,如果大于一个字节使用**UTF-16** 3. 创建 java 字符串方式有两种,一种是**字符串字面值**,一种使**用String 类** 4. **java程序中**所有**字符串文字** 例如 `String name = "1"` 都被实例为`String `类的对象 5. 字符串不可变,它们的值在创建后**不能被更改** 6. **String 是final 类**,**不能被其他的类继承** 7. java8 `String` 有属性 `private final char value[]`; 用于存放字符串内容,java9 则在 `private final byte[] value` 8. 以**字符串字面值** 即""方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,**JVM 都只会建立一个 String 对象**,并在字符串池中维护 * 注关于第二条参考资料 [# UTF-16 揭秘Java String中的字符编码,全程高能无废话 #安员外很有码](https://www.bilibili.com/video/BV13U4y1y7LP/?spm_id_from=333.788&vd_source=1e4d2f8cd0a4f142adfbc4bf47b6c113) [# jdk9为何要将String的底层实现由char\[\]改成了byte\[\]?](https://www.zhihu.com/question/447224628) >[info] ## String类接口实现和父类 ~~~ public final class String implements java.io.Serializable, Comparable<String>, CharSequence {} ~~~ 1. `String` 继承自 `Object` 因此具备 `Object` 方法 2. 实现了接口 `implements Serializable, Comparable<String>, CharSequence` 2.1. **Serializable**: **String 可以串行化:可以在网络传输** 2.2. **Comparable** : **String 对象可以比较大小** 2.3. **CharSequence**: **描述字符串结构** 3. **final 修饰类不可继承**,即String类不存在子类,Java程序中有且仅有一个String类,**说明**:继承和方法重写在为程序带来灵活性的同时,也会带来多个子类行为不一致的问题。为保证所有JDK使用者都使用同一String类的固有实现,不允许自定义修改其中的具体实现,不希望体现个体差异性,故使用final终结器,防止String类被继承后进行方法重写。 >[info] ## String 字符串存储和不可修改原因 1.` String`字符串实际上底层是以**字符数组value实现存储的** * java8 ~~~ public final class String implements java.io.Serializable, Comparable<String>, CharSequence { @Stable private final char[] value; ... } ~~~ java 11 ~~~ public final class String implements java.io.Serializable, Comparable<String>, CharSequence { @Stable private final byte[] value; ... } ~~~ 2. **value 是一个final类型**, 不可以修改,即**value不能指向新的地址,但是单个字符内容是可以变化** ~~~ final char[] value = { 'a', 'b', 'c' }; char[] v2 = { 't', 'o', 'm' }; value[0] = 'H'; // final 不改变地址情况下可以修改内容 // value = v2; 不可以修改 value地址 ~~~ 3. ` String`,访问控制修饰符为**private**,类外无法直接访问,且String类并未为value数组提供相应public权限的getter和setter方法,故外部无法访问此属性,无法对此字符数组进行修改,即无法对此字**符串进行修改** >[info] ## 创建字符串 1. **字符串字面值** 就是使用双引号包裹,创建形式 ~~~ // 使用直接赋值的方式获取一个字符串对象 String name = "123" ~~~ 2. 通过`String` 类创建形式,常见的构造方法 | 方法声明| 功能介绍| | --- | --- | |String() |使用无参方式构造对象得到空字符序列| |String(byte[] bytes, int offset, int length)|使用bytes数组中下标从offset位置开始的length个字节来构造对象| |String(byte[] bytes) |使用bytes数组中的所有内容构造对象| |String(char[] value, int offset, int count)|使用value数组中下标从offset位置开始的count个字符来构造对象| |String(char[] value) |使用value数组中的所有内容构造对象| |String(String original) |根据参数指定的字符串内容来构造对象,新创建对象为参数对象的副本| >[danger] ##### 常用构造创建 -- 使用案例 ~~~ public class TestStr { public static void main(String[] args) { // 1. 使用无参方式构造对象打印 String str1 = new String(); // 打印结果为""表示空字符串对象 System.out.println("str1:" + str1); // 2. 使用byte 数组来创建字符串 byte[] bArr = { 97, 98, 99, 100, 101 }; /* * bytes[] 要解码为字符的字节数组 * int offset 要解码的第一个字节的索引 offset大于 bytes.length - length 和 offset 不能为负数 * int length 要解码的字节数 不能为负数 */ String str2 = new String(bArr, 1, 3); // 打印结果bcd ,将每个整数对应他的ascii转换对应字符串 System.out.println("str1:" + str2); // 3. 使用整个字节数组来构造字符串对象 String str3 = new String(bArr); // 打印结果 abcde System.out.println("str3:" + str3); // 4. 使用字符数组 char[] charArr = { 97, 98, 'h', 'm' }; String str4 = new String(charArr); // 打印结果abhm 将整个字符数组拼接 System.out.println("str4:" + str4); // 5. 使用指定字符数组 位置拼接 /* * char[] 要解码为字符数组 * int offset 要解码的第一个字节的索引 offset大于 bytes.length - length 和 offset 不能为负数 * int length 要解码的字节数 不能为负数 */ String str5 = new String(charArr, 1, 3); // 打印结果 bhm System.out.println("str5:" + str5); // 6 使用字符串来构造 String str6 = new String("你好"); // 打印结果 你好 System.out.println("str6:" + str6); } } ~~~ >[danger] ##### 常用的构造方法应用场景 1. `char[]` 字符数组作为 **String 构造参数**时候,因为字符串是不能被更改的,因此我们可以去更改`char[]`数组重新生成我们想要的字符串例如`abc --> {'a','b','c'} --> {'Q','b','c'} --> "Qbc"` 2. 当在网络当中传输的数据其实都是字节信息,我们一般要把字节信息根据ascii 码进行转换,转成字符串,就用到`byte[]` 字节数组作为 **String 构造参数** 3. `String str = "abc";` 相当于: ~~~ char data[] = {'a', 'b', 'c'}; String str = new String(data); ~~~ >[danger] ##### 创建字符串对象两种方式的区别 1. **直接赋值方式创建**,​ 以`""`方式给出的字符串,只要字符序列相同(顺序和大小写),系统会检查该字符串在串池中是否存在,**不存在:创建新的**,存在无论在程序代码中出现几次,JVM 都只会**建立一个 String 对象**,并在字符串池中维护 * 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(**也就是方法区**)中,在JDK7.0版本,字符串常量池被移到了**堆中**了,下图中说明s1 和 s2 **内存空间地址相同** ![](https://img.kancloud.cn/46/ae/46ae978b9c4688490ccbb96eb35e3c1f_1143x501.png) 2. **通过构造方法创建**,通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然**内容相同**,但是**地址值不同**,但如图在作为构造参数的"abc" 是被双引号包裹,被包裹的其实会在常量池创建,但如果使用的是`char[]`字符数组作为**String 构造参数**时候,并不会有字符串常量池的概念 ![](https://img.kancloud.cn/ef/19/ef19a9c9980b5207ba04278052a24470_1165x523.png) * **总结**:字符串字面量创建字符时,代码中出现了相同字符串内容则直接使用池中已有的字符串对象而无需申请内存及创建对象,从而提高了性能 >[danger] ##### 字符串创建地址比较 1. **==号的作用**, 比较**基本数据类型**:比较的是具体的值, 比**较引用数据类型**:比较的是对象地址值 ~~~ public class TestStr { public static void main(String[] args) { // 1.请问下面的代码会创建几个对象?分别存放在什么地方? // String str1 = "hello"; // 1个对象 存放在常量池中 // String str1 = new String("helo"); // 2个对象 1个在常量池中,1个在堆区 // 2.常量池和堆区对象的比较 String str1 = "hello"; // 常量池 String str2 = "hello"; // 常量池 String str3 = new String("hello"); // 堆区 String str4 = new String("hello"); // 堆区 System.out.println(str1 == str2); // 比较地址 true System.out.println(str1.equals(str2)); // 比较内容 true System.out.println(str3 == str4); // 比较地址 false System.out.println(str3.equals(str4)); // 比较内容 true System.out.println(str2 == str4); // 比较地址 false System.out.println(str2.equals(str4)); // 比较内容 true System.out.println("------------------------------------------------------------"); // 3.常量有优化机制,变量没有(Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串) String str5 = "abcd"; String str6 = "ab" + "cd"; // 常量优化机制 "abcd", System.out.println(str5 == str6); // 比较地址 true String str7 = "ab"; String str8 = str7 + "cd"; // 没有常量优化 System.out.println(str5 == str8); // 比较地址 false } } ~~~ * 案例二,`intern` 方法返回是常量池中地址 ~~~ public class TestStr { public static void main(String[] args) { String str = "abc"; String str2 = "abc1"; String str1 = new String("abc"); // 在堆中str1 System.out.println(str == str1); // false System.out.println(str == str1.intern()); // true System.out.println(str1 == str1.intern()); // false System.out.println(str == str2 ); // false 他们常量池中的地址不同 } } ~~~