一. 概述(java.lang.String类)
- public final class String,String类被声明为final,因此不可变、不可被继承
- Java 8中,String内部使用char[]数组存储数据
public final char[] value;
- Java 9之后,使用byte[]数组存储数据,并用coder标识编码方式
public final byte[] value;
public final coder;
问题1:String为什么定义为final,有什么好处?
1、如果一个String对象已经被创建过了,那么就会从字符串常量池(String Pool在heap内)中取得引用
2、当String用做HashMap的key,不可变特性可以使hash值也不可变,因此只需要计算一次
3、String不可变性天生具备线程安全,可以在多个线程中安全地使用。
二. String实例化
方法一:字面量方式赋值
String s = "abc";
/*JVM首先会去字符串池中查找是否存在"abc"这个对象
1、不存在,则在字符串池中创建"abc"这个对象,然后将引用地址返回给字符串常量s,s指向"abc"
2、存在,则不创建任何对象,直接将池中"abc"这个对象的地址返回,赋给字符串常量s
方法二:new构造器
String s = new String("abc");
/*JVM首先在字符串池中查找有没有"abc"这个字符串对象
1、不存在,则先在字符串池中创建"abc"字符串对象,再在堆中创建一个"abc"字符串对象,
然后将堆中的"abc"对象地址返回给s引用,s指向堆中的"abc"
2、存在,则不在字符串池中再去创建"abc"这个对象,直接在堆中创建一个"abc"字符串对象,
然后将堆中的"abc"对象地址返回给s引用,s指向堆中的"abc"
问题2:判断下列代码
String s1 = "abcd";
String s2 = "abcd";
System.out.println(s1 == s2); //true
String s3 = new String("abcd");
String s4 = new String("abcd");
System.out.println(s3 == s4); //false
String s5 = s4.intern();
String s6 = s4.intern();
System.out.println(s5 == s6); //true
/*字符串调用intern()方法
如果字符串池中已经存在一个字符串和该字符串值相等(equals()方法),则返回字符串池中对象的引用
否则,就会在字符串池中添加一个新的字符串,并返回这个新字符串对象的引用
三. String与其他类型转换
1. String与基本类型、包装类转换
String -> 基本类型、包装类:调用包装类静态方法parseXxx()
String s = "123";
Int i = Integer.parseInt(s);
基本类型、包装类 -> String:调用String的valueOf()
int i = "123";
String s = String.valueOf(i);
2. String与字符数组转换
String -> char[]:调用String的toCharArray()
String s = "abc";
char[] c = String.toCharArray(s);
char[] -> String:调用String构造器
char[] c = new char[]{'a', 'b', 'c'};
String s = new String(c);
3. String与字节数组转换
String -> byte[]:编码,调用String的getBytes()
String s = "abc123一二三";
byte[] b1 = s.getBytes(); //使用默认方式编码
byte[] b2 = s.getBytes("gbk"); //使用gbk编码
byte[] -> String:解码,调用String构造器
String s1 = new String(b1); //使用默认方式解码
String s2 = new String(b2, "gbk"); //使用gbk解码
四. StringBuffer和StringBuilder
1. 可变性
- String不可变
- StringBuffer和StringBuilder可变
2. 线程安全性
- String线程安全,效率最低
- StringBuffer线程安全,内部使用synchronized同步,但效率较低
- StringBuilder线程不安全,未加线程锁,但效率较高
- 运行速度:StringBuilder > StringBuffer > String
/*以StringBuffer为例,StringBuilder同理*/
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建长16字符数组
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length()+16];
3. 结论:
少量操作字符串使用:String
多线程大量操作字符串使用 :StringBuffer(int capacity)
单线程大量操作字符串使用:StringBuilder(int capacity)
常用方法:
- 增:.append(xxx)
- 删:.delete(int start,int end)
- 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
- 查:charAt(int n)
- 插:insert(int offset, xxx)
- 长度:length()
- 遍历:for() + charAt() / toString()
五. 字符串拼接
- 字符串间或字符串常量间拼接,相当于拼接后的字符串,直接存入字符串常量池
String s = "a" + "b" + "c"; //等同于 String s = "abc",也就是说在字符串常量池存入了"abc"
final String s1 = "a";
final String s2 = "b";
String s3 = s1 + s2; //等同于 String s3 = "ab"
- 拼接元素如果存在字符串的引用(变量),结果则存入堆中新创建的对象中
String s = "Hello";
String s1 = "Hello" + "World"; //结果在字符串常量池,s1指向常量池中的"HelloWorld"
String s2 = s + "World"; //结果在堆中的对象里,s2指向对象里的"HelloWorld"
System.out.println(s1 == s2); //false,地址不同
String s2 = s + “World”; 内部过程如下
- 创建StringBuilder,从局部变量表加载变量s,调用append方法
- 根据s变量地址找到常量池里的"Hello"进行append,再对"World",append
- appendStringBuilder调用toString方法,类似于new出新的字符串(和new的区别在于,如果常量池不存在拼接完成的字符串,也不会自动在常量池里创建,但是new会)