一、String
1、介绍
String是一个final类,不可被继承,代表不可变的字符序列。是一个类类型的变量,Java程序中的所有字符串字面量(如"abc")都作为此类的实例实现,"abc"是一个对象。
字符串是常量,创建之后不能更改。String 类复写 了Object类中的equals方法。
String对象的字符内容是存储在一个字符数组value[]中的。
JDK8部分源码:
说明:实现了Serializable接口,表示字符串是支持序列化的。实现了Comparable<String>接口,表示字符串可以比较大小。
内部定义了final char[] value用于储存字符串数据,final体现不可变性。
通过字面量(区别于new)给一个字符串赋值,此时的字符串声明在字符串常量池中。
字符串都在字符串常量池中,字符串常量池是不会存储相同内容的字符串。
总结:最后对String的考查都基于以上最后两点原理。
不难理解:
row1:首先常量池什么也没有,就创建了字符串"abc",然后将它的地址赋给了s1。
row2:常量池已经有了字符串"abc",所以直接将它的地址赋给了s2,此时s1==s2为true。
row3:常量池没有"hello",就创建了字符串"hello",然后将它的地址赋给了s1。
深刻理解常量池的概念及字符串的不可变性,不难得出:
1 String s1 = "abc"; // 字面量的定义方式 2 String s2 = "abc"; 3 System.out.println(s1 == s2); // true 4 5 String s3 = s1.replace('a', 'm'); 6 System.out.println(s1); // abc 7 System.out.println(s3); // mbc 8 9 System.out.println(s1 == s3); // false
2、考查点
代码示例:
1 String s1 = "javaEE"; 2 String s2 = "javaEE"; 3 String s3 = new String("javaEE"); 4 String s4 = new String("javaEE"); 5 6 System.out.println(s1 == s2); // true 7 System.out.println(s1 == s3); // false 8 System.out.println(s1 == s4); // false 9 System.out.println(s3 == s4); // false 10 11 Person p1 = new Person("Tom", 12); 12 Person p2 = new Person("Tom", 12); 13 System.out.println(p1.name == p2.name); // true 14 System.out.println(p1.name.equals(p2.name)); // true
面试题:String s = new String("abc")的方式,在内存中创建了几个对象?
答:两个。一个是堆空间中new结构,另一个是char value[]对应的常量池中的数据:"abc"。
有的答案是一个或两个。原因是如果在字符串常量池已经存在"abc"字符串,就不会再创建了,只会在堆内存创建一个对象。
1 String s1 = "java"; 2 String s2 = "mqsql"; 3 String s3 = "javamqsql"; 4 5 String s4 = "java" + "mqsql"; 6 7 String s5 = s1 + "mqsql"; 8 String s6 = "java" + s2; 9 String s7 = s1 + s2; 10 11 // 1.不同拼接的对比 12 System.out.println(s3 == s4); // true 13 System.out.println(s3 == s5); // false 14 System.out.println(s3 == s6); // false 15 System.out.println(s3 == s7); // false 16 17 System.out.println(s5 == s6); // false 18 System.out.println(s5 == s7); // false 19 20 System.out.println(s6 == s7); // false 21 22 // 2.intern().返回常量池里的地址 23 String intern = s5.intern(); 24 System.out.println(s3 == intern); // true 25 26 // 3.final.会使String成为一个常量 27 final String finalStr = "java"; 28 String s8 = finalStr + "mqsql"; 29 System.out.println(s3 == s8); // true
内存结构:
结论:
①常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
②只要有变量参与的连接,结果就在堆中。s5,存放的是堆空间的地址值。
③intern()是返回String对象在常量池中的引用。
代码示例:
1 public class Main { 2 String str = new String("good"); 3 char[] ch = {'t', 'e', 's', 't'}; 4 5 public static void main(String[] args) { 6 Test test = new Test(); 7 test.change(test.str, test.ch); 8 9 System.out.println(test.str); // good 10 System.out.println(test.ch); // best 11 } 12 13 public void change(String str, char[] ch) { 14 str = "test ok"; 15 ch[0] = 'b'; 16 } 17 }
3、字符串的内存结构
JDK 1.6:字符串常量池存储在方法区(永久区)
JDK 1.7:字符串常量池存储在堆空间
JDK 1.8:字符串常量池存储在方法区(元空间)
详细介绍见JVM。JVM中涉及字符串的内存结构。
4、常用方法
获取
int length():获取字符串长度
char charAt(int index):获取指定位置的字符
int indexOf(int ch):返回字符ch第一次出现的位置
int indexOf(String str):!=-1 可判断包含str
int indexOf(int ch, int fromIndex):从指定位置字符ch第一次出现的位置
int indexOf(String str, int fromIndex)
int lastIndexOf(int ch):反向索引,返回字符ch第一次出现的位置
int lastIndexOf(String str)
int lastIndexOf(int ch, int fromIndex)
int lastIndexOf(String str, int fromIndex)判断
boolean startWith(str):是否以指定字符串开头
boolean endsWith(str):是否以指定字符串结尾
boolean isEmpty():字符串长度是否为空("" true. null 报空指针异常)
boolean contains(str):是否包含str
boolean equals(str):复写了Object类中的equals方法
boolean equalsIgnoreCase(str)转换
byte[] getBytes():String---->byte[]
new String(byte[] bytes):byte[]---->String
char[] toCharArray():String---->char[]
new String(char[] arr):char[]---->String
new String(char[] arr, int offset, int count):char[]---->String
String valueOf():将基本数据类型转换成String
String toUpperCase()
String toLowerCase()替换
String replace(char oldChar, char newChar)
String replace(charSequence tar, charSequence rel)切割
String[] split(regex):按指定规则切割字符串
String substring(int beginIndex):从指定位置开始获取子串
String substring(int beginIndex, int endIndex):获取子串[begin,end)
二、StringBuffer
1、介绍
线程安全的可变字符序列,可将字符串缓冲区安全地用于多个线程。
StringBuffer可以对字符串内容进行增删,是字符串缓冲区,此时不会产生新的对象。是一个容器,返回的还是原缓冲区对象。很多方法与String相同。
字符串的组成原理就是通过该类实现的。
StringBuffer是可变长度的。
2、源码分析
1 String str1 = new String(); // final char[] value = new char[0]; 2 String str2 = new String("abc"); // final char[] value = new char[]{'a', 'b', 'c'}; 3 4 // char[] value = new char[16]; 底层创建了一个长度16的数组 5 StringBuilder sb1 = new StringBuilder(); 6 sb1.append('a'); // char[0] = 'a'; 7 sb1.append('b'); // char[1] = 'b'; 8 9 // char[] value = new char["abc".length() + 16]; 10 StringBuffer sb2 = new StringBuffer("abc");
总结:sb.length() == 3
扩容:从源码中,可以看到,默认空参数的构造器StringBuffer容器大小为16。默认情况下,扩容为原容量2倍 + 2,同时将原有数组中的元素copy到新数组中。
指导意义:数组copy效率低,在知道需要多少容量时,建议用StringBuffer(int capacity)或StringBuilder(int capacity)
3、常用方法
存储
StringBuffer append("abc"):将指定参数追加到末尾
StringBuffer insert(index, "qq"):将指定参数追加到index处删除
StringBuffer delete(int start, int end):删除缓冲区数据[start,end)
StringBuffer deleteCharAt(int index):删除缓冲区指定位置字符获取
char charAt(int index)
int length()
int indexOf(String str)
int lastIndexOf(String str)
String substring(int start, int end)
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):将缓冲区指定数据存储到指定字符数组中修改
StringBuffer replace(int start, int end, string str):把[start,end)替换为str
void setCharAt(int index, char ch):将缓冲区指定位置换成指定字符反转
StringBuffer reverse():返回的依然是原容器
三、StringBuilder
1、介绍
线程不安全的可变字符序列,将StringBuilder的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer。
JDK1.5以后,一个可变的字符序列,此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换。用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
每个字符串生成器都有一定的容量。只要字符串生成器所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区。如果内部缓冲区溢出,则此容量自动增大。
2、考查点
1 String s1 = "abc"; 2 StringBuffer s2 = new StringBuffer("abc"); 3 StringBuffer s3 = s2.reverse(); 4 5 System.out.println(s1); // abc 6 System.out.println(s2); // cba 7 System.out.println(s3); // cba 8 9 System.out.println(s2 == s3); // true 10 11 System.out.println(s1.equals(s2)); // false 12 System.out.println(s1.equals(s3)); // false 13 System.out.println(s2.equals(s3)); // true
记住:StringBuffer是一个容器。返回的还是原缓冲区对象。就不难理解上述答案。
3、String、StringBuffer、StringBuilder异同
StringBuffer是线程同步。StringBuilder是线程不同步。实际开发中建议使用StringBuilder,效率高。
String:不可变的字符序列,底层使用char[]存储。
StringBuffer:可变的字符序列,线程安全的,效率低。底层使用char[]存储。
StringBuilder:可变的字符序列,线程不安全的,效率高。底层使用char[]存储。
4、String、StringBuffer、StringBuilder效率对比
从高到低:StringBuilder > StringBuffer > String