Java 字符串相关面试题

问:下面程序的运行结果是什么?

String s1 = "abc";
StringBuffer s2 = new StringBuffer(s1);
System.out.println(s1.equals(s2));	// 1,false
		
StringBuffer s3 = new StringBuffer("abc");
System.out.println(s3.equals("abc"));	// 2,false
System.out.println(s3.toString().equals("abc"));	// 3,true

答:

注释 1 打印为 false,主要考察 String 的 equals 方法,String 源码中 equals 方法有对参数进行 instance of String 判断语句,StringBuffer 的祖先为 CharSequence,所以不相等; 注释 2 打印为 false,因为 StringBuffer 没有重写 Object 的 equals 方法,所以 Object 的 equals 方法实现是 == 判断,故为 false; 注释 3 打印为 true,因为 Object 的 toString 方法返回为 String 类型,String 重写了 equals 方法、实现为值比较。

问:怎样将 GB2312 编码的字符串转换为 ISO-8859-1 编码的字符串?

答:

如下代码即可实现:

String s1 = "你好";
String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

问:String、StringBuffer、StringBuilder 的区别是什么?

答:

String 是字符串常量,StringBuffer 和 StringBuilder 都是字符串变量,后两者的字符内容可变,而前者创建后内容不可变;StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,线程安全会带来额外的系统开销,所以 StringBuilder 的效率比 StringBuffer 高;String 的每次修改操作都是在内存中重新 new 一个对象出来,而 StringBuffer、StringBuilder 则不用,且提供了一定的缓存功能,默认 16 个字节数组的大小,超过默认的数组长度时扩容为原来字节数组的长度 * 2 + 2,所以使用 StringBuffer 和 StringBuilder 时可以适当考虑下初始化大小,以便通过减少扩容次数来提高代码的高效性。

问:String 为什么是不可变的?

答:

String 不可变是因为在 JDK 中 String 类被声明为一个 final 类,且类内部的 value 字节数组也是 final 的,只有当字符串是不可变时字符串池才有可能实现,字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串;如果字符串是可变的则会引起很严重的安全问题,譬如数据库的用户名密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中主机名和端口都是以字符串的形式传入,因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子改变字符串指向的对象的值造成安全漏洞;因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步,字符串自己便是线程安全的;因为字符串是不可变的所以在它创建的时候 hashcode 就被缓存了,不变性也保证了 hash 码的唯一性,不需要重新计算,这就使得字符串很适合作为 Map 的键,字符串的处理速度要快过其它的键对象,这就是 HashMap 中的键往往都使用字符串的原因。

问:说说 String str = “hello world”; 和 String str = new String(“hello world”); 的区别?

答:

在 java 的 class 文件中有专门的部分用来存储编译期间生成的字面常量和符号引用,这部分叫做 class 文件常量池,在运行期间对应着方法区的运行时常量池,所以 String str = “hello world”; 在编译期间生成了 字面常量和符号引用,运行期间字面常量 “hello world” 被存储在运行时常量池(只保存了一份),而通过 new 关键字来生成对象是在堆区进行的,堆区进行对象生成的过程是不会去检测该对象是否已经存在的,所以通过 new 来创建的一定是不同的对象,即使字符串的内容是相同的。

问:下面程序的运行结果是什么?

分段解析基于 JDK 1.7 版本分析。

String stra = "ABC"; 
String strb = new String("ABC");
System.out.println(stra == strb);	// 1,false
System.out.println(stra.equals(strb));	// 2,true

对于 1 和 2 中两个都是显式创建的新对象,使用 == 总是不等,String 的 equals 方法有被重写为值判断,所以 equals 是相等的。

String str1 = "123";
System.out.println("123" == str1.substring(0));	// 3,true
System.out.println("23" == str1.substring(1));	// 4,false

对于 3 和 4 中 str1 的 substring 方法实现里面有个 index == 0 的判断,当 index 等于 0 就直接返回当前对象,否则新 new 一个 sub 的对象返回,而 == 又是地址比较,所以结果如注释。

String str3 = new String("ijk");
String str4 = str3.substring(0);
System.out.println(str3 == str4);	// 5,true
System.out.println((new String("ijk") == str4));	// 6,false

对于 5 和 6 来说没啥分析的必要了,参见上面对于 3 和 4 的分析结果。

String str5 = "NPM";
String str6 = "npm".toUpperCase();
System.out.println(str5 == str6);	// 7,false
System.out.println(str5.equals(str6));	// 8,true

String str7 = new String("TTT");
String str8 = "ttt".toUpperCase();
System.out.println(str7 == str8);	// 9,false
System.out.println(str7.equals(str8));	// 10,true

对于 7、8、9、10 来说实质都一样,toUpperCase 方法内部创建了新字符串对象。

String str9 = "a1";
String str10 = "a" + 1;
System.out.println(str9 == str10);	// 11,true

对于 11 来说当两个字符串常量连接时(相加)得到的新字符串依然是字符串常量且保存在常量池中只有一份。

String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13);	// 12,false

对于 12 来说当字符串常量与 String 类型变量连接时得到的新字符串不再保存在常量池中,而是在堆中新建一个 String 对象来存放,很明显常量池中要求的存放的是常量,有 String 类型变量当然不能存在常量池中了。

String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16);	// 13,true

对于 13 来说此处是字符串常量与 String 类型常量连接,得到的新字符串依然保存在常量池中,因为对 final 变量的访问在编译期间都会直接被替代为真实的值。

private static String getBB() {   
	return "b";   
}
String str17 = "ab";
final String str18 = getBB();
String str19 = "a" + str18;
System.out.println(str17 == str19);	// 14,false

对于 14 来说 final String str18 = getBB() 其实与 final String str18 = new String(“b”) 是一样的,也就是说 return “b” 会在堆中创建一个 String 对象保存  ”b”,虽然 str18 被定义成了 final,但不代表是常量,因为虽然将 str18 用 final 修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此指向的不是同一个对象,所以可见看见并非定义为 final 的就保存在常量池中,很明显此处 str18 常量引用的 String 对象保存在堆中,因为 getBB() 得到的 String 已经保存在堆中了,final 的 String 引用并不会改变 String 已经保存在堆中这个事实;对于 str18 换成 final String str18 = new String(“b”); 一样会返回 false,原因同理。

String str20 = "ab";
String str21 = "a";
String str22 = "b";
String str23 = str21 + str22;
System.out.println(str23 == str20);	// 15,false
System.out.println(str23.intern() == str20);	// 16,true
System.out.println(str23 == str20.intern());	// 17,false
System.out.println(str23.intern() == str20.intern());	// 18,true

对于 15 到 18 来说 str23 == str20 就是上面刚刚分析的,而对于调用 intern 方法如果字符串常量池中已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定)则返回字符串常量池中的字符串,否则将此 String 对象添加到字符串常量池中,并返回此 String 对象的引用,所以 str23.intern() == str20 实质是常量比较返回 true,str23 == str20.intern() 中 str23 就是上面说的堆中新对象,相当于一个新对象和一个常量比较,所以返回 false,str23.intern() == str20.intern() 就没啥说的了,指定相等。

 

    原文作者:Devin_教主
    原文地址: https://blog.csdn.net/qq_32053993/article/details/82855210
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