0、前言
String类在Java中是个特殊的存在,它太常用以至于Java对于String设计了一些特殊的机制,它不是个基本数据类型,但是也和其他普通的类有一些区别。今天就对String类型做一个详细的总结,同时聊一下面试中常见的问题。
1、为什么String不可变?
首先要明确,这个不可变是指:每次对字符串进行修改时都会生成新的String对象。下面段代码可以解释:
String a= "111";
System.out.println("a="+a);
a="222";
System.out.println("a="+a);
//输出
a=111
a=222
在我们看来,a的值其实是变化了的,但是并不能说明String类是可变的。其实String a= "111";这段代码是创建了一个新的对象"111",而a="222";这段代码又新建了一个对象"222",但是原来的对象还存在于内存中,a只不过不是对象的一个引用,改变a只是将它引用的对象改变了。
因此:每次对字符串进行修改时都会生成新的String对象,这可能在频繁操作大量字符串的情况下导致性能问题。
那么String类是怎么实现不可变的
这要从源码中查看原因:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
/**
* The value is used for character storage.
*/
//java8之前
private final char value[];
//java9开始
@Stable
private final byte[] value;
字符串实际上就是一个 char 数组,在Java9后改为了byte数组,为了节约内存,不过思路一样。并且char 数组是被 final 修饰的,只要是对char数组的改变,方法内部都是返回一个新的 String 实例。
2、String 常量池
Java字符串常量池是Java语言为了优化String类,在JVM中开辟一块区域用来存储String常量,它用于缓存字符串常量,以减少字符串的创建和销毁,提高字符串的使用效率。
String str1 = "hello"; //在常量池中创建字符串常量
String str2 = new String("world"); //堆内存中创建对象,同时也会在常量池中创建常量
String str3 = new String("hello"); //堆内存中创建对象,常量池已有常量,不会创建
System.out.println(str1==str3);
System.out.println(str1.equals(str3));
//输出
false
true
str1和str3两个对象的地址不同,两个对象的值相等,都存在常量池中,所以equals方法判断二者值相等。
String str1 = "hello"; //在常量池中创建字符串常量
String str4 = "hello"; //常量池中已有此常量,不会创建新对象
System.out.println(str1==str4);
System.out.println(str1.equals(str4));
//输出
true
true
在创建str4的时候,因为常量池中已存在"hello",所以直接返回了str1的引用,因此str1和str4的地址是相同的。
String str1 = "ab";
String str2 = "a"+"b";
String str3 = "a".concat("b");
System.out.println(str1==str2); //true
System.out.println(str1==str3); //false
concat方法是通过复制数组在通过 char 数组进行拼接生成一个新的对象,所以地址值会有变动。而+会由于Java中的常量优化机制,会判断拼接后的常量在不在常量池中,若不在才会生成新对象。
3、String、StringBuffer、StringBuilder 的区别
因为String是不可变的,所以在使用String类的时候可能会频繁创建对象,造成资源浪费。因此官方提供了StringBuffer、StringBuilder两个类,可以在原字符串上进行修改,二者的区别在于是否线程安全。
三者具体区别主要用:
- String类是不可变的,它的值是常量,因此在多线程环境下是安全的。但是,由于String类是不可变的,因此它的性能较差,特别是在频繁地修改字符串的情况下。
- StringBuilder类也是可变的,它的值可以被修改,但是是线程不安全的,因此只能在单线程环境下使用,但由于不需要考虑线程同步问题,因此在单线程环境下比 StringBuffer 更高效。
- StringBuffer类是可变的,它的值可以被修改,而且是线程安全的,因此可以在多线程环境下使用。
总之:
如果需要频繁对字符串进行修改,并且在多线程环境下使用,应该使用 StringBuffer;如果只在单线程环境下使用,建议使用 StringBuilder,因为它比 StringBuffer 更高效;如果不需要对字符串进行修改,应该使用 String。
4、字符串一些常用方法
String s="abc";
System.out.println(s.length());
System.out.println(s.startsWith("a"));
System.out.println(s.substring(0, 1));
System.out.println(s.toCharArray());
System.out.println(s.charAt(1));
System.out.println(s.contains("ab"));
//输出
3
true
a
abc
b
true
int length()返回此字符串的长度。boolean startsWith(String prefix)检查字符串是否以指定的前缀开始。String substring(int beginIndex, int endIndex)根据下标返回原字符串的一个子串。char[] toCharArray()将此字符串转换为一个新的字符数组。char charAt(int index)返回指定处的字符。contains(CharSequence chars)判断是否包含指定的字符或字符串。String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。isEmpty()判断字符串是否为空。
5、小结
String类可以说是Java中最常用的类了,掌握了String的各种特性,才会在学习和工作中游刃有余。以上就是今天的全部内容啦!