String、StringBuffer、StringBuilder类解析
概述
String类:代表字符串。
特点
- String实现了Serializable接口,表示String是可序列化的
- 实现了Comparable接口
- 实现了CharSequence(字符序列接口)
- String类中用于存储字符的数组
value[]
是final类型的 - String代表不可变的字符序列,具有不可变性。
- 被final修饰,无法被继承
String类的部分源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
// ...
}
String对象的创建
方式一: 字面量赋值
String s = "hello";
变量s中存储的地址是常量池当中对应的地址值。
方式二: 使用new关键字
String s1 = new String();
// this.value = original.value;
String s2 = new String(String original);
// this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
// offset: 起始下标, count字符个数
String s4 = String(char value[], int offset, int count);
以上s1, s2, s3, s4保存的地址值都是 数据在堆空间中开辟空间后对应的地址值。
String的不同拼接方式对比
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
String s8 = s6.intern();
System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s5 == s7); // false
System.out.println(s6 == s7); // false
System.out.println(s3 == s8); // true
总结:
- 如果拼接方式是字面量拼接,那么拼接结果是在常量池中的。
- 如果拼接方式是变量 + 字面量、变量 + 变量,只要有变量参与,就是在堆中创建对象。
- 如果拼接结果调用的
intern()
方法,返回值就在常量池中。
一道String相关面试题
public class StringTest {
String s = new String("good");
char[] ch = {'t' , 'e' , 's' , 't' };
public void change(String str, char[] ch) {
str = "hello";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.s, ex.ch);
System.out.println(ex.s); // good
System.out.println(ex.ch); // best
}
}
解析:
方法的参数类型是引用数据类型,传递的是地址值,一般情况下会改变引用值,但是参数是String类型的,具有不可变性,所以不会发生改变,还是原来的值。
String常用方法
方法 | 说明 |
---|---|
int length() | 返回字符串的长度: return value.length |
char charAt(int index) | 返回某索引处的字符return value[index] |
boolean isEmpty() | 判断是否是空字符串:return value.length == 0 |
String toLowerCase() | 使用默认语言环境,将 String 中的所有字符转换为小写 |
String toUpperCase() | 使用默认语言环境,将 String 中的所有字符转换为大写 |
String trim() | 返回字符串的副本,忽略前导空白和尾部空白 |
boolean equals(Object obj) | 比较字符串的内容是否相同 |
boolean equalsIgnoreCase(String anotherString) | 与equals方法类似,忽略大 小写 |
String concat(String str) | 将指定字符串连接到此字符串的结尾。等价于用“+” |
int compareTo(String anotherString) | 比较两个字符串的大小 |
String substring(int beginIndex) | 返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。 |
String substring(int beginIndex, int endIndex) | 返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。 |
boolean endsWith(String suffix) | 测试此字符串是否以指定的后缀结束 |
boolean startsWith(String prefix) | 测试此字符串是否以指定的前缀开始 |
boolean startsWith(String prefix, int toffset) | 测试此字符串从指定索引开始的 子字符串是否以指定前缀开始 |
boolean contains(CharSequence s) | 当且仅当此字符串包含指定的 char 值序列 时,返回 true |
int indexOf(String str) | 返回指定子字符串在此字符串中第一次出现处的索引 |
int indexOf(String str, int fromIndex) | 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始 |
int lastIndexOf(String str) | 返回指定子字符串在此字符串中最右边出现处的索引 |
int lastIndexOf(String str, int fromIndex) | 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索 注:indexOf和lastIndexOf方法如果未找到都是返回-1 |
String replace(char oldChar, char newChar) | 返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有 oldChar 得到的。 |
String replace(CharSequence target, CharSequencereplacement) | 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。 |
String replaceAll(String regex, String replacement) | 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。 |
String replaceFirst(String regex, String replacement) | 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 |
boolean matches(String regex) | 告知此字符串是否匹配给定的正则表达式。 |
String[] split(String regex) | 根据给定正则表达式的匹配拆分此字符串。 |
String[] split(String regex, int limit) | 根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。 |
String与包装类和基本数据类型之间的转换
String -> 包装类、基本数据类型
调用parseXxx(String s)
方法
String str = "123";
String str1 = "123.456";
int parseInt = Integer.parseInt(str);
double parseDouble = Double.parseDouble(str1);
基本数据类型 -> String
int num1 = 1;
Integer num2 = new Integer(1);
String s1 = String.valueOf(num1);
String s2 = String.valueOf(num2);
StringBuffer和StringBuilder
String、StringBuffer、StringBuilder三者的异同?
- String: 不可变的字符序列,底层使用char数组存储
- StringBuffer: 可变的字符序列,线程安全的,但是效率低,底层使用char数组存储
- StringBuilder: 可变的字符序列,JDK5.0新增的,线程不安全的,效率高,底层使用char数组存储
StringBuffer
java.lang.StringBuffer
代表可变的字符序列,JDK1.0
中声明,可以对字符串内容进行增删,此时不会产生新的对象。- 很多方法与String相同
- 作为参数传递时,方法内部可以改变值
源码分析
StringBuffer类继承了抽象类AbstractStringBuilder,继承了属性char[] value
,用于存储字符。
public final class StringBuffer
extends AbstractStringBuilder
implements Serializable, CharSequence
{
// ...
}
抽象类:AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// ...
}
创建一个StringBuffer类的对象必须使用关键字new的方式进行创建。
调用StringBuffer类的空参构造器时回调用父类的构造器,将char[]初始大小设置为16。
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
// 父类的构造器
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
因为StringBuffer中用于存储字符的数组是可变的,所以可以对这个字符进行添加、修改、删除等操作。
所以有一个问题,那就是默认长度是16,如何实现扩容?
来看看StringBuffer中的append方法:
// 重写了父类的append方法,但是在重写的方法中还是调用了父类的append方法
// 并且重写后的方法是线程安全的
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
再来看看父类中的append方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length(); // 获取要添加的字符串的长度
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
// ensureCapacityInternal方法处理是否需要进行扩容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
// 调用Arrays中的copyOf方法,将原来的数组内容复制给新的数组
value = Arrays.copyOf(value,
newCapacity(minimumCapacity)); // 实际的扩容方法
}
}
// newCapacity
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2; // 默认情况下是原来的大小乘2在加上2
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity; // 如果容量还是不够,就将minCapacity作为容量大小
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) // 如果需求容量更大的话在进一步处理
? hugeCapacity(minCapacity)
: newCapacity;
}
但是为了避免频繁的扩容操作,所以一般推荐使用以下构造器创建StringBuffer对象:
一开始就指定容量大小,避免扩容操作。
public StringBuffer(int capacity) {
super(capacity);
}
StringBuilder
StringBuilder与StringBuffer一样,都实现了AbstractStringBuilder类,StringBuilder与StingBuffer的构造器和append方法几乎一致,不同的是StringBuffer中的方法是线程安全的,被synchronized修饰,而StringBuilder中的方法都是线程不安全的。
如:
Stringbuffer
@Override
public synchronized int length() {
return count;
}
StringBuilder
@Override
public int length() {
return count;
}
StringBuffer和StringBuilder中常用方法
方法 | 说明 |
---|---|
append(xxx) | 提供了很多的重载的append()方法,用于进行字符串拼接 |
delete(int start,int end) | 删除指定位置的内容 |
replace(int start, int end, String str) | 把[start,end)位置替换为str |
insert(int offset, xxx) | 在指定位置插入xxx |
reverse() | 把当前字符序列逆转 |
-
当append和insert时,如果原来value数组长度不够,可扩容。
-
如上这些方法支持方法链操作。
-
方法链的原理:方法逻辑结束后return this;
此外,还定义了如下的方法:
- public int indexOf(String str)
- public String substring(int start,int end)
- public int length() public char charAt(int n )
- public void setCharAt(int n ,char ch)
String、StringBuffer、StringBuilder三者效率对比
测试代码
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
执行结果
StringBuffer的执行时间:6
StringBuilder的执行时间:2
String的执行时间:887
总结:
效率按大小排序:StringBuilder > StringBuffer > String