Bootstrap

Java 字符串

1. 字符串概述

  • 概述
  1. String 类定义的变量可以用于存储字符串,同时还提供了很多操作字符串的功能。
  2. String 类属于java.lang包。
  • 特点

String 常被称为不可变字符串类型,它的对象在创建后不能被更改。

2. 字符串对象的创建

2.1. 双引号创建

  • 说明

直接使用""定义,在字符串常量池中存储,而且相同内容只会在其中存储一份。

  • 快速入门
public class Test {
    public static void main(String[] args) {
        String str1 = "Black";
        String str2 = "Black";

        // 加载 str2 变量时,去字符串常量池创建是发现 "Black" 字符串存在,则不创建,直接赋地址。
        System.out.println(System.identityHashCode(str1));
        System.out.println(System.identityHashCode(str2));
        System.out.println(str1 == str2);

        str1 += "Pink";

        // 字符串加运算验证
        System.out.println(System.identityHashCode(str1));
        System.out.println(str1);
    }
}

  • 补充

""创建字符串变量时,栈内存中分配一个字符串变量,在堆内存中的字符串常量池中保存字符串对象,地址赋给字符串变量。

进行加运算时,新运算的对象(BlackPink)放在堆内存中,然后 str1 指向新对象(地址赋值)。原来的字符串对象(Black)没有发生改变,仍然在常量池中。

2.2. 构造函数创建

  • 说明

通过 String 类的构造器创建对象,每 new 一次都会产生一个新对象,放在堆内存中。

  • 快速入门
public class Test_02 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        // 创建一个空白字符串对象,不含有任何内容
        System.out.println(new String());

        // 根据传入的字符串内容,来创建字符串对象
        System.out.println(new String("BlackPink"));

        // 根据字符数组的内容,来创建字符串对象
        System.out.println(new String(
                (new char[]{'B','l','a','c','k','P','i','n','k',})));

        // 根据字节数组的内容,来创建字符串对象,可以指定编码
        System.out.println(new String(
                new byte[]{-60, -29, -70, -61, -93, -84, -42, -48, -71, -6},"GBK"));

        // 探究具体细节
        char[] chs = {'a', 'b', 'c'};
        String s3 = new String(chs);
        String s4 = new String(chs);
        System.out.println(System.identityHashCode(s3));
        System.out.println(System.identityHashCode(s4));
        System.out.println(s3 == s4);
    }
}

  • 补充

Java 存在编译优化机制,程序在编译时“a” + “b” + “c”会直接转成 abc 。

每 new 一次,出来的都是新对象,地址不一样

3. 字符串常用方法

3.1. 通用方法

  • API
// 返回此字符串的长度
length();

// 获取某个索引位置处的字符
charAt(int index);

// 将当前字符串转换成字符数组返回
toCharArray();

// 根据开始和结束索引进行截取,得到新的字符串(包前不包后)
substring(int beginIndex, int endIndex);

// 从传入的索引处截取,截取到末尾,得到新的字符串
substring(int beginIndex);

// 使用新值,将字符串中的旧值替换,得到新的字符串
replace(A,B);

// 根据传入的规则切割字符串,得到字符串数组返回
split(String regex);

// 判断是否含有这个字符
contains(CharSequence s);

// 判断字符串首字母
startsWith(String prefix);

// 删除字符串的头尾空白符
trim();
  • 注意事项

字符串的内容比较不可以使用 == ,因为键盘输入的数据在堆内存,字符串对象在字符串常量池,地址不一样。

一般使用==比较基本数据类型,String 提供了equlas方法,只关心内容一样就返回true

3.2. 字符串编码与解码

  • 常用方法
// 使用默认字符集将该 String 字符编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes();

// 使用指定字符集将该 String 编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName);


// 使用默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes);

// 使用指定字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName);    
  • 快速入门
public class Test {
    public static void main(String[] args) throws Exception {

        // 编码:把文字转换成字节(使用指定的编码)
        String name = "我爱你中国";

        byte[] defaultBytes = name.getBytes();
        byte[] gbkBytes = name.getBytes("GBK");

        System.out.println(defaultBytes.length);
        System.out.println(Arrays.toString(defaultBytes));

        System.out.println();

        System.out.println(gbkBytes.length);
        System.out.println(Arrays.toString(gbkBytes));
        
        // 解码:把字节转换成对应的中文形式(编码前和编码后的字符集必须一致,否则乱码)
        System.out.println(new String(defaultBytes));
        System.out.println(new String(gbkBytes, "GBK"));
        System.out.println(new String(defaultBytes, "GBK"));
    }
}

4. 字符串拓展

4.1. 探究 String 的不可变性

  • 什么是不可变的对象?
  1. 不可变对象是指如果一个对象在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。
  2. 不能改变状态的意思是不能改变对象内的成员变量,包括:
    1. 基本数据类型的值不能改变。
    2. 引用类型的变量不能指向其他的对象。
    3. 引用类型指向的对象的状态也不能改变。
  • String 为什么不可变?

