专业编程基础技术教程

网站首页 > 基础教程 正文

神奇的字符串常量池 字符串常量池在新生代还是老年代

ccvgpt 2024-11-08 10:59:29 基础教程 7 ℃

前言:

我们在初学编程的时候,经历了数据类型部分知识的掌握,知道了数据类型宏观分成值类型与引用类型两个部分。

而引用类型中的String字符串,以特立独行的定义、调用风格,仿佛如同一朵奇葩一般,存在于自己身处的阵营之中。

神奇的字符串常量池 字符串常量池在新生代还是老年代

甚至让很多初学者误以为String字符串属于值类型的一种,也有很多人吐槽String类型是最像值类型的引用类型。


为什么要设计字符串常量池?

字符串常量池的设计方式,普遍存在于以面向对象为编程思想的语言体系中,例如:Java、C#等等。因为面向对象编程的核心是类的设计,而类需要大量的成员字段进行修饰、描述,然后再通过类去创建出一个又一个的实例对象。

而在对类描述的过程中,我们发现所有的数据类型中,String类型的占比最高,属于需要高频使用的一种数据类型。例如:定义人类,其中名字、电话、住址、邮箱、性别、兴趣爱好等等基本全是String类型的。

如果按照传统引用类型的实例化创建方式,面对一个项目成百、上千、甚至上万的类来说,无疑会给系统的性能以及内存资源的消耗,带来灾难性的影响,这是我们绝对不愿意看到的情况。


所以需要对String类型进行池化设计,对String类型进行一个封装,增加一些对该类型的描述字段,例如:调用次数、操作时间、生命周期的定义等等,然后通过后台线程对线程池进行管理。

String常量池与我们经常用到的数据库连接池,设计的原则都是一致的,就是为了节省资源、提高性能、防止重复创建、节约内存空间。

你需要知道的关于字符串常量池相关的知识:

常量的最大特点就是不能被改变,既然不会被改变,那么在内存中只需要存在一份即可。池中的每一个不同的字符串都是唯一存在的个体。

但需要注意的是,这种池化的操作虽好,但并不是完全全自动的状态,毕竟String属于引用类型,通过New关键字进行实例化操作属于天性,再提供了便利化的使用方式的同时,对这种天性也做到了完全保留。

无论在java中,还是C#语言,都为开发人员提供了,通过New关键字进行手工实例化字符串的编程语法。

我们平时在编写程序的时候,关于字符串的定义,需要有如下的注意事项:

1:在定义字符串变量的时候,双引号运算符可以将所修饰的字符串,添加到字符串常量池之中。

例如:String name = "admin",那么程序在编译期间,会判断常量池中是否存在字符串admin,如果存在则直接返回引用,如果不存在,则添加到常量池之中,再返回给句柄引用。

2:如果我们通过New关键字去创建字符串对象,那么在程序运行期,字符串会被创建并存储在堆内存中。

我们也可以通过调用String的Intern方法,将手工实例化出来的字符串,添加到字符串常量池中。

3:对于字符串的拼接,通常采用连接符+号,如果是两个常量,使用连接符进行拼接,那么结果也是常量,在程序的编译器就可以确定。

如果是拼接的两端有变量进行参与,那么结果是在运行期创建的,拼接的结果存储在堆内存中(稍后通过案例分析,把这种现象进行解释)。

案例分析:

Demo1:

Java代码:

String a = "a";
String content1 = "a"+"b";
String content2 = "ab";
String content3 = a+"b";
System.out.println("content1==content2:"+(content1==content2)); 
System.out.println("content2==content3:"+(content2==content3)); 

C#代码:

string a = "a";
string content1 = "a" + "b";
string content2 = "ab";
string content3 = a + "b";
Console.WriteLine(object.ReferenceEquals(content1,content2));
Console.WriteLine(object.ReferenceEquals(content2, content3));

两段代码的运行结果完全一样,为true、false。“a”+"b"在程序编译阶段,通过反编译工具可以清楚地看到编译后的结果为“ab”,这是一种优化的行为,所以content1与content2,引用的内容都是常量池中的“ab”。

而content3由于有变量的参与,实际相当于通过new关键字创建了一个新的字符串对象,内容为“ab”,虽然内容与content1、content2相同,但内存地址截然不同!


Demo2:

Java代码:

String content1 = "abc";
String content2 = new String("abc");
System.out.println(content1.equals(content2));
System.out.println(content1==content2);

C#代码:

string content1 = "abc";
string content2 = new string(new char[] {'a','b','c'});
Console.WriteLine(content1==content2);
Console.WriteLine(object.ReferenceEquals(content1,content2));

连段程序代码运行的结果:true、false,程序在编译阶段”abc“被载入到字符串常量池中,通过new实例化出来的字符串,内容虽然与content1相同,但内存地址则不相同。


Demo3:

这个案例比较有意思,以字符串作为参数进行传递,会有不一样的效果。

public static void main(String[] args) {

String content = "abc";
Person p = new Person();
p.name = "admin";
p.age = 18;
changeStr(content);
changePersonObj(p);
System.out.println(content);
System.out.println(p.name+","+p.age);
  
}

public static void changeStr(String content){
		content = "Hello World";
}

public static void changePersonObj(Person p){
		p.name = "我叫"+p.name;
		p.age++;
}

程序运行结果为:abc、我叫admin、19,这时候肯定有小伙伴感觉到奇怪,为什么引用类型Person作为参数传递,结果发生了改变。而字符串则没有呢?其实这还是字符串常量池的概念。

程序在编译阶段,首先将abc、Hello World加载到常量池中,主函数中的String content引用的是常量池中的abc,作为参数传递的时候,changeStr方法中的形参String content也是引用常量池中的abc,在方法内部将引用换成了Hello World而已,并没有改变主函数中content地址的引用。


Demo4:

经典的面试题目:String s = new String("abc") ;这条语句创建了几个对象?

题目解析:

一共创建了两个对象,通过冒号运算符将abc添加到字符串常量池中,再通过new关键字是创建一个内容为abc的字符串对象。


Demo5:

String s1 = new String("s1") ;String s2 = new String("s1") ;一共创建了几个对象?

题目解析:

一共创建了3个对象,具体原因参考Demo4即可。


Demo6:

java代码如下,目测一下执行结果是什么?

String a = new String("ab");
String b = new String("ab");
String c = "ab";
String d = "a" + "b";
String e = "b";
String f = "a" + e;

System.out.println(b.intern() == a);
System.out.println(b.intern() == b);
System.out.println(b.intern() == c);
System.out.println(b.intern() == d);
System.out.println(b.intern() == f);
System.out.println(b.intern() == a.intern());

结果为:false、false、true、true、false、true。


Demo7:

再补充一段C#中的常量池代码,目测下执行结果是什么?

string s = new string(new char[] { 'x', 'y', 'z' });
Console.WriteLine(String.IsInterned(s) ?? "not interned");
String.Intern(s);
Console.WriteLine(String.IsInterned(s) ?? "not interned");
 Console.WriteLine(object.ReferenceEquals(String.IsInterned(new string(new char[] { 'x', 'y', 'z' })), s));
Console.WriteLine(object.ReferenceEquals("xyz", s));

结果是:xyz、xyz、false、false。


总结一下:

以上就是整理的关于字符串常量池的一些知识点,以及经典的面试题案例分享。写到这里基本处于头昏眼花的状态了,觉觉去喽!

喜欢的小伙伴可以关注我,一起交流学习!我是IT鸟叔,一位喜欢写程序、钓鱼、喝茶、玩游戏的中年大叔!

Tags:

最近发表
标签列表