1. String类简介

  • String类:代表字符串。Java程序中所有的字符串字面值(如"abc")都作为此类的实例实现
  • String是一个final类,代表不可变的字符序列
  • 字符串是常量,用双引号引起来表示。它们的值在创建后不能更改。
  • String对象的字符内容存储在一个字符数组value[]
  • String实现的接口:

    • String实现了Serializable接口,表示字符串支持序列化
    • String实现了Compareable接口,可以进行比较

2. 字符串的特征

2.1 字符串不可变性:

  • 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
  • 对现有字符串进行连接操作时,也需要重新指定内存区域赋值
  • 调用Stringreplace()方法时,也必须指定新的内存区域进行赋值
@Test
public void test1() {
    String s1 = "abc";// 字面量定义方式
    String s2 = "abc";

    System.out.println(s1 == s2);

    String s3 = "abc";
    s3 += "def";
    System.out.println("s2:" + s2);
    System.out.println("s3:" + s3);

    String s4 = "abc";
    String s5 = s4.replace('a', 'm');
    System.out.println("s4:" + s4);
    System.out.println("s5:" + s5);

}

运行结果:

true
s2:abc
s3:abcdef
s4:abc
s5:mbc

==比较两个对象时,会比较两者的内存地址是否相同,运行结果为true说明s1s2在内存中是同一个地址。

通过字面量(区别于new)的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。字符串常量池不会存储相同内容的字符串。

2.2 字符串不同实例化方式的区别

String实例化方式:

  • 通过字面量定义
  • 通过new String()
@Test
public void test2() {
    // 通过字面量定义的方式,此时s1和s2的数据声明在方发区中的字符串常量池中
    String s1 = "JavaEE";
    String s2 = "JavaEE";

    // 通过new+构造器方式,此时s3与s4保存的地址值是数据在堆空间中开辟空间以后对应的地址值
    String s3 = new String("JavaEE");
    String s4 = new String("JavaEE");

    System.out.println("s1 == s2? " + (s1 == s2));
    System.out.println("s1 == s3? " + (s1 == s3));
    System.out.println("s1 == s4? " + (s1 == s4));
    System.out.println("s3 == s4? " + (s3 == s4));
}

运行结果:

s1 == s2? true
s1 == s3? false
s1 == s4? false
s3 == s4? false

通过字面量定义的方式,两个变量直接指向方法区的常量池中的字符串,而通过new关键字的方式创建的字符串,变量指向的是堆中的对象地址,但最终使用的还是常量池中的内容,即使用new关键字创建字符串,共产生两个对象,一个是String对象,另一个是常量池中的value[]数组。

2.3 字符串拼接的理解

  • 常量与常量的拼接结果也存放在常量池,且常量池中不会存在相同内容的常量
  • 只要有一个是变量,结构就在堆中
  • 如果拼接的结果调用intern()方法,返回值就在常量池中。
@Test
public void test3() {
    String s1 = "javaEE";
    String s2 = "hadoop";
    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;


    System.out.println("s3 == s4: " + (s3 == s4));
    System.out.println("s3 == s5: " + (s3 == s5));
    System.out.println("s3 == s6: " + (s3 == s6));
    System.out.println("s3 == s7: " + (s3 == s7));
    System.out.println("s5 == s6: " + (s5 == s6));
    System.out.println("s5 == s7: " + (s5 == s7));
    System.out.println("s6 == s7: " + (s6 == s7));
    
    String s8 = s5.intern();
    System.out.println("s3 == s8: " + (s3 == s8));
}

运行结果:

s3 == s4: true
s3 == s5: false
s3 == s6: false
s3 == s7: false
s5 == s6: false
s5 == s7: false
s6 == s7: false
s3 == s8: true

3. String中的常用方法

  • int length():返回字符串长度
  • char charAt(int index):返回某索引处的字符
  • boolean isEmpty():判断是否是空字符串
  • String toLowerCase():使用默认语言环境,将String字符串中所有字符转换为小写
  • String toUpperCase():使用默认语言环境,将String字符串中所有字符转换为大写
  • String trim():返回字符串的副本,忽略前导空白和尾部空白
  • boolean equals(Object obj):比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String anotherString):比较字符串内容是否相同但忽略大小写
  • String contact(String str):将指定字符串连接到此字符串尾部
  • int compareTo(String anotherString):比较两个字符串的大小
  • String sunString(int beginIndex):返回一个新的字符串,它是此字符串从begin处开始截取一直到结尾
  • String subString(int befinIndex, int endIndex):返回一个新字符串,从begin开始,end结束
