Java源码学习------String类

1、一些基本概念

  • 堆:jvm运行中申请的对象存放的位置。也就是所说的新生代+老年代,YGC就发生在这 里
  • 虚拟机栈:每个方法在被调用执行时都会创建一个虚拟机栈,用于存储临时的遍历、方法等信息。调用相当于进栈,返回结果则相当于出栈,异常输出的栈信息就是从这里来的。
  • 本地方法栈:也是方法调用,只是调用的方法是本地native方法
  • 方法区:存储的类的结构信息,静态变量等信息。也就是永生代,发生FULL GC的地方(Java8中以元空间代替了永生代这个概念)
  • 常量池:方法区的一部分,用来存放各种生成的字面量,例如定义的一个String类型的数据(Java7中把常量池移到了堆中)

2、创建字符串

String s1="hello";
String s2=new String("hello");

第一种方法是在栈中创建一个String类型的引用s1,然后在常量池中寻找,如果常量池中存在hello的字符串数据,则直接把s1指向常量池中hello的地址;否则会在常量池中创建hello这个字符串数据,然后把s1指向新创建的hello地址。

第二种方法是在栈中创建一个String类型的引用s2,然后在常量池中寻找,如果常量池中存在该数据,则在堆中复制拷贝该数据,然后把s2指向堆中新建的地址;否则会创建一个字符串存放在常量池中,然后进行拷贝一份到堆中,s2指向堆的地址。

通过new创建的string一定会在堆中创建一份数据,同时常量池肯定有一个值的备份操作;而单独的字符串则是直接指向常量池,所以一般还是使用字符串更好些,具体可看如下的图解

在这里插入图片描述

3、字符串 + 操作

String s1="helloworld"
String s2="hello" + "world"
String s3=new String("hello") + "world"

System.out.println(s1 == s2);  // true
System.out.println(s2 == s3);  // false
System.out.println(s1 == s3);  // false

代码中"hello" + "world"在编译期已经知道了数据情况(使用javac编译查看class文件会发现s1和s2是一致的),JVM会自动优化使得s1和s2是一样的,s3由于有new操作,所以需要StringBuilder的append完成,具体可看如下图解。
在这里插入图片描述

4、字符串变量 + 操作

String s1="hello"
String s2="world"
String s3="helloworld"
String s4=s1 + s2

System.out.println(s3 == s4);  // false

这里的样例和上一个样例存在一些差别,这里的s4是由s1 + s2获得的,在编译期无法感知到其实际值,在运行期时会利用StringBuilder的append剩下一个新的String对象,所以s3指向的是常量池,而s4指向的堆,两者自然是不一样的。

s4 = new StringBuilder()).append(s1).append(s2).toString()

5、带final的字符串变量 + 操作

final String s1="hello"
String s2="world"
String s3="helloworld"
String s4=s1 + "world"
String s5=s1 + s2

System.out.println(s3 == s4);  // true
System.out.println(s3 == s5); // false

添加了final关键字修饰的变量在编译期会被对应的字符串直接替换掉,相当于字符串数据,而包含了字符串变量的+操作则依旧是使用了StringBuilder

6、反射修改字符串

常规上,字符串都是不可变的(imutable),所以线程安全。但是,可以通过反射手段修改字符串!!!因为底层是char[],获取到之后可以强行修改。

@Test
public void test5() throws NoSuchFieldException, IllegalAccessException {
     String s = "hello";
     System.out.println("修改前 s = " + s);
	 //value是String内部字段:private final char value[];
     Field value = s.getClass().getDeclaredField("value");
     value.setAccessible(true);
     char[] s1 = (char[])value.get(s);

     s1[0] = 'A';
     System.out.println("修改后 s = " + s);
 }

总结
String 本身是final类型的类,在日常使用中需要频繁的做字符串合并操作时,尽可能的使用StringBuilder(如需要考虑线程安全则使用StringBuffer),降低无谓的字符串创建操作,在保证安全的情况下,提高效率!

发布了1 篇原创文章 · 获赞 0 · 访问量 12

猜你喜欢

转载自blog.csdn.net/CoderPeople/article/details/105034653