功能概述
- 字符串是Java编程中常用的数据类型,本文对String部分常见功能做了对应实践以及分析。
功能实践
场景1:字符串比较
用例代码
@Test
public void test_string_compare() {
String s1 = "abc";
String s2 = s1;
String s5 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println("s1 == s5: " + (s1 == s5));
System.out.println("s1 == s2: " + (s1 == s2));
System.out.println("s1.equals(s2): " + s1.equals(s2));
System.out.println("s3 == s4: " + (s3 == s4));
System.out.println("s1.equals(s4): " + s1.equals(s4));
System.out.println("s3.equals(s4): " + s3.equals(s4));
}
运行结果
s1 == s5: true
s1 == s2: true
s1.equals(s2): true
s3 == s4: false
s1.equals(s4): true
s3.equals(s4): true
结果分析
- “==” 比较基本类型时,判断值是否相等,比较对象类型时,判断是否指向同一内存地址。
- equals是比较两个对象是否一样(即所有成员的值是否相同)。
- 字符串,如"abc"是放在常量池中的,在内存中只存在一份副本,所以s1 == s5,同一个字符串,只有一个内存地址(会在常量池中检查是否存在已有的字符串,存在时,返回原有字符串的内存地址,就不会新建新的内存地址了)。
- s3、s4指向的对象都是通过new创建的新对象,内存地址不一样,所以比较为false。
场景2:字符串intern方法使用
用例代码
@Test
public void test_intern_v1() {
// 未使用intern时
String s1 = new String("aaa777");
String s2 = "aaa777";
System.out.println(s1 == s2);
// 使用intern时
String s3 = new String("aaa777");
s3 = s3.intern();
String s4 = "aaa777";
System.out.println(s3 == s4);
}
运行结果
false
true
结果分析
- s1是指向堆中对象的引用,s2是指向常量池中字符串的引用,所以两者的内存地址不一样。
- intern()会查找常量池中是否存在"aaa777"的引用,若存在返回,否则把String对象添加到池中,再返回在池中的引用。
场景3:String、StringBuffer、StringBuilder性能比较
用例代码
@Test
public void test_String_StringBuffer_StringBuilder_efficiency() {
testString();
testStringBuffer();
testStringBuilder();
}
private void testString() {
String s = "Hello";
String s1 = "World";
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + s1;
}
long end = System.currentTimeMillis();
long runtime = (end - start);
System.out.println("testString runtime:" + runtime);
}
private void testStringBuilder() {
StringBuilder s = new StringBuilder("Hello");
String s1 = "World";
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s.append(s1);
}
long end = System.currentTimeMillis();
long runtime = (end - start);
System.out.println("testStringBuilder runtime:" + runtime);
}
private void testStringBuffer() {
StringBuffer s = new StringBuffer("Hello");
String s1 = "World";
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s.append(s1);
}
long end = System.currentTimeMillis();
long runtime = (end - start);
System.out.println("testStringBuffer runtime:" + runtime);
}
运行结果
testString runtime:646
testStringBuffer runtime:1
testStringBuilder runtime:0
结果分析
- 在执行方面来看:StringBuilder最高、StringBuffer次之、String最低。
场景4:StringTokenizer字符串分割
用例代码
@Test
public void test_StringTokenizer() {
StringTokenizer st2 = new StringTokenizer("Hello&World&!","&"); //指定自定义分隔符
while (st2.hasMoreTokens()) {
System.out.println(st2.nextToken());
}
}
运行结果
Hello
World
!
结果分析
- StringTokenizer可按指定分隔符,第字符串进行分隔。
场景5:字符串与字符数组比较
用例代码
@Test
public void test_string_with_char_array_compare() {
String s = "hello";
String t = "hello";
char c[] = {'h','e','l','l','o'};
System.out.println(s.equals(t));
System.out.println(t.equals(c));
System.out.println(s == t);
System.out.println(t.equals(new String("hello")));
String s1 = "he" + "llo";
System.out.println(s == s1);
}
运行结果
true
false
true
true
true
结果分析
- s.equals(t)值为true:因为"hello"会存在堆中的字符串常量池中,并且按照享元模式,判断字符串是否存在,不存在才创建,所以s、t是同一个字符串引用
- t.equals©值为false:按照String重写的equals方法,会判断入参是否为String类型,若不是则返回false
- s == t值为true:==比较的是引用,因为s、t是同一个引用,所以相等
- t.equals(new String(“hello”))值为true:因为String的比较逻辑,会去字符串中的字符数组来比较,字符都相等,则返回true
- s == s1值为true:因为"he" + “llo"在编译器就会转化"hello”,存在常量区,因为s已经放在常量池中,与s1不会创建新的字符串,s与s1是同一个字符串引用
功能总结
-
String常量池采用了享元模式(Flyweight)即会共享字符串,判断字符串是否存在,若存在则使用,否则新创建。常量池:它是一个由数组组成的表,用来存储程序中使用的各种常量,包括Class、String、Integer等各种基本的Java数据类型
-
intern()方法分析:(intern:助理、实习生)
-
intern方法主要把字符串放入字符串常量池中。有两种情况,会将字符串放在常量池中
- 直接使用双引号声明的String对象会直接存储在常量池中,如:String s1 = “aa” 。
- 通过调用String提供的intern方法把字符串放在常量池中,会检查字符串在常量池中是否存在,存在则不处理,若不存在则放入字符串常量池中。
-
Returns a canonical representation for the string object. (jdk中的描述)
- 根据jdk中描述:intern()会返回字符串对象的规范标识,即会从常量池中查找指定字符串,若能找到则返回对应引用,否则把String对象添加到池中,再返回在池中的引用。
-
注明:
- a)字符串放入字符串常量池,需要满足上面所说的两种情况。
- b)并不是调用了intern就会将字符串放入字符串常量池中,会先检查字符串是否存在,若存在则不处理。若不存在,要看字符串在堆中是否已存在,已存在只存对象引用,减少对象的创建 c)字符串常量池,在jdk1.7后就从Perm区迁移到堆中,这样对于字符串常量池中可以存对象引用,减少了对象的创建,就大大减少了字符串所占的空间了。
-
-
String、StringBuilder、StringBuffer使用总结:
- 如果要操作的数据量比较小,优先使用String类(量多的话,String本质是使用StringBuilder处理的,会产生许多临时对象,触发垃圾回收,影响性能)
- 如果是单线程下处理大量数据,优先使用StringBuilder类(StringBuilder是线程不安全的,单线程下可以使用,减少线程同步的开销)
- 如果是多线程下处理大量数据,优先使用StringBuffer类(因为是线程安全的)