面试_java_String字符串相关知识

108 阅读6分钟

常用的方法

  • equals()
  • length()
  • append()
  • endsWith()
  • startsWith()
  • charAt()
  • isEmpty()
  • split()
  • substring(pos,N)
  • indexOf(str)
  • toLowerCase()
  • toUpperCase()



创建字符串对象的不同方式

 String s = new String("bbb");

创建了2或1个实例对象。

第一个是在类【解析】的时候,生成一个字符串字面量对象放到堆中,然后字符串池中存放该对象的引用。当然,如果字符串池中已经有这个字面量对象的引用的话,第一个字面量对象就不用创建了,相当于什么也没发生。 第二个是在类【初始化】的时候用new来动态创建的,生成一个对象放到堆中(与第一个不同),然后将这个对象的引用存到栈中,并返回给s。所以,s指向的是new出来的那个对象。

String s = "aaa";

创建一个实例对象,也就是在类【解析】的时候,生成一个字符串字面量对象放到堆中,然后字符串常量池池中存放该对象的引用,并返回给s。




不可改变、不可继承

String类是final类,也即意味着String类不能被继承,不可变,并且它的成员方法都默认为final方法。

String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。比如sub、concat、replace操作、重新赋值操作、字符串编辑操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串字面量对象,都是在这个新对象副本上操作的。


比如:

String a = "aaa";
String b = a;
b = "bbb";
System.out.println(a);    // aaa
System.out.println(b);    // bbb

第二句没有问题,效果就是引用a和b同时指向字符串对象”aaa“,但是第三句生成了新的字符串对象”bbb“,被引用b指向;原来的”aaa“对象仍然存在,被引用a指向。

另外,想通过下标修改字符串中的某一位也是不可行的,会在编译器报错,因为字符串不支持下标操作。




尝试“修改”String的原理?

首先创建一个 StringBuffer,然后调用 StringBuffer 的 append() 方法,最后调用 StringBuffer 的 toString() 方法返回一个新的字面量String对象。




final 修饰 StringBuffer 后还可以 append 吗?

可以。final 修饰的 StringBuffer 只是一个引用变量,堆中这个对象内部的属性是可以变化的。

其实这个题和String无关,和final关键字有关。




为什么String是对象类型的?

因为String底层是数组实现的(一个final的char数组),而数组是对象的一种。




为什么要设计成不可变

  • 便于实现字符串池,可复用。

  • 线程安全:不可变的对象天生线程安全




真的不可变吗?

可以用反射来改变String中value的值,所以严格意义上说不一定不可变




“+” 操作

每遇到一个 “+” ,就会生成一个StringBuilder对象,通过append方法完成与后面内容的拼接,然后再自动调用一个toString()方法转化为String 字面量对象 放到堆,最后把堆中地址存到字符串常量池中,并返回给等号左边的引用变量一份。

String a = "hello";
String s1 =a + new String("world");  // 返回的是字面量对象,也就是第一个生成的那个对象
String s2 = s1.intern();
System.out.println(s2 == s1);  //true

String s3 = "helloworld";
System.out.println(s3==s1);   // true

另外,对于执行时机,也有区别

  • 如果 “+” 两边都是纯字面量,包括以下两种情况,会在编译期直接拼接,只创建合并后的那个对象。

    String str = "hello"+"world"
    String str = new String("hello") + new String("world");
    
  • 如果 “+” 两边至少存在一个引用或者方法的调用,那这个 “+” 就只能等到执行期才能拼接。

    特殊情况:用final修饰的字符串变量,等同于纯字面量。例如下面的a + "world"

    其中的a不会被看作是引用。

    final a  = "hello";
    String str = a + "world"    // 等同于 "hello"+"world"
    



小例题

String怎样翻转

public static String reverseStringBuffer(String s){
   StringBuffer sb = new StringBuffer(s);
   String afterReverse = sb.reverse().toString();
   return afterReverse;
  }


以下的第三行代码创建出几个对象(第一和第二行不算入)

String var1 = "abc";
String var2 = new String("abc");
String str = var1 + var2;

答:两个对象,一个是String对象,另一个是StringBuilder对象,首先创建一个StringBuilder对象,调用append方法把var1和var2拼接起来,最后调用toString()方法返回一个String对象



以下三种创建String对象的方式有什么区别吗?

String str1 = new String(); 
String str2 = new String("");   // 空串在Java虚拟机启动的时候默认生成,因此不生成字面量对象
String str3 = "";    			// 空串在Java虚拟机启动的时候默认生成,因此生成了0个对象

答:str1和str2都是在堆内存里创建了一个通过new产生的对象,但都没有生成字符串字面量对象,前者是因为null,而后者是利用了虚拟机启动时默认生成的空串。str3相比于前两种,什么都没有创建。



以下代码中,str2和str3哪种创建方式更好?

    String str1 = "abc";
    String str2 = new String("abc");
    String str3 = new String(str1);

答:s3好。因为在编译的时候,如果直接用字符串常量的方式构造,编译器会去遍历常量区里的所有字符串,直到找到"abc"字符串,再去构造str2。而str3在构造的时候,由于str1指向的是一个引用地址,相当于直接告诉str3你要的字符串常量就在我保存的这个位置,这样就省去了遍历常量区的过程,速度会更快一点。



以下代码打印出来的结果是什么

char[] chars = {'a','b','c'};
String str = new String(chars);
chars[0] = 'x';
System.out.println(str);

答:是abc,使用带char数组参数的构造方法,底层是把char数组的内容复制一份给了字符串,所以chars数组和str底层的value数组不是同一个数组,即使改变了chars数组的内容,str还是不变。



为什么在循环的时候,String用+号进行字符串拼接效率低? 答:因为使用+进行拼接的话,编译器每一次的拼接都会创建一个StringBuilder对象将每个+号后面的字符串通过append方法拼接起来,但是每循环一次都会产生一个StringBuilder对象,当循环的次数很大的时候,效率自然就降低了



String中的 hashCode() 函数,计算方法:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 

s[i] 的值是字符的ASCII值;n是字符串长度




segmentfault.com/a/119000002… juejin.cn/post/684490…