String的intern()方法
intern方法用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后返回引用。
intern()判断这个常量是否存在于常量池。
(1)如果存在
1.1判断存在内容是引用还是常量,
如果是引用,
返回引用地址指向堆空间对象,
如果是常量,
直接返回常量池常量
(2)如果不存在
2.1将当前对象引用复制到常量池,并且返回的是当前对象的引用
String a1 = "AA";
System.out.println(a1 == a1.intern()); //true
String a2 = new String("B") + new String("B"); //a2 堆中
a2.intern();// 把堆中a2的地址放入常量池
String a3 = new String("B") + new String("B"); //a3 堆中
System.out.println(a2 == a3.intern());//true
System.out.println(a3 == a3.intern());//false
String a4 = new String("C") + new String("C");// a4堆中
System.out.println(a4 == a4.intern()); //true
intern()方法总结
1.只在常量池上创建常量
String a1 = "AA";
2.只在堆上创建对象
String a2 = new String("A") + new String("A");
3.在堆上创建对象,在常量池上创建常量
String a3 = new String("AA");
4.在堆上创建对象,在常量池上创建引用
String a4 = new String("A") + new String("A");//只在堆上创建对象AA
a4.intern();//将该对象AA的引用保存到常量池上
5.在堆上创建对象,在常量池上创建引用,不会在常量池中再创建常量。
String a5 = new String("A") + new String("A");//只在堆上创建对象
a5.intern();//在常量池上创建引用
String a6 = "AA";//此时不会再在常量池上创建常量AA,而是将a5的引用返回给a6
System.out.println(a5 == a6); //true
6.有某些特殊的字符串默认加载入常量池,如java
两个例题及其内存分析图
String
String对象一经创建则不可改变。
对象不可改变,但这并不表示引用不可以改变
对象不可改变,但这并不表示引用不可以改变
对象不可改变,但这并不表示引用不可以改变
可以更加深入理解String的代码
String s1="zhangsan";//存储常量池中
String s2="zhangsan";
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);//true
System.out.println("-----------------------");
String s3=new String("zhangsan");
String s4=new String("zhangsan");
System.out.println(s3==s4); //false
System.out.println("----------修改value[]----------");
//获取String的类对象
Class<? extends String> class1 = s1.getClass();
//获取String value属性
Field field=class1.getDeclaredField("value");
//设置访问权限
field.setAccessible(true);
//获取value的值
char[] v= (char[]) field.get(s1);
System.out.println("v的长度"+v.length);
v[0]='x';
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
最后输出的s2,s3,s4都已经改变了,说明三个引用都是指向了同一个String。这是因为常量池里面已经有了zhangsan这个字符串,所以不会再创建新的字符串。内存图如下:
这里的String s3 = new String(“zhangsan”); 实际上只是创建了一个引用,这个引用是指向常量池里面的String。
在String变量(进行相加的时候,实际上是创建了一个StringBuilder的对象来进行字符串的连接最后返回一个字符串,并不是原来的字符串进行连接,字符串是不可变的。源代码与反编译代码如下:
String s = "hey";
String s1 = "damon";
String s3 = s + s1;
String s = "hey";
String s1 = "damon";
String s3 = (new StringBuilder()).append(s).append(s1).toString();
也只是String变量相加的时候,如果是两个字面量相加,那么会直接加载入方法区,并且仅在内存中生成一个对象。如String str = “hello” + “damon”)
一个关于String对象创建的例题:
```java
String str = "";
for(int i = 0; i < 5000; i++){
str = str + i;
}
这道题相当于产生了10k个对象,因为字符串是不可变的,并且在进行字符串的拼接时,i也被创建为一个对象。这种做法非常消耗时间和空间。
StringBuider和StringBuffer
- 它们与String的核心区别:
String:private final char value[];
StringBuider和StringBuffer的父类AbstractStringBuilder:char[] value;
因为String是不可变的,所以对String进行修改过后它的hash值会更改,但是更改过后StringBuilder和StringBuffer却不会发生改变,说明StringBuilder和StringBuffer是可更改的字符串。 - StringBuilder是线程不安全的,效率高,一般来说是用他
- StringBuffer线程安全,效率低。
StringBuider和StringBuffer的常用方法:
常用的成员方法
2.2.1 追加:append()
/*
StringBuffer append(String str)
StringBuffer insert(int offset, String str)
*/
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
sb1.append("hello");
sb2.append("hello");
System.out.println(sb1 == sb2);//输出结果是false,因为每一个StringBuffer都是一个独立的字符数组
//方法链
sb1.append("java").append("java").append("java").append("java");
//插入
sb1.insert(2,"hhhhhhhh");
System.out.println(sb1);//从0开始计数
2.2.2 删除
/*
StringBuffer delete(int start, int end)
StringBuffer deleteCharAt(int index)
*/
//删除指定区间的字符串
sb1.delete(2,3);//同样是含头不含尾
//删除指定位置上的字符
sb1.deleteCharAt(0);
System.out.println(sb1);
2.2.3 替换
/*
StringBuffer replace(int start, int end, String str)
void setCharAt(int index, char ch)
2.2.4 获取
//和String类中的用法相同
/*
indexOf
lastIndexOf
charAt
length
substring
*/
2.2.5 反转
// StringBuffer reverse()
StringBuffer sb3 = new StringBuffer("my name is zhansan");
sb3.reverse();
System.out.println(sb3);