【Java面试基础篇】浅谈字符串
浅谈字符串
字符串操作是计算机程序设计中最常见的行为
1.String
String对象的基本特性
String对象是不可变的,通过查看JDK源码可以发现,String类中所有关于字符串的修改方法,都是创建了一个新的String对象。而原String对象并没有任何变化。
先看一段代码,代码中有两个String对象,name和newName。我们把name作为参数,传给了lowerCase()方法。实际上我们传递的是对象引用的一个拷贝,引用实际指向的对象并没有任何变化。
public static String lowerCase(String str){
return str.toLowerCase();
}
public static void main(String[] args){
String name = "Admin";
System.out.println("name is" + name);
String newName = lowerCase(name);
System.out.println("name is :" + name);
System.out.println("newName is:" + newName);
} 运行结果:
name isAdmin
name is :Admin
newName is:adminString 是 Immutable 类的典型实现,原生的保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。
针对常量池的优化。
- 当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
字符串缓存
String 在 jdk1.6 以后提供了 intern() 方法,目的是提示 JVM 把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用 intern() 方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。一般来说,JVM 会将所有的类似“abc”这样的文本字符串,或者字符串常量之类缓存起来。
jdk1.6中,字符串缓存被放在永久代,一般不会被GC,这样会导致OOM
Jdk1.6以后这个缓存被放置在堆中,这样就极大避免了永久代占满的问题。
jdk1.8中,永久代被MetaSpace(元数据区)替代了。而且,默认缓存大小也不断地扩大。
打印缓存大小
-XX:+PrintStringTableStatistics
设置缓存大小为N
-XX:StringTableSize=NIntern 是一种显式地排重机制,需要开发者手动调用。但是在实际开发中,往往无法预估字符串的重复情况。好在jdk8u20后,可以使用G1 GC 下的字符串排重。它是通过将相同数据的字符串指向同一份数据来实现,无需手动调用,通过jvm底层来实现。开发人员只需要指定使用G1 GC 并设置参数即可
-XX:+UseStringDeduplication
String对象的重载
我们可以使用”+“来实现Sting对象的重载操作
代码如下
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1);
str1 = str1 + "world";
System.out.println(str1);
}运行结果
hello
helloworld 通过以上操作我们可以看到str1被修改为“helloworld”,这就是字符串的重载,我们可以通过javap反编译看看这段代码做了些什么
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: new #5 // class java/lang/StringBuilder
13: dup
14: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: ldc #8 // String world
23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_1
30: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: return
} 通过以上字节码我们可以看到,编译器帮我们自动引入了java/lang/StringBuilder类。虽然我们没有主动使用它,但是编译器对字符串重载做了优化。通过创建了StringBuilder对象,使用它的append()方法来提高程序性能。减少中间生成的垃圾对象。
2.StringBuffer和StringBuilder
相同点
1.对象的值都可以改变
2.都实现了AbstractStringBuilder抽象类
3.与String在内存中的存储方式相同,都是一个有序的字符序列(char类型的数组)进行存储
4.底层实现都是通过字符数组的操作来实现的,如果涉及到较大的数据量扩容会有一定的性能问题。不同点:
1.StringBuffer是线程安全的
2.StringBuilder是线程不安全的
3.StringBuffer的性能要远低于StringBuilder(StringBuffer类中方法定义前面都会有synchronize关键字)@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
3.重提字符串压缩
众所周知jdk1.6时,虚拟机参数-XX:+UseCompressedStrings可以设置使用字节数组来存储字符串,但是在1.7中被废弃了。时过境迁,在1.9中又引入了Compact String。
字符串实现的改变
private final byte[] value;
private final byte coder;
coder取值
static final byte LATIN1 = 0;
static final byte UTF16 = 1;coder的作用
如果字符串只包含 LATIN-1编码的字符,coder变量的值将为 0,因此字符串的长度就为字节数组的长度一致,否则coder变量的值为1,采用 UTF-16 编码,那么字符串的长度为字节数组长度的一半。public int length() {
return value.length >> coder;
}
笔者比较菜,目前还在1.8上做开发,以后有使用心得再分享吧