1. String类简介
String
类:代表字符串。Java程序中所有的字符串字面值(如"abc"
)都作为此类的实例实现String
是一个final
类,代表不可变的字符序列- 字符串是常量,用双引号引起来表示。它们的值在创建后不能更改。
String
对象的字符内容存储在一个字符数组value[]
中String
实现的接口:String
实现了Serializable
接口,表示字符串支持序列化String
实现了Compareable
接口,可以进行比较
2. 字符串的特征
2.1 字符串不可变性:
- 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的
value
进行赋值 - 对现有字符串进行连接操作时,也需要重新指定内存区域赋值
- 调用
String
的replace()
方法时,也必须指定新的内存区域进行赋值
@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
说明s1
和s2
在内存中是同一个地址。
通过字面量(区别于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()
:把当前字符序列逆转
注意点:
- 当
append
和insert
时,如果原来的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