知识库

Java之String类的底层原理

2024-01-21  本文已影响0人  Java_Evan

String类的特性

"hello" 相当于 char[] data = {'h','e','l','l','o'}

String类的底层存储结构
//JDK1.8中的源码
public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence {

 private final char value[]; //String对象的字符内容是存储在此数组中
  1. private意味着外面无法直接获取字符数组,而且String没有提供value的get和set方法。
  2. final意味着字符数组的引用不可改变,而且String也没有提供方法来修改value数组某个元素值
  3. 因此字符串的字符数组内容也不可变的,即String代表着不可变的字符序列。即,一旦对字符串进行修改,就会产生新对象。
//JDK1.17中的源码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    @Stable
    private final byte[] value;

针对char[] value改为byte[] value数组的说明:
官方说明:大多数String对象只包含Latin-1字符。这样的字符只需要1字节的存储空间,因此这些字符串对象的内部字符数组中有一半的空间没有使用。
细节:新的String类将根据字符串的内容存储编码为ISO-8859-1/Latin-1(每个字符1字节)或UTF-16(每个字符2字节)的字符。encoding标志表示使用哪种编码。

String底层内存结构

因为字符串对象设计为不可变,所以字符串有常量池来保存很多常量对象。

JDK6中,字符串常量池在方法区。JDK7开始,就移到堆空间,直到目前JDK17版本。


String的内存结构
public class StringTest {

    /*
    String:字符串。使用一对""引起来表示
    1. String声明为final的,不可被继承
    2. String实现了Serializable接口:表示字符串支持序列化
    3. String实现了Comparable接口:表示String可以比较大小
    4. String内部定义了final char[] value用于存储字符串数据
    5. String代表了不可变的字符序列。简称:不可变性
    String代码的实现逻辑:
    1. 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的char[] value进行赋值;
       因为字符串不可变序列,第一次赋值以及固定数组长度,当重新赋值是,需要重新开辟一片内存区域
    2. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在方法区的字符串常量池中
    3. 方法区常量池中是不会存储相同内容的字符串
    4. 当对现有的字符串进行拼接操作时,也需要重新分配内存区域赋值,不能使用原有的char[] value进行赋值
    5. 当调用String中replace()方法替换指定字符或字符串时,也需要重新分配内存区域赋值,
        不能使用原有的char[] value进行赋值
     */
   @Test
    public void test1() {
        String s1 = "abc";
        String s2 = "abc";

        // 内存中只有一个"abc"对象被创建,同时被s1和s2共享。
        System.out.println(s1 == s2); //true:比较的两个常量对象的地址值

        s2 = "hello";
        System.out.println(s2 == s1); //false: 重新赋值时,方法区中重新分配了字符串常量
    }
}

String字面量定义&实例化对象的方式

/*
String实例化的方式:
      方式一:通过字面量定义的方式
      方式二:通过new+构造器的方式
*/
@Test
public void test2(){
    //通过字面量定义的方式:此时的s1和s2的数据声明在方法区的字符串常量池中
    String str1 = "abc";
    String str2 = "abc";

    //通过new+构造器的方式:此时的s3和s4都保存的地址值,是数据在堆空间中开辟内存空间以后生成对应的地址值
    String str3 = new String("JavaEE");
    String str4 = new String("C++");
    String str5 = str3;

    System.out.println(str1 == str2); //true
    System.out.println(str3 == str4); //false
    System.out.println(str1 == str3); //false
    System.out.println(str5 == str3); //true
}
String字面量定义的内存结构 String字面量定义&实例化对象的内存结构

结论:
str2 首先指向堆中的一个字符串对象,然后堆中字符串的value数组指向常量池中常量对象的value数组。
1、字符串常量存储在字符串常量池,目的是共享。
2、字符串非常量对象存储在堆中。

String定义字面量的注意点

/*
String不同拼接的内存解析:
     1. 常量和常量的拼接结果在常量池中。且常量池中不会存在相同内容的常量
     2. 只要其中有一个是变量,结果就在堆空间中
     3. 如果拼接的结果调用intern()方法,返回值就在常量池中
*/
@Test
public void test2() {
    String s1 = "hello";
    String s2 = "world";
    String s3 = "hello" + "world";
    String s4 = s1 + "world";
    String s5 = s1 + s2;
    String s6 = (s1 + s2).intern();

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

结论:
(1)常量+常量:结果是常量池。且常量池中不会存在相同内容的常量。
(2)常量与变量 或 变量与变量:结果在堆中
(3)拼接后调用intern方法:返回值在常量池中

String常用方法

@Test
public void test01(){
    //将用户输入的单词全部转为小写,如果用户没有输入单词,重新输入
    Scanner input = new Scanner(System.in);
    String word;
    while(true){
        System.out.print("请输入单词:");
        word = input.nextLine();
        if(word.trim().length()!=0){
            word = word.toLowerCase();
            break;
        }
    }
    System.out.println(word);
}

@Test
public void test02(){
    //随机生成验证码,验证码由0-9,A-Z,a-z的字符组成
    char[] array = new char[26*2+10];
    for (int i = 0; i < 10; i++) {
        array[i] = (char)('0' + i);
    }
    for (int i = 10,j=0; i < 10+26; i++,j++) {
        array[i] = (char)('A' + j);
    }
    for (int i = 10+26,j=0; i < array.length; i++,j++) {
        array[i] = (char)('a' + j);
    }
    String code = "";
    Random rand = new Random();
    for (int i = 0; i < 4; i++) {
        code += array[rand.nextInt(array.length)];
    }
    System.out.println("验证码:" + code);
    //将用户输入的单词全部转为小写,如果用户没有输入单词,重新输入
    Scanner input = new Scanner(System.in);
    System.out.print("请输入验证码:");
    String inputCode = input.nextLine();

    if(!code.equalsIgnoreCase(inputCode)){
        System.out.println("验证码输入不正确");
    }
}

@Test
public void test01(){
    String str = "尚硅谷是一家靠谱的培训机构,尚硅谷可以说是IT培训的小清华,JavaEE是尚硅谷的当家学科,尚硅谷的大数据培训是行业独角兽。尚硅谷的前端和UI专业一样独领风骚。";
    System.out.println("是否包含清华:" + str.contains("清华"));
    System.out.println("培训出现的第一次下标:" + str.indexOf("培训"));
    System.out.println("培训出现的最后一次下标:" + str.lastIndexOf("培训"));
}

@Test
public void test01(){
    String str = "helloworldjavaatguigu";
    String sub1 = str.substring(5);
    String sub2 = str.substring(5,10);
    System.out.println(sub1);
    System.out.println(sub2);
}

@Test
public void test02(){
    String fileName = "快速学习Java的秘诀.dat";
    //截取文件名
    System.out.println("文件名:" + fileName.substring(0,fileName.lastIndexOf(".")));
    //截取后缀名
    System.out.println("后缀名:" + fileName.substring(fileName.lastIndexOf(".")));
}

@Test
public void test01(){
    //将字符串中的字符按照大小顺序排列
    String str = "helloworldjavaatguigu";
    char[] array = str.toCharArray();
    Arrays.sort(array);
    str = new String(array);
    System.out.println(str);
}

@Test
public void test02(){
    //将首字母转为大写
    String str = "jack";
    str = Character.toUpperCase(str.charAt(0))+str.substring(1);
    System.out.println(str);
}
@Test
public void test03(){
    char[] data = {'h','e','l','l','o','j','a','v','a'};
    String s1 = String.copyValueOf(data);
    String s2 = String.copyValueOf(data,0,5);
    int num = 123456;
    String s3 = String.valueOf(num);

    System.out.println(s1);
    System.out.println(s2);
    System.out.println(s3);
}
@Test
public void test1(){
    String name = "张三";
    System.out.println(name.startsWith("张"));
}

@Test
public void test2(){
    String file = "Hello.txt";
    if(file.endsWith(".java")){
        System.out.println("Java源文件");
    }else if(file.endsWith(".class")){
        System.out.println("Java字节码文件");
    }else{
        System.out.println("其他文件");
    }
}
@Test
public void test1(){
    String str1 = "hello244world.java;887";
    //把其中的非字母去掉
    str1 = str1.replaceAll("[^a-zA-Z]", "");
    System.out.println(str1);

    String str2 = "12hello34world5java7891mysql456";
    //把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
    String string = str2.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
    System.out.println(string);

}

String与其他类型结构间的转换

字符串 --> 基本数据类型、包装类:

基本数据类型、包装类 --> 字符串:

字符数组 --> 字符串:

字符串 --> 字符数组:

字符串 --> 字节数组:(编码)

字节数组 --> 字符串:(解码)

@Test
public void test2(){
    //String 转基本数据类型
    String str1 = "123";
    int i = Integer.parseInt(str1);
    System.out.println(i);

    //基本数据类型转字符串
    String s = String.valueOf(i);
    String s1 = i + "";
    System.out.println(s);
    System.out.println(s1);
}

@Test
public void test01() throws Exception {
    String str = "中国";
    System.out.println(str.getBytes("ISO8859-1").length);// 2
    // ISO8859-1把所有的字符都当做一个byte处理,处理不了多个字节
    System.out.println(str.getBytes("GBK").length);// 4 每一个中文都是对应2个字节
    System.out.println(str.getBytes("UTF-8").length);// 6 常规的中文都是3个字节

    /*
     * 不乱码:(1)保证编码与解码的字符集名称一样(2)不缺字节
     */
    System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1"));// 乱码
    System.out.println(new String(str.getBytes("GBK"), "GBK"));// 中国
    System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));// 中国
}
@Test
public void test3(){
    //字符串转char[]
    String str1 = "hello";
    char[] c = str1.toCharArray();
    for (int i = 0; i < c.length; i++) {
        System.out.println(c[i]);
    }

    //char[]转字符串
    char arr[] = new char[]{'w', 'o', 'r', 'l', 'd'};
    String str2 = new String(arr);
    System.out.println(str2);
}
@Test
public void test4() throws UnsupportedEncodingException {
    System.out.println("*************编码************");
    //字符串转byte[]
    String str1 = "abcd中国";
    byte[] b1 = str1.getBytes(); //使用默认字符集,进行编码
    System.out.println(Arrays.toString(b1));

    byte[] gbks = str1.getBytes("GBK");  //使用指定字符集编码,进行编码
    System.out.println(Arrays.toString(gbks));

    System.out.println("***************解码*****************");
    String str2 = new String(b1);  //使用默认的字符集,进行解码
    System.out.println(str2);

    String str3 = new String(gbks);  //中文乱码,原因:解码和编码字符集不一致
    System.out.println(str3);

    String gbk = new String(gbks, "GBK");  //指定字符集进行解码,编码解码保持一致
    System.out.println(gbk);
}
上一篇 下一篇

猜你喜欢

热点阅读