大家好,又见面了,我是你们的朋友全栈君。
方法区
中)。
这个对象的地址返回
。创建一个新的字符串对象并放入常量池
,并返回新创建的字符串的引用地址。length()
, charAt(int index)
, subSequence(int start, int end)
这几个API接口。StringBuffer和StringBuilder
也实现了CharSequence接口。可读可写字符串
,而String的值是只读字符串
。String在java编程中广泛应用,首先从源码进行分析
String底层是一个final类型的字符数组
,所以String的值是不可变的
,每次对String的操作都会生成新的String对象
,造成内存浪费 而StringBuffer和StringBuilder
就不一样了,他们两都继承了AbstractStringBuilder抽象类
,从AbstractStringBuilder抽象类中我们可以看到
他们的底层都是可变的字符数组
,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。
接着看一下他们的继承结构以及部分源码实现
StringBuffer.apped()方法是线程安全的
StringBuilder.apped()方法线程不安全
从这三张图我们不难得知:
多数情况下建议使用 StringBuilder 类
。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
null 表示string还没有new
,也就是说对象的引用还没有创建
,也没有分配内存空间给他
,java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串常量池(字符串缓冲池)
。那个java的字符串缓冲池是如何工作的呢?
String a = "abc";
String b = "abc";
String c = new String("xyz");
String a = "abc";
字符串常量池
中有没有相同的对象
,如果有
相同的对象就直接返回该对象的引用
,如果没有
相同的对象就在字符串常量池中创建该对象
,然后将该对象的引用返回
。对于这一步而言,缓冲池中没有abc这个字符串对象,所以首先创建一个字符串对象,然后将对象引用返回给a。String b = "abc";
对象引用变量b
使其指向abc
这一对象。这时,首先查找字符串常量池
,发现abc这个对象已经有了
,这是就直接将这个对象的引用返回给b
,此时a和b就共用
了一个对象abc,不过不用担心,a改变了字符串不会影响b,因为字符串都是常量
,一旦创建就没办法修改了,除非创建一个新的对象。String c = new String("xyz");
String c = new String(“xyz”); JVM首先是在字符串常量池中找”xyz” 字符串,如果没有创建字符串常量,然后放到常量池中,若已存在,则不需要创建;当遇到 new 时,还会在内存(不是字符串常量池中)上创建一个新的String对象,存储”Hello”,并将内存上的String对象引用地址返回。
从上边的分析可以看出,当new一个字符串时并不一定是创建了一个新的对象
,有可能是与别的引用变量共同使用了同一个对象
。下面看几个常见的有关字符串常量池的问题。
String a = "abc";
String b = "abc";
String c = new String("xyz");
String d = new String("xyz");
String e = "ab" + "cd";
这个程序与上边的程序比较相似,我们分比来看一下:
4、String d = new String(“xyz”);常量池中已有该字符串对象,则常量池中不再创建该对象,然后会在new的时候内存中创建一个新的字符串对象,所以只创建了一个对象;
5、String e = ”ab” + ”cd”;由于常量的值在编译的时候就被确定了。所以这一句等价于String e = ”abcd”;所以创建了一个对象;
所以创建的对象的个数分别是:1,0,2,1,1
。
了解了String类的工作原理,回归问题本身。 在String的工作原理中,已经提到了,new 一个String对象,是需要先在字符串常量中查找相同值或创建一个字符串常量,然后再在内存中创建一个String对象,所以 String str = new String(“xyz”); 会创建两个对象。
我们知道两个字符串对象相等的判断要用equal
而不能使用==
,但是学习了字符串常量池以后,应该知道为什么不能用==
, 什么情况下==
和equal
是等价
的>
首先,必须知道的是
值
是否相等内存地址
是否相等实例一:
public static void main(String[] args) {
String s1 = "money";
String s2 = "money";
if (s1 == s2) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
}
执行结果: s1 == s2
分析: 通过对字符串常量池的了解,我们知道s1和s2都是指向字符串常量池中的同一个对象
,所以内存地址是一样的
,所以用==
可以判断两个字符串是否相等。
实例二:
public static void main(String[] args) {
String s1 = "money";
String s2 = new String("money");
if (s1 == s2) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
执行结果:
s1 != s2
s1 equals s2
分析: String s2 = new String(“money”);这一句话没有
在字符串常量池
中创建新的对象,但是会在内存的其他位置创建一个新的对象
,所以s1是指向字符串常量池的
,s2是指向内存的其他位置
,两者的内存地址不同的
。
实例三:
public static void main(String[] args) {
String s1 = "money";
String s2 = new String("money");
s2 = s2.intern();
if (s1 == s2) {
System.out.println("s1 == s2");
}else {
System.out.println("s1 != s2");
}
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
}else {
System.out.println("s1 not equals s2");
}
}
输出结果:
s1 == s2
s1 equals s2
分析: 先来说说intern()
这个方法的作用吧,这个方法的作用是返回在字符串常量池中的对象的引用
,所以s2指向的也是字符串常量池中的地址
,和s1是相等的。
intern()方法:返回在字符串常量池中的对象的引用
实例四:
public static void main(String[] args) {
String Monday = "Monday";
String Mon = "Mon";
String day = "day";
System.out.println(Monday == "Mon" + "day");
System.out.println(Monday == "Mon" + day);
}
输出结果:
true
false
分析: 第一个为什么等于true我们已经说过了,因为两者都是常量所以在编译阶段就已经能确定了
,在第二个中,day是一个变量,所以不能提前确定他的值,所以两者不相等,从这个例子我们可以看出,只有+连接的两边都是字符串常量时,引用才会指向字符串常量池
,否则都是指向内存中的其他地址。
实例五:
public static void main(String[] args) {
String Monday = "Monday";
String Mon = "Mon";
final String day = "day";
System.out.println(Monday == "Mon" + "day");
System.out.println(Monday == "Mon" + day);
}
输出结果:
true
true
分析: 加上final后day也变成了常量,所以第二句的引用也是指向的字符串常量池。
String类是一个不可变对象,其它有两层意思:
不可变的
过构造方法或字面常量
创建字符串
可能在堆内存,也可能在字符串常量池(和创建方法以及JDK的版本有关)
。使用构造方法构建的字符串对象一定在堆内存,如果堆该字符串对象调用String.intern()方法,则可以将该字符串移入字符串常量池。
Hashtable
返回一个新的字符串
“+”运算符重载
,底层是依靠StringBuilder实现的;String.contact()方法
,底层是依赖Array.copy实现的;ringBuilder
,通过预先分配一个字符缓冲区来进行字符串的连接,适合大批量字符串连接的情况
JDK1.5提供的
,目的是补充StringBuffer用在单线程环境下——不必要且性能低的不足。
底层数据结构都是char[]数组
,不同的是String将该char数组设置成了不可变的(final)
将此String对象添加到池中,并且返回此池中对象的引用
。
在JDK6中,不推荐大量使用intern方法
,因为这个版本字符串缓存在永久代
中,这个空间是有限了,除了FullGC之外不会被清楚,所以大量的缓存在这容易OutOfMemoryError。字符串在JDK1.6—JDK1.8的区别
JDK1.6及以前
,常量池在方法区,
这时的方法区也叫做永久代
,其中存放的是字符串的实例(字符串存在永久代中,容易出现性能问题和内存溢出。)JDK1.7(含)方法区合并到了堆内存中
,这时的常量池也可以说是在堆内存中,存储的是字符串对象的引用,字符串实例是在堆中
1.6之后的版本把字符串放入了堆中,避免了永久代被挤满。
JDK1.8 已移除永久代
,方法区又从堆内存中剥离出来了
,字符串常量池是在本地内存当中,存储的也只是引用
。但实现方式与之前的永久代不同,这时的方法区被叫做元空间
,常量池就存储在元空间
。版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/193656.html原文链接:https://javaforall.cn