- Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
- String是一个final类,代表不可变的字符序列。
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字符数组value[]中的
创建
存储
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder
来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
字符串常量存储在字符串常量池,目的是共享
字符串非常量对象存储在堆中
String s1 = "hello world";
String s2 = "hello world";
String s3 = new String("hello world");
String s4 = new String("hello world");
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s3==s4);
不可变的好处
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
Program Creek : Why String is immutable in Java?
空字符串
“”表示空字符串,表示没有任何内容,空字符串是分配了内存空间,
null是没有分配内存空间。
类型转换
valueOf
基本数据类型、包装类 转成字符串,调用String类的public String valueOf(int n)可将int型转换为字符串
相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double
d)、valueOf(boolean b)可由参数的相应类型到字符串的转换
String intVal = String.valueOf(1);
System.out.println(intVal);
String floatVal = String.valueOf(2.5);
System.out.println(floatVal);
char[] charList = {'a','b','c'};
String charVal = String.valueOf(charList);
System.out.println(charVal);
parseXXX
Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
int i = Integer.parseInt("10");
System.out.println(i);
float v = Float.parseFloat("3.1415926");
System.out.println(v);
方法
比较
int length():返回字符串的长度: return value.length
boolean isEmpty():判断是否是空字符串:return value.length == 0
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大
小写
int compareTo(String anotherString):比较两个字符串的大小
String s1 = new String("Baxiang");
String s2 = new String("Baxiang");
String s3 = new String("BaXiang");
String s4 = s3;
System.out.println(s1==s2);// 地址比较
System.out.println(s1.equals(s2));// 字符内容比较
System.out.println(s3==s4);
System.out.println(s2.equals(s3));
System.out.println(s2.equalsIgnoreCase(s3));// 忽略大小写
字符串拼接比较
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);
System.out.println(s3==s5);
System.out.println(s4==s5);
System.out.println(s3==s6);
打印结果
false
false
false
true
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
截取
String trim():返回字符串的副本,忽略前导空白和尾部空白
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字
符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。查找
char charAt(int index): 返回某索引处的字符return value[index]
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方法如果未找到都是返回-1String string = "we are family";
System.out.println(string.length());
// 搜索字符的位置 返回首次出现的位置
System.out.println("e的位置"+string.indexOf("e"));
// 搜索字符的位置 返回最后出现的位置
System.out.println("e的位置"+string.lastIndexOf("a"));
转换
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
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个,如果超过了,剩下的全部都放到最后一个元素中连接
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
字符串连接可以使用加号(+)和concat(String str)方法,加号运算符的优势就是可以把任意类型数据拼接成字符串,而concat只能拼接String类型字符串
String s1 = "Hello";
String s2 = s1+2;
System.out.println(s2);
String s3 = s1.concat(" World");
System.out.println(s3);
StringBuffer
StringBuffer是线程安全的,它的方式支持线程同步,线程同步会操作串行顺序执行,在单线程环境下回影响效率。
方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
StringBuider
StringBuilder是StringBuffer单线程版本,它不是线程安全的,但它的执行效率最高。
StringBuilder str = new StringBuilder();
System.out.println(str.length());
System.out.println(str.capacity());//默认缓冲器容量是16
StringBuilder str1 = new StringBuilder("hello");
System.out.println(str1.length());
System.out.println(str1.capacity());//
字符串增加
StringBuilder str = new StringBuilder("Hello");
str.append(" ").append("World");
System.out.println(str);
字符串插入,删除,替换
StringBuilder str = new StringBuilder("Hello World");
str.insert(5," Java");
System.out.println(str);
str.delete(5," Java".length()+5);
System.out.println(str);
str.replace(6,11,"Java");
System.out.println(str);
效率测试
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
StackOverflow : String, StringBuffer, and StringBuilder
String str = "";
StringBuffer stringBuffer = new StringBuffer("");
StringBuilder stringBuilder = new StringBuilder("");
long startTime = System.currentTimeMillis();
for (int i =0;i <50000;i++){
stringBuffer.append(String.valueOf(i));
}
long endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
stringBuilder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
str = str + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
str = str.concat(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("concat的执行时间:" + (endTime - startTime));
执行结果
StringBuffer的执行时间:12
StringBuilder的执行时间:7
String的执行时间:1894
concat的执行时间:4750
String Pool
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。
String s1 =newString(“aaa”);
String s2 =newString(“aaa”);
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
String s5 =”bbb”;
String s6 =”bbb”;
System.out.println(s5 == s6); // true
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
- StackOverflow : What is String interning?
-
new String(“abc”)
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
“abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
- 而使用 new 的方式会在堆中创建一个字符串对象。
创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}
使用 javap -verbose 进行反编译,得到以下内容:
// ...
Constant pool:
// ...
#2 = Class #18 // java/lang/String
#3 = String #19 // abc
// ...
#18 = Utf8 java/lang/String
#19 = Utf8 abc
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
// ...
在 Constant Pool 中,#19 存储这字符串字面量 “abc”,#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。
以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
String 的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
String str1 ="中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 =newString(bytes, "UTF-8");
System.out.println(str2);
在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。
byte[] bytes = str1.getBytes();