String类解析JDK1.8
字符串的创建方式
//1.方法一 字面量的方式
String str1 = "123456";
//2.方法二
String str2 = new String("zhw");
1.通过字面的方式创建的字符串存放在常量池中
2.通过new的方式创建的字符串存放在堆中
String的概述
特点
1.String类由final关键字修饰,不可以被继承,并且一旦创建不可更改
2.jdk8后String中的数据由Byte数组存储,并且不可以扩容
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
//用于存储数据
private final byte[] value;
//用于指定存放的数据的使用哪一种编码方式,一旦指定就不可变
private final byte coder;
......
}
为什么改为byte来存储数据
- 因为char在内存中需要占据2个字节,而在程序中存储的大部分是拉丁文,用不了2个字节,用Byte类型也足够用了。
- 那么怎么存储中文哪?中文需要2字节存储。在String类中新增了一个coder常量用来标记使用什么编码方式来存储数据。
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charset, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
String的不可变性
//测试String的不可变性
public class StringDemo3 {
public static void main(String[] args) {
//s1和s2同时指向字符串常量池的地址
String s1 = "abc";
String s2 = "abc";
//s3指向字符串常量池中的该值的地址
String s3 = "abcdef";
/**
* 先创建一个StringBuilder对象将s1的值和"def"用append方法加入最后用ToString返回
* StringBuilder ToString()方法返回的数据存放在堆中还是字符串常量池中?
* 结果存放到了堆中
*/
//s1指向堆中的地址
s1 = s1 + "def"; //abceefg
System.out.println(s1 == s3);//false
}
}
对应的字节码
public void test2() {
//s1和s2同时指向字符串常量池的地址
String s1 = "abc";
//在s1的基础上将形成一个新的值但是不改变原来的s1,且该值在堆内存中
String s2 = s1.replace('a', 'd');
String s3 = "dbc";
System.out.println(s2 == s3); //false
}
对应的字节码
@Test
public void test3(){
String str1 = new String("zzz");
char arr[] = {'b','c','d'};
System.out.println("没有change的地址");
System.out.println("str1的地址:"+str1.hashCode()); //str1的地址:121146
System.out.println("arr的地址:"+arr.hashCode()); //arr的地址:627185331
change(str1,arr);
System.out.println("=================");
System.out.println("change后的地址str的地址:"+str1.hashCode()); //change后的地址str的地址:121146
System.out.println("change后的地址arr的地址:"+arr.hashCode()); //change后的地址arr的地址:627185331
System.out.println(str1);
System.out.println(arr);
}
public void change (String str,char arr[]){
//可以看到是又有一个地址指向 zzz的地址
System.out.println(str); //zzz
str = "hello";
arr[0] = 'a';
System.out.println("=========================");
System.out.println("change方法里str的地址:"+str.hashCode()); //change方法里str的地址:99162322
System.out.println("change方法里arr的地址:"+arr.hashCode()); //change方法里arr的地址:627185331
}
String的基本特性
1.字符串常量池中是不会存储相同的字符串的
设置运行时的参数
String内存结构的分布位置
1.通过字面量的方式创建的字符串会保存在字符串常量池中,并且只保存一份,当有相同的数据被创建时,不会再添加
public class Memory {
public static void main(String[] args) {
Object o = new Object(); //存放到堆空间中
Memory memory = new Memory(); //存放到堆空间中
memory.foo(o); //开启一个新的栈帧
}
public void foo(Object param){
String s = param.toString(); //将param生成的数据存放到字符串常量池中
System.out.println(s);
}
}
字符串的拼接操作
/**
* 字符串的拼接操作
*/
public class StringDemo5 {
@Test
public void test1() {
//在编译期间就已经优化了
String s1 = "a" + "b";
String s2 = "ab";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
}
@Test
public void test2() {
//在编译期间就已经优化了
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b"; // 编译期间优化
String s5 = s1 + "b"; //使用StringBuilder进行操作
String s6 = "a" + s2; //使用StringBuilder进行操作
String s7 = s1 + s2; //使用StringBuilder进行操作
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //fasle
System.out.println(s5 == s6); //false
System.out.println(s5 == s7); //false
System.out.println(s6 == s7); //false
String s8 = s6.intern(); //会从字符串常量池中获取“ab”值的地址
//因为s3本身就在字符串常量池中,s8又是从字符串常量池中获取的
System.out.println(s3 == s8); //true
}
@Test
public void test3() {
//在编译期间就已经优化了
String s1 = "ab";
String s2 = "a";
String s3 = s2 + "b";
System.out.println(s1 == s3); //false
final String s4 = "a";
String s5 = s4 + "b";
System.out.println(s1 == s5); //true ? 因为s4由final关键字修饰此时s4为常量
}
}
StringBuilder的append方法和String的拼接的执行效率
package com.zhw.String;
import org.junit.Test;
/**
* append操作和字符串的拼接的执行效率
*/
public class StringDemo6 {
public static void main(String[] args) {
StringDemo6 stringDemo6 = new StringDemo6();
long begin = System.currentTimeMillis();
stringDemo6.join(100000);//用时2375
long time1 = System.currentTimeMillis() - begin;
System.out.println(time1);
long begin1 = System.currentTimeMillis();
stringDemo6.append(100000); //用时8
long time2 = System.currentTimeMillis() - begin1;
System.out.println(time2);
System.out.println("相差"+time1/time2+"倍"); //相差214倍
}
public void append(int times){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < times; i++) {
stringBuilder.append("a");
}
// System.out.println(stringBuilder.toString());
}
public void join(int times){
String s = "";
for (int i = 0; i < times; i++) {
s += "a"; //每次都会创建StringBuilder和String对象
}
// System.out.println(s);
}
}
intern()方法的理解
When the intern method is invoked, if the pool already contains a
string equal to this {@code String} object as determined by
the {@link #equals(Object)} method, then the string from the pool is
returned. Otherwise, this {@code String} object is added to the
pool and a reference to this {@code String} object is returned.
It follows that for any two strings {@code s} and {@code t},
{@code s.intern() == t.intern()} is {@code true}
if and only if {@code s.equals(t)} is {@code true}.
if and only if ====> <==>等价于
底层会调用equals()方法进行对比,如果池中已经有该对象,则返回池中的字符串的引用。 否则,这个对象将被添加到池中,并返回这个对象的引用。
package com.zhw.String;
/**
* append操作和字符串的拼接的执行效率
*/
public class StringDemo7 {
public static void main(String[] args) {
//会创建至少一个对象,如果ab在字符串常量值中已经存在,就不会再在常量池中创建
//但是一定会在堆中创建一个值为ab的String对象
String s1 = new String("ab");
String s2 = s1;
System.out.println(s2 == s1); //true
String s3 = new String("zhw").intern();
String s4 = "zhw";
System.out.println(s3 == s4); //true
String s5 = new String("zzz");
String s6 = "zzz";
System.out.println(s5 == s6); //false
}
}
new String()到底创建了几个对象?
/**
* new String到底创建了几个对象
*/
public class StringDemo8 {
public static void main(String[] args) {
String s1 = new String("ab");
//看字节码知道是2个
//一个对象是:new 关键字在堆空间中创建的
//另一个是:字符串常量池中的
/**
* 1.StringBuffer对象
* 2.new String对象
* 3.a在字符串常量池中的对象
* 4.new String对象
* 5.b在字符串常量池中的对象
* 6.StringBuilder的toString方法也会创建一个新的对象,但是不会将添加到字符串常量池中
*/
String s2 = new String("a") + new String("b"); // 等同于 new String("ab"),但是字符串常量池中并没有该数据
s2.intern();//将ab添加到字符串常量池中
String s3 = "ab";
System.out.println(s2 == s3); //JDK14:false
}
}
intern的练习题
/**
* intern练习题
*/
public class StringDemo9 {
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); //false
String s3 = new String("1")+new String("1");
//执行完上一行代码之后,字符串常量池中是否存在“11”呢?答案:不存在
s3.intern(); //如何理解:jdk6会实打实的创建一个新的对象"11",在字符串常量池中
//jdk7不再常量池中创建"11"对象,而是指向堆空间中的"11"对象的地址
String s4 = "11"; //s4指向的是堆空间中"11"的地址
System.out.println(s3 == s4); //true
}
}
intern的总结:
jdk1.7起,如果堆中有字符对象,常量池中没有,使用intern方法,会在常量池中保存堆空间中该对象的引用地址
要点:
StringBuilder的toString方法并不会把该字符加入到字符串常量池中
/**
* intern练习题jdk1.8
*/
public class StringIntern2 {
public static void main(String[] args) {
String s3 = new String("1")+new String("1");
//执行完上一行代码之后,字符串常量池中是否存在“11”呢?答案:不存在
String s4 = s3.intern();//在常量池中复制一份堆中的“11”的地址
System.out.println(s4 == "11"); //true 此时常量11也指向堆中的地址
System.out.println(s3 == s4); //true
}
}
intern的空间使用效率
package com.zhw.String;
/**
* intern的空间使用效率
*/
public class StringIntern3 {
static final int MAX_COUNT = 1000 * 10000;
static final String[] arrs = new String[MAX_COUNT];
public static void main(String[] args) {
Integer[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
long start = System.currentTimeMillis();
for (int i = 0; i < MAX_COUNT; i++) {
// arrs[i] = new String(String.valueOf(data[i % data.length]));
arrs[i] = new String(String.valueOf(data[i % data.length])).intern();
}
long end = System.currentTimeMillis();
System.out.println("总共耗时:"+ (end - start));
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结论:当需要用到大量的字符串对象时,使用intern方法可以大大节约内存空间的使用