字符串类型在每种编程语言中都是都是个特殊的存在,因为不管是体积还是数量,字符串都是大多数应用的重要组成部分。下面是一些典型的回答:
String: String是java语言中非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的Immutable类,被声明成final类,所有属性也都是被final修饰的,但是也正是由于String的不可变性,类似拼接,裁剪字符串类型等动作,都会产生新的字符串对象。这样多多少少都会对性能有些影响。
StringBuffer:StringBuffer是为解决上面提到的拼接,裁剪产生太多中间类的问题而提供的一个类。我们可以用append或insert方法来把字符串添加到已有字符串的末尾或指定的位置。StringBuffer由于方法中都用Synchronized修饰,所以是线程安全的,同时又带来了额外的性能方面的开销,如果是在单线程中进行字符串的拼接,建议使用StringBuffer的后继着StringBuilder.
StringBuilder: StringBuilder在能力上是和StringBuffer是没有区别的,无非它是去除了关于线程安全的部分,有效的减少了性能的开销,是大部分时候进行字符串拼接的首选函数。
StringBuffer和StringBuilder的底层都是利用可修改的数组(char,JDK9以后是byte),二者都继承了AbstractStringBuilder,里面包含了基本的操作。那么这个内部数组初始应该为多大呢?目前的实现是构建时初始长度+16(这意味着没有构建对象,没有输入最初长度,长度应该是16),如果确定要拼接的字符串长度的话,构建的时候带上长度,因为扩容会产生多重开销,要抛弃原有数组,创建新的数组(可以简单认为是倍数的数组),还要进行arraycopy。
在日常的编程中,保证程序的可读性,可维护性,往往比所谓的最优性能更重要,可以根据实际需求酌情选择具体的编码方式。
字符串的缓存技术:String在java6提供了intern()方法,目的是提示JVM把相应的字串缓存起来,以备重复使用。在我们创建字符串对象的时候并调用intern()方法的时候,如果已经有缓存的字符串,就会返回缓存的实例,否则就会将其缓存起来。一般来说JVM会将所有类似"abc"这样的文本字符串,或者字符串常量之类缓存起来。虽然提供了intern()方法,但是不建议大量使用intern,为什么呢?魔鬼存在于细节中,在JAVA6这种版本里,被缓存的字符串是存在所谓的PermGen里的,也就是臭名昭著的“永久代”,这个空间是有限的,而且也基本不会被FullGc之外的垃圾收集照顾到,所以一不小心,OutOfMemoryError(OOM内存溢出)会很快光顾的。后续的版本里这个缓存放在堆里,这样就极大的避免了永久代占满的问题,甚至永久代在JAVA8中被MetaSpace(元数据区)取代了,默认的缓存大小也在不断的扩大中,从最初的1009,到7u40以后被修改为60013,intern虽然是一种显示的排重技术,但是要显示的调用,又很难保证效率,开发阶段很难预测到字符重复的情况,有人认为这是一种污染代码的实践。
String自身的演化:
在历史版本中,String字符串是使用char数组来存储数据的,这样非常直接,但是JAVA语言的char是占2个byte的,拉丁语系的字符,根本就不需要太宽的char,这样无区别的实现就会造成了一定的浪费。密度是编程语言平台永恒不变的话题,因为绝大部分的应用是要操作数据的。
在JAVA9中,引用了Compact Strings(紧凑字符串)的设计,对字符串进行大刀阔斧的改进,将数据的存储方式从char数组改变为byte数组和标识编码的所谓的coder,并且将相关字符串操作类都进行了修改。另外所有相关的Intrinsic之类的也都进行了重写,保证没有性能上的损失。
虽然底层进行了这么大的改变,但是JAVA字符串的行为并没有发生太大的改变,所以这个特性对绝大部分应用是透明的,绝大部分不需要修改现有的代码,只要升级JDK版本,就能零成本享受到这些益处。
下面是我使用JDK12关于字符串的一些测试,注意JDK版本不一样,结果可能会有一些细微的差别。
==,equals(),hashcode()三个的不同之处?
==是比较两个对象的引用是否相等
equals()是比较两个字符串的实际值
hashcode()是计算字符串的hash值,两个对象的equals()为true,那么它们的hashcode()值一般情况下也是相等的。
public class TestString {
public static void main(String[] args) {
String s1 = new String("1111");
String s2 = new String("1111");
String s3 = "1111";
String s4 = "1111";
String s5 = new String("11") + new String("11");
String s6 = "11" + "11";
System.out.println(s1 == s2); //false
System.out.println(s3 == s4); //true
System.out.println(s1 == s3); //false
System.out.println(s1.equals(s2));//true
System.out.println(s1.equals(s3)); //true
System.out.println(s3.equals(s4)); //true
System.out.println(s1.hashCode()); //1508416
System.out.println(s2.hashCode()); //1508416
System.out.println(s3.hashCode()); //1508416
System.out.println(s4.hashCode()); //1508416
System.out.println(s1 == s5); //false
System.out.println(s3 == s5); //false
System.out.println(s1 == s6); //false
System.out.println(s3 == s6); //true
System.out.println(s1.equals(s5)); //true
System.out.println(s3.equals(s5)); //true
System.out.println(s1.equals(s6)); //true
System.out.println(s3.equals(s6)); //true
System.out.println(s5.hashCode()); //1508416
System.out.println(s6.hashCode()); //15081416
}
}