@Test
public void test() {
    String s1 = "Hello World";
    String s2 = "    Hello World     ";
    System.out.println("s1.length(): " + s1.length());
    System.out.println("s1.charAt(3): " + s1.charAt(3));
    System.out.println("s1.isEmpty(: " + s1.isEmpty());
    System.out.println("s1.toLowerCase(Locale.ROOT): " + s1.toLowerCase());
    System.out.println("s1.toUpperCase(): " + s1.toUpperCase());
    System.out.println("s2.trim(): " + s2.trim());
    System.out.println("s1.equals(s2): " + s1.equals(s2));
    System.out.println("s1.concat(s2): " + s1.concat(s2));
    System.out.println("s1.compareTo(s2): " + s1.compareTo(s2));
    System.out.println("s1.substring(2,8): " + s1.substring(2,8));// 左闭右开区间
    System.out.println("s1.substring(2): " + s1.substring(2));
}

运行结果:

s1.length(): 11
s1.charAt(3): l
s1.isEmpty(: false
s1.toLowerCase(Locale.ROOT): hello world
s1.toUpperCase(): HELLO WORLD
s2.trim(): Hello World
s1.equals(s2): false
s1.concat(s2): Hello World    Hello World     
s1.compareTo(s2): 40
s1.substring(2,8): llo Wo
s1.substring(2): llo World

注意,返回值为字符串类型的方法会返回一个新的字符串,原有字符串不会改变(字符串不可变性)。

  • boolean endsWith(String suffix):测试此字符串是否以指定后缀结束
  • boolean startsWith(String prefix):测试此字符串是否以指定前缀开始
  • boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的字符串是否以指定前缀开始
  • boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定索引开始
  • int lastIndexOf(String str):返回指定子字符串再此字符串中最右边出现处的索引
  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf()lastIndexOf()如果未找到都会返回-1

@Test
public void test2() {
    String s1 = "Hello World";
    System.out.println("s1.endsWith(\"ld\"): " + s1.endsWith("ld"));
    System.out.println("s1.startsWith(\"He\"): " + s1.startsWith("He"));
    System.out.println("s1.startsWith(\"ll\", 2): " + s1.startsWith("ll", 2));
    System.out.println("s1.contains(\"ell\"): " + s1.contains("ell"));
    System.out.println("s1.indexOf(\"el\"): " + s1.indexOf("el"));
    System.out.println("s1.indexOf(\"l\", 5): " + s1.indexOf("l", 5));
    System.out.println("s1.lastIndexOf(\"el\"): " + s1.lastIndexOf("el"));
    System.out.println("s1.lastIndexOf(\"l\", 3): " + s1.lastIndexOf("l", 3));
}

运行结果

s1.endsWith("ld"): true
s1.startsWith("He"): true
s1.startsWith("ll", 2): true
s1.contains("ell"): true
s1.indexOf("el"): 1
s1.indexOf("l", 5): 9
s1.lastIndexOf("el"): 1
s1.lastIndexOf("l", 3): 3

当子字符串在字符串中只出现了一次或者不存在时,indexOf()lastIndexOf()返回值相同

  • String replace(char oldChar, char newChar):返回一个新的字符串,它是newChar将原字符串中出现的所有oldChar替换后得到的
  • String replace(CharSequence target, CharSequence replacement):使用指定字符串的字面值替换字符串中所有与目标匹配的字符串序列
  • String replaceAll(String regex, String replacement):使用给定的replacement字符串替换所有匹配到指定正则表达式的子字符串
  • String replaceFirst(String regex, String replacement):使用给定的replacement字符串替换第一个匹配到指定正则表达式的子字符串
  • boolean matches(String regex):判断字符串是否与指定正则表达式匹配
  • String[] split(String regex):根据指定正则表达式的匹配拆分字符串
  • String[] split(String regex, int limit):根据匹配给定的正则表达式拆分此字符串,最多不超过limit个,如果超过了,剩下的全部放到最后一个元素中
@Test
public void test3() {
    String s1 = "Hello World";
    System.out.println("s1.replace('e', 'a'): " + s1.replace('e', 'a'));
    System.out.println("s1.replace(\"World\", \"Java\"): " + s1.replace("World", "Java"));
    System.out.println("s1.replaceAll(\"\\\\w+\", \"#\"): " + s1.replaceAll("\\w+", "#"));
    System.out.println("s1.replaceFirst(\"\\\\w+\", \"#\"): " + s1.replaceFirst("\\w+", "#"));
    System.out.println("s1.matches(\"^H.+d$\"): " + s1.matches("^H.+d$"));
    String[] arr = s1.split("l");
    for (String s : arr) {
        System.out.println(s);
    }
}

运行结果:

s1.replace('e', 'a'): Hallo World
s1.replace("World", "Java"): Hello Java
s1.replaceAll("\\w+", "#"): # #
s1.replaceFirst("\\w+", "#"): # World
s1.matches("^H.+d$"): true
He

o Wor
d

4. String与其它类型的转换

4.1 String与基本数据类型、包装类的转换

  • String to 基本数据类型或包装类:调用包装类中的parseXxx()方法
  • 包装类 to String:使用String.valueOf()

4.2 String与字符数组转换

  • String to 字符数组:使用String.toCharArray()
  • 字符数组 to String:直接调用String的构造器
@Test
public void test4() {
    String str1 = "abc123";
    char[] charArray = str1.toCharArray();
    for (char c : charArray) {
        System.out.print(c+"\t");
    }
    System.out.println();
    
    char[] arr = new char[]{'1', '2', '3', 'a', 'b', 'c'};
    String str2 = new String(arr);
    System.out.println(str2);
}

运行结果:

a   b   c   1   2   3   
123abc

4.3 Strint与字节数组的转换

  • String to 字节数组:使用String.getBytes()
  • 字节数组 to String:直接调用String的构造器
@Test
public void test5() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();// 使用默认编码集
    System.out.println("str1.getBytes(): "+Arrays.toString(bytes));
    byte[] gbks = str1.getBytes("gbk");
    System.out.println("str1.getBytes(\"gbk\"): "+Arrays.toString(gbks));
    String str2 = new String(bytes);
    System.out.println("bytes to String: "+str2);
    String str3 = new String(gbks);
    System.out.println("bytes to String in gbk use default charset: "+str3);
    String str4 = new String(gbks, "gbk");
    System.out.println("bytes to String in gbk use gbk charset: "+str4);
}

