字符串常量池

声明: 本文主要作为作者的复习笔记,由于作者水平有限,难免有错误和不准确之处,欢迎读者批评指正.

字符串对象的产生方式

public static void main(String[] args) {
//直接赋值
String s1 = "hello"; //第一次出现,产生该字符串对象并置入常量池中
String s2 = "hello"; //后续出现的"hello",会使用直接赋值法,直接从常量池中返回该对象,不再产生新的字符串对象
//通过构造方法产生,每当调用一次new,都会在堆上开辟新空间,返回新创建的对象
String s3 = new String(original: "hello"); //这里new出来的字符串对象,只是复制了常量池中字符串对象的内容,在堆上产生了新的空间,该对象的名字为s3;
String s4 = new String(original: "hello"); //这里new出来的字符串对象,只是复制了常量池中字符串对象的内容,在堆上产生了新的空间,该对象的名字为s4;

//输出 true
System.out.println(s1 == s2); //s1和s2指向了相同的地址空间
//输出 false
System.out.println(s3 == s4); //s3和s4,s1都指向不同的地址空间
//输出 false
System.out.println(s1 == s3);} //s3和s4,s1都指向不同的地址空间

这部分代码一共产生了3个字符串对象,其中一个在常量池中,另外两个在堆上; 产生了4个引用,其中s1和s2执行的是常量池的对象,s3指向对象2,s4指向对象3;
字符串常量池

  • s1是指向字符串对象的引用,value是指向字符数组的引用;
  • 字符串中保存了该数组的引用,不是实体;
  • 字符串对象不可变,无法修改这个value指向的内容;
  • String str => 引用,引用就保存一块地址;
  • 对象是实实在在存在的实体,引用是保存的地址;
  • 字符串常量池保存的都是字符串对象;

字符串常量池

Java使用""称为字符串常量,为了提高程序的运行速度,节省空间,JVM会维护一个字符串常量池; 当字符串常量第一次出现,则产生新对象并将该对象置入常量池中; 后续若再次出现该字符串常量,不会产生新对象,直接复用常量池中的已有对象,直接赋值法默认会从常量池中取对象;

例:
博客的内容相当于字符数组的内容,value数组引用保存了这块内容的地址,字符串对象相当于博客的链接,CSDN相当于字符串的常量池,保存了一系列的字符串对象(即链接);

入池方法: intern方法

将手动创建的字符串对象置入常量池,并返回置入常量池之后的地址;

char[] ch = {'a','b','c'};
//通过new的方式产生的字符串仍然在堆中存储,并不会置入常量池
String s1 = new String(ch);
//置入常量池
String s2 = "abc"; //"abc"这个字符串常量此时第一次出现
//输出false
System.out.println(s1 == s2);

尝试将当前字符串对象置入常量池;
若常量池中不存在该对象内部保存的内容,则将当前对象置入常量池;

char[] ch = {'a','b','c'};
//通过new的方式产生的字符串仍然在堆中存储,并不会置入常量池
String s1 = new String(ch);
//手工入池,将s1产生的字符串对象,置入常量池
s1.intern();
String s2 = "abc"; //此时"abc"已经在常量池中存在了,不会产生新对象,直接复用常量池中的已有对象
//输出true
System.out.println(s1 == s2);  //这里s2直接返回常量池中的已有对象的地址,和s1的地址相同

尝试将当前字符串对象置入常量池;
若常量池中已经存在该对象内部保存的内容,则该方法直接返回常量池中的字符串对象地址;

String s2 = "abc";  //字符串常量第一次出现,则产生新对象并将该对象置入常量池中
char[] ch = {'a','b','c'};
String s1 = new String(ch);
s1.intern(); //此时常量池中已经有了"abc",所以不会将s1指向的对象入池,而是返回常量池中的字符串对象地址
//输出true
System.out.println(s1 == s2);  

注意: intern方法会在调用后返回常量池中的字符串对象地址(无论是否入池成功),调用之后接收即可;

s1 = s1.intern();

String不可变

字符串对象一旦产生,这个对象内部包裹的字符串内容就不可再修改;

String str = "hello";
str += "world";
str += "!!!";
System.out.println(str);

这里字符串对象内容不可变,一直在变的是str的引用指向在变化; 当str发生拼接时,产生了新的字符串对象,str指向新的对象,对原本的str字符串内容不产生影响(值传递本质);

字符串不可变的本质原因

JDK中String类的源码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
	private final char value[];
}

value数组是具体保存字符串对象内容的引用,所谓的内容不可变,实际上就是value指向的字符数组内容无法修改; 在String中是私有权限,出了这个类,对外完全隐藏,且在String类中没有提供任何访问或修改value数组的方法; 因此String对象一旦产生,内容就彻底没法修改;

假如String可以多态的话,表现出来的行为就不可控了; 为了保证JDK String类的使用者表现出来的行为完全相同,直接让String类被final修饰,则String没有子类; 相当于JDK关闭了String的拓展,保证所有使用JDK String类的使用者拿到的都是相同的类和方法; 所以,对于String类来说,没有多态;

总结: 字符串设置为不可变的原因

  1. 方便实现字符串常量池,若String对象可变,常量池中的内容就会随时变化,常量池的实现就会非常麻烦("写时拷贝"可以但开销大效率低);
  2. 不可变的对象永远是线程安全的,不用考虑线程安全问题,效率很高;
  3. 不可变对象可以作为哈希表的key值,高效保存在哈希表中;
    由于字符串是不可变的,因此所有字符串修改的方法其实本质都是产生了新的字符串,不是在原字符串上进行的修改!!!

JDK提供的专门用来处理字符串内容修改的两个类(StringBuffer、StringBuilder),适用于某些需要频繁修改字符串的内容的场景下

  • 这两个类所有方法名称,具体使用都一模一样;
  • StringBuffer线程安全,效率较低;
  • StringBuilder线程不安全,效率较高; 不考虑线程安全问题时,优先使用StringBuilder类;
  • StringBuilder和String是两个独立的类,字符串的常量池保存的都是String对象;
  • StringBuilder对象内部是可以修改的;

常用的方法

方法说明
StringBuffer append(String str)在尾部追加,相当于String的+=,可以追加: boolean,char,char[],double,float,int,long,Object,String,StringBuffer的变量; (当前对象中进行的拼接操作,不会产生新对象)
StringBuffer insert(int offset, String str)在offset位置插入: 八种基本类型 & String类型 & Object类型数据 (在当前StringBuffer对象中新增内容)
StringBuffer deleteCharAt(int index)删除index位置字符 (在当前StringBuffer对象中删除内容)
StringBuffer delete(int start,int end)删除[start,end)区间内的字符
StringBuffer reverse()反转字符串 (将当前保存的内容反转处理)
String toString()将所有字符按照String的方式返回
  • String => StringBuilder
    通过StringBuilder的构造方法或者append方法;
  • StringBuilder => String
    toString();

String、StringBuilder、StringBuffer的区别

  1. String的内部不可修改,StringBuilder和StringBuffer是可变对象,可以修改其内容;
  2. StringBuffer采用synchronized方法处理,线程安全,效率较低; StringBuilder采用异步处理,线程不安全,效率较高; 一般不要求线程安全的场景下,推荐使用StringBuilder;
  3. String对象 “+=” 其实编译器会默认优化为StringBuilder的append方法;
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值