从源码可以看出,String 对象其实在内部就是一个个字符,存储在 value 数组里面的。value 数组用 final 修饰,final 修饰的变量值不能被修改,因此 value 不可以指向其他对象。String 类内部所有的字段都是私有的,也就是被 private 修饰,而 String 没有对外提供修改内部状态的方法,因此 value 数组不能改变。所以String 是不可变的。

String 变量每次的修改其实都是产生并指向了新的字符串对象,原来的字符串对象都是没有改变的,所以称不可变字符串。

  • 为什么 String 要设计成不可变的?
  1. 线程安全:同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
  2. 支持 hash 映射和缓存:String 的 hash 值经常会使用,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
  3. 安全考虑:网络地址 URL 、文件路径 path 、密码通常情况下都是以 String 类型保存,假若 String 不是固定不变的,将会引起各种安全隐患。比如将密码用 String 的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如 String 类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。
  4. 字符串常量池优化:String 对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。
  • 注意事项

String 是不可变的,它内部还有很多 substring, replace, replaceAll 这些操作的方法。这些方法不会改变 String 对象,我们每次调用这些方法,其实会在堆内存中创建了一个新的对象,然后其 value 数组引用指向不同的对象。

  • 参考链接

如何理解java中String的不可变性-CSDN博客

4.2. StringBuilder

4.2.1. StringBuilder 概述

  • 说明

StringBuilder 是一个可变的字符串类,可以把它看成是一个对象容器 ,用来提高字符串的操作效率,如拼接、修改等操作。

StringBuilder 只是拼接字符串的手段,最后还是要恢复成 String 类型。

  • 构造函数
// 创建一个空白的可变的字符串对象,不包含任何内容
public StringBuilder()

// 创建一个指定字符串内容的可变字符串对象
public StringBuilder(String str)
  • 常用方法
// 添加数据并返回 StringBuilder 对象本身
public StringBuilder append(任意类型)

// 将对象的内容反转
public StringBuilder reverse()

// 返回对象内容长度
public int length()

// 把 StringBuilder 转换为 String
public String toString()

4.2.2. String 和 StringBuilder

4.3. StringJoiner

如何理解java中String的不可变性_leo825...的博客-CSDN博客

    @Test
    public void testString() {

    }

    /**
     * 向字符串中添加bbb
     *
     * @param str
     */
    public String addString(String str) {
        str = str + "bbb";
        return str;
    }

    /**
     * 向字符串中追加bbb
     *
     * @param str
     */
    public StringBuilder addStringBuilder(StringBuilder str) {
        str.append("bbb");
        return str;
    }

4.4. String 和 StringBuilder

聊聊StringBuffer,StringBuilder和String拼接字符串到底谁更快?stringbuffer连接字符串速度没有string 快向上的狼的博客-CSDN博客

4.5. String 和 StringBuilder

  1. 为什么拼接、反转字符串建议使用StringBuilder?
    • String :内容是不可变的、拼接字符串性能差,浪费内存
    • StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
  1. 定义字符串使用String,拼接、修改等操作字符串使用StringBuilder

4.5.1. String 类拼接字符串原理

进行 s1 + "b" 时,会在堆内存创建新的对象StringBuilder进行加法,然后创建新的对象s2,通过toString转成String类型.

4.5.2. StringBuilder提高效率原理

只会创建一个StringBuilder对象,提高效率

4.6. String、StringBuffer、Stringbuilder 区别

表格比较

String

StringBuffer

StringBuilder

拼接速度

最差

其次

最高

线程安全

线程安全

线程安全

线程不安全

使用场景

少量字符串操作

多线程环境下的大量操作

单线程环境下的大量操作

概念

  1. String 代表一个不可变类,也就是说一个 String 对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。
  2. StringBuffer:代表一个字符序列可变的字符串,当一个 StringBuffer 对象被创建之后,我们可以通过 StringBuffer 提供的 API 来改变这个字符串对象的字符序列。也可以通过 toString 将其转换为 String 对象。
  3. StringBuilder:JDK1.5 中新增的类,也代表可变的字符序列。

性能分析

  1. String 是不可变的对象,因此在每次对 String 类型进行改变的时候,会在堆内存创建新的对象 StringBuilder 并进行运算,然后创建一个新的对象,最后通过 toString 转成 String 对象。所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响。
  2. StringBuffer、Stringbuilder 只会创建一个对象,故每次结果都会对 StringBuffer(Stringbuilder) 对象本身进行操作。
  3. StringBuffer、Stringbuilder 有共同的父类 AbstractStringBuilder ,二者无论是构造器还是方法都基本相同。但 StringBuilder 没有考虑线程安全问题,Stringbuffer 的方法都有 synchronized 修饰,故所有方法均要保证同步,所以 StringBuilder 比 StringBuffer 性能略高。

最佳实践

  1. 如果是在单线程下操作大量数据,应优先使用 StringBuilder 类。
  2. 如果是在多线程下操作大量数据,应优先使用 StringBuffer 类。

;