运行结果:

str1.getBytes(): [97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
str1.getBytes("gbk"): [97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
bytes to String: abc123中国
bytes to String in gbk use default charset: abc123�й�
bytes to String in gbk use gbk charset: abc123中国

在转换过程中要注意字符集的问题,如果字符集使用错误会出现乱码。编码时使用什么字符集,解码时就应该使用什么字符集

5. StringBuffer和StringBuilder

5.1 String、StringBuffer、StringBuilder的区别

  • 不同

    • String:不可变字符序列
    • StringBuffer:可变字符序列:线程安全但效率偏低
    • StringBuilder:可变字符序列:效率高但存在线程安全问题(jdk5.0 新增)
  • 相同

    • 都是字符串相关类
    • 底层都在维护一个char[]数组
使用多线程操作同一个字符串时建议使用StringBuffer,如果是单线程则使用StringBuilder

5.2 StringBuffer的源码分析

  • StringBuffer底层创建了一个长度为16的字符数组
  • StringBuffer重写了length()方法,调用时返回的不是数组的长度而是当前字符的个数
  • 当字符长度大于字符数组长度时,会进行扩容,默认情况下,会扩容为原数组长度的 “2倍+2”,同时将原有数组中的元素复制到新的数组中。
在开发中,建议使用:StringBuffer(int capacity)StringBuilder(int capacity)方式创建,避免频繁扩容操作影响性能。

5.3 StringBuilder中的常用方法

  • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
  • StringBuffer delete(int start, int end):删除指定位置的内容
  • StringBuffer replace(int start, int end, String str):把[stert, end)位置替换为str
  • StringBuffer insert(int offset, xxx):在指定位置插入xxx
  • StringBuffer reverse():把当前字符序列逆转

注意点:

  • appendinsert时,如果原来的value数组长度不够,可进行扩容
  • 如上方法支持方法链操作
@Test
public void test() {
    StringBuffer sb = new StringBuffer();
    System.out.println("sb: " + sb.append("Hello").append(" ").append("World"));
    System.out.println("sb.insert(3, \"@\"): " + sb.insert(3, "@"));
    System.out.println("sb.delete(3,4): " + sb.delete(3,4));
    System.out.println("sb.replace(6,11, \"Java\"): " + sb.replace(6,11, "Java"));
    System.out.println("sb.reverse(): " + sb.reverse());
}

运行结果:

sb: Hello World
sb.insert(3, "@"): Hel@lo World
sb.delete(3,4): Hello World
sb.replace(6,11, "Java"): Hello Java
sb.reverse(): avaJ olleH

5.4 三者效率对比

@Test
public void test1() {
    // 初始化
    long startTime = 0L;
    long endTime = 0L;
    String text = "";
    StringBuffer buffer = new StringBuffer("");
    StringBuilder builder = new StringBuilder("");

    // 开始对比
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        buffer.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuffer的执行时间:" + (endTime - startTime) + "ms");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        builder.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuilder的执行时间:" + (endTime - startTime) + "ms");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        text = text + i;
    }
    endTime = System.currentTimeMillis();
    System.out.println("String的执行时间:" + (endTime - startTime) + "ms");

}

运行结果:

StringBuffer的执行时间:9ms
StringBuilder的执行时间:4ms
String的执行时间:1814ms

执行效率排序:StringBuilder > StringBuffer > String

最后修改:2021 年 03 月 17 日
如果觉得我的文章对你有用,请随意赞赏