13-字符串

nobility 发布于 2025-07-15 01-Java语言基础 1605 次阅读


字符串

什么是字符串

字符串是由数字、字母或特殊符号组成的一串字符,用于表示文本数据。在 Java 中字符串是一种引用类型,由 String 所定义。

创建字符串

字符串可以用字面量的方式直接创建:

String str = "Hello World";

也可以使用 new 关键字创建:

String str = new String();
String str = new String("Hello World");
String str = new String(new char[]{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'});

字符串的不可变性

Java 中的字符串一旦被创建,其内容就不能被修改,任何修改操作(如拼接、替换)都会生成新字符串对象,而原字符串不变:

String s1 = "Hello";
// 这里会创建新对象"Hello World",s1 仍然是"Hello"
String s2 = s1 + " World";

字符串常量池(String Pool)

Java 中有一个特殊的内存区域叫字符串常量池,用来存储字符串字面量:

String s1 = "Java";
// 这里不会创建新对象,而是复用字符串常量池中的"Java"
String s2 = "Java";
// 这里编译器会将 "Ja" + "va" 优化成"Java",并复用字符串常量池中的"Java"
String s4 = "Ja" + "va"; 
// 此时 s1 == s2 == s3
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true

String s5 = "Ja";
// 由于是 s5 是变量,变量具有不确定性,所以编译器不会优化,所以这里不会复用常量池中的"Java",而是动态的创建一个新的 String 对象进行拼接
String s6 = s5 + "va";
// 此时 s1 != s6
System.out.println(s1 == s6); // false

字符串的底层实现

JDK 1.8 以及之前使用 char[] 来存储字符,JDK 1.9 开始使用 byte[] 来存储字符,同时增加一个 byte 类型的 coder 字段来存储字符编码。

JDK 1.8 源码:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    /** The value is used for character storage. */  
    private final char value[];
}

JDK 1.11 源码:

public final class String  
        implements java.io.Serializable, Comparable<java.lang.String>, CharSequence {  
  
    /**  
     * The value is used for character storage. 
     * 
     * @implNote This field is trusted by the VM, and is a subject to  
     * constant folding if String instance is constant. Overwriting this 
     * field after construction will cause problems. 
     *
     * Additionally, it is marked with {@link Stable} to trust the contents 
     * of the array. No other facility in JDK provides this functionality (yet). 
     * {@link Stable} is safe here, because value is never null.  
     */ 
     @Stable
     private final byte[] value;  
  
    /**  
     * The identifier of the encoding used to encode the bytes in
     * {@code value}. The supported values in this implementation are
     *
     * LATIN1
     * UTF16
     *
     * @implNote This field is trusted by the VM, and is a subject to  
     * constant folding if String instance is constant. Overwriting this
     * field after construction will cause problems.
     */
     private final byte coder;  
}

JDK 1.8 之后将 String 的底层实现由 char 改成了 byte 的原因

在 JDK 9 引入的 Compact Strings 特性中,String 的内部存储由 char[](每个字符固定占 2 字节)改为 byte[] + coder 的方案,其核心原因是降低内存占用,从而提升运行时性能。

  1. 实际业务中,大部分字符串只包含拉丁字符(ISO-8859-1),这些字符使用单字节就可以表示,而用 char 存储会白白浪费一半空间。
  2. 改为 byte[] 后,如果字符串中的内容全部可以用 Latin-1 编码,就用 1 个字节保存。只有包含无法压缩的字符时才使用 UTF-16(2 字节)。JVM 通过额外的 coder 字段来标识当前编码(取值可能为 LATIN1UTF16)。
  3. 内存占用的减少可以直接减少 GC 所带来的压力、更好的缓存命中率,从而提升整体性能。

所以,JDK 在 1.8 之后(从 1.9 开始)使用 byte[] 取代 char[],有机会使用单字节来存储字符,节省内存空间,并提高程序执行效率。

字符串中常用的方法

  1. length():返回字符串长度。
    String s = "Hello";
    System.out.println(s.length()); // 5
    
  2. charAt(int index):取得指定索引处的字符(索引从 0 开始)。
    String s = "Java";
    System.out.println(s.charAt(2)); // v
    
  3. substring(int begin)substring(int begin,int end):截取子串,第二个参数 end 不包含在结果内。
    String s = "Hello,World";
    System.out.println(s.substring(6)); // World
    System.out.println(s.substring(0, 5)); // Hello
    
  4. contains(CharSequence seq):判断是否包含指定序列。
    String s = "student";
    System.out.println(s.contains("stu")); // true
    
  5. indexOf(String str)lastIndexOf(String str):查找子串首次、最后一次出现的位置,如果未找到返回 –1
    String s = "I am a good cat";
    System.out.println(s.indexOf("a")); // 2
    System.out.println(s.indexOf("b")); // -1
    System.out.println(s.indexOf("good", 3)); // 7
    System.out.println(s.lastIndexOf("a")); // 13
    
  6. equals(Object obj)equalsIgnoreCase(String str):比较内容是否相等;后者忽略大小写。
    String a = "Java";
    String b = "java";
    System.out.println(a.equals(b)); // false
    System.out.println(a.equalsIgnoreCase(b)); // true
    
  7. compareTo(String str)compareToIgnoreCase(String str):按字典序比较大小;后者忽略大小写,小于返回负数、等于返回 0、大于返回正数。
    System.out.println("abc".compareTo("abd")); // -1
    System.out.println("Abc".compareToIgnoreCase("abc")); // 0
    
  8. startsWith(String prefix)endsWith(String suffix):判断前缀或后缀。
    String s = "helloworld";
    System.out.println(s.startsWith("hello")); // true
    System.out.println(s.endsWith("ld")); // true
    
  9. split(String regex):按正则表达式分割为字符串数组。
    String s = "a,b,c";
    String[] arr = s.split(",");
    System.out.println(Arrays.toString(arr)); // [a, b, c]
    
  10. concat(String str)(等价于 “+” ):拼接字符串。
    String s = "hello".concat(" world");
    System.out.println(s); // hello world
    
  11. replace(char oldC,char newC)replace(CharSequence t,CharSequence r):替换字符或子串。
    String s = "banana";
    System.out.println(s.replace('a','o'));        // bonono
    System.out.println("Java".replace("va","VA")); // JaVA
    
  12. replaceAll(String regex,String repl):按正则批量替换。
    String s = "a1b2c3";
    System.out.println(s.replaceAll("\\d","")); // abc
    
  13. toLowerCase()toUpperCase():大小写转换。
    System.out.println("Java".toUpperCase()); // JAVA
    
  14. trim() (JDK 11 及以上可用 strip()):去除首尾空白字符。
    String s = "  text  ";
    System.out.println(s.trim()); // "text"
    
  15. toCharArray():转换为 char 数组。
    char[] chars = "Hello".toCharArray();
    System.out.println(chars[0]); // H
    
  16. getBytes():按平台默认字符集转换成字节数组。
    byte[] data = "abc".getBytes();
    System.out.println(Arrays.toString(data)); // [97, 98, 99]
    
  17. format(String format, Object... args):格式化字符串。
    System.out.println(String.format("Name: %s, Age: %d", "Alice", 25)); // "Name: Alice, Age: 25"
    
  18. valueOf(Object obj):转换为字符串。
    System.out.println(String.valueOf(123)); // "123"
    System.out.println(String.valueOf(null)); // "null"
    

字符串类的体系结构

字符串类体系结构

 StringBuilderStringBuffer

因为 Java 中的 String 是不可变的,每次对字符串的修改都会创建新对象,频繁修改字符串会产生大量临时对象,从而严重影响性能。而 StringBuilderStringBuffer 可以直接在原对象上修改,所以对于频繁修改字符串的场景,可以使用 StringBuilderStringBuffer

性能对比示例

public class Dome {  
    public static void main(String[] args) {  
        // 迭代次数  
        final int ITERATIONS = 100000;  
  
        // 使用普通字符串拼接  
        long start1 = System.currentTimeMillis();  
        String str = "";  
        for (int i = 0; i < ITERATIONS; i++) {  
            // 每次都会创建新 String 对象  
            str += "a";  
        }  
        long end1 = System.currentTimeMillis();  
        System.out.println("字符串拼接耗时: " + (end1 - start1) + "ms");  
  
        // 使用 StringBuilder 拼接  
        long start2 = System.currentTimeMillis();  
        StringBuilder sb = new StringBuilder();  
        for (int i = 0; i < ITERATIONS; i++) {  
            // 直接修改内部字符数组  
            sb.append("a");  
        }  
        long end2 = System.currentTimeMillis();  
        System.out.println("StringBuilder 耗时: " + (end2 - start2) + "ms");  
    }  
}

StringBuilderStringBuffer 的区别

  • StringBuilder:非线程安全,性能更高
  • StringBuffer:线程安全(方法均使用 synchronized 修饰),性能稍低

StringBuilderStringBuffer 中常用的方法

下列方法两种类的用法完全一致,仅在线程安全上有差异。

  1. 构造器:默认容量 16,或指定容量,或初始内容。
    StringBuilder sb0 = new StringBuilder(); // 空,容量16
    StringBuilder sb1 = new StringBuilder(32); // 指定容量32
    StringBuilder sb2 = new StringBuilder("Hello"); // 以"Hello"开头
    
  2. append(Object obj):追加内容到末尾。
    StringBuilder sb = new StringBuilder("Hello");
    sb.append(", Java ").append(8);
    System.out.println(sb); // Hello, Java 8
    
  3. insert(int offset, Object obj):在指定下标插入。
    StringBuilder sb = new StringBuilder("Hello, Java!");
    sb.insert(7, "world and ");
    System.out.println(sb); // Hello, world and Java!
    
  4. delete(int start, int end):删除区间 [start,end)[start, end) 字符。
    StringBuilder sb = new StringBuilder("Hello, world and Java!");
    sb.delete(0, 7);
    System.out.println(sb); // world and Java!
    
  5. replace(int start, int end, String str):用新串替换区间。
    StringBuilder sb = new StringBuilder("Hello, world");
    sb.replace(7, 12, "java"); // 将 world 替换为 java
    System.out.println(sb); // "Hello, java"
    
  6. reverse():原地反转字符序列。
    StringBuffer bf = new StringBuffer("abcd");
    bf.reverse();
    System.out.println(bf); // dcba
    
  7. length():实际字符数。
    StringBuffer bf = new StringBuffer("Hello, world"); // 长度 12
    System.out.println(bf.length()); // 12
    
  8. capacity():返回当前缓冲区容量。
    StringBuffer bf = new StringBuffer("Hello, world"); // 长度 12,容量 12 + 16 = 28
    System.out.println(bf.capacity()); // 28
    
  9. charAt(int index):取单个字符。
    StringBuffer bf = new StringBuffer("Hello, world");
    char c = bf.charAt(4);
    System.out.println(c); // o
    
  10. indexOf(String str) / lastIndexOf(String str):查找子串位置。
    StringBuffer bf = new StringBuffer("Hello, world");
    int first = bf.indexOf("e"); // 1
    int last  = bf.lastIndexOf("r"); // 9
    
  11. substring(int start, int end):取子串(左闭右开)。
    StringBuffer bf = new StringBuffer("Hello, world");
    String sub = bf.substring(1, 5);
    System.out.println(sub); // ello
    
  12. toString():转为不可变 String
    StringBuilder sb = new StringBuilder("Hello");
    String result = sb.toString(); // "Hello"
    
加油啊!即便没有转生到异世界,也要拿出真本事!!!\(`Δ’)/
最后更新于 2025-07-15