Java中字符串处理主要有这三个类完成:String
、StringBuilder
、StringBuffer
String
String是不可变对象
构造方法
创建字符串常见方式:4种构造方法,1种直接构建
String()
初始化一个String对象,表示一个空字符串String(String value)
利用一个直接的字符串来创建一个字符串对象String(char[] value)
利用一个字符数组来创建String(byte[] value)
利用一个字节数组来创建- 直接双引号创建 --用双引号来创建的字符串常量属于直接量字符串对象,JVM在处理这类字符串的时候,会进行缓存,产生时放入字符串常量池,当程序需要再次使用的时候,无需重新创建一个新的字符串,而是直接指向已存在的字符串。
常用方法与属性
1. 不可变的原因
private final char value[];
- 底层是一个字符串数组
- 这个数组被final关键字修饰,一旦赋值,不可以被改变
- 字符串很常用,而且很大概率会出现重复的现象,为了提升效率和减少内存分配,于是诞生了字符串常量池。得益于不可变,常量池很容易被管理
- 考虑安全性。字符串涉及到的场景很多,不可变可以防止被有意无意的篡改(路径、全限定类名)
- 作为HashMap、HashTable等hash型数据key的必要。因为不可变的设计,jvm底层很容易在缓存String对象的时候缓存其hashcode,这样在执行效率上会大大提升。
2. 字符串长度
public int length() {
return value.length;
}
- 因为底层是一个常量数组,所以字符串长度就是获取这个数组的长度
3. 比较
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null) && (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
- 判断是否为同一个引用,否返回
false
- 使用
instanceof
关键字来判断两者是否都是字符串,否返回false
- 强转(equals)
- 判断长度是否相等,否返回
false
- 对字符数组逐字符比较
重写equals方法一般步骤
- 使用
==
来检查实参是否为对象的一个引用,如果不是,返回false
- 使用
isinstanceof
来检查实参是否为正确的类型(和我同一个类型),如果不是,返回false
- 把实参转换到正确的类型
- 检查实参中的属性与当前对象中对应的属性值是否匹配
- 除
float
、double
类型的基本数据类型,直接用==
来判断 float
类型用Float.floatToIntBits
转成int
类型的值,然后用==
来比较double
类型用Double.doubleToIntBits
转成int
类型的值,然后用==
来比较- 引用类型可以递归地调用其
equals
方法
- 除
重写之后考虑三个问题
- 是否对称:a.equals(b)的结果和b.equals(a)的结果相同
- 是否传递:a.equals(b)=true b.equals(c)=true => a.equals(c)
- 是否一致
4. 驻足
public native String intern();
简单来说:用来返回字符串常量池中的某个字符串,如果这个字符串在常量池已存在,则直接返回这个字符串常量的引用;如果不存在字符串常量池,在字符串常量池中加入这个字符串,再返回引用
只有创建的时候直接用双引号创建的字符串才会进入字符串常量池,用构造方法创建的字符串不会进入常量池,这个方法则可以把这个对象(如果不存在常量池)放入到常量池
String s1 = new String("abc");
String s2 = "abc";
System.out.println(s1 == s2); // 显然是false
s1 = new String("xyz").intern();
s2 = "xyz";
System.out.println(s1 == s2); // true
5. 比较
public int compareTo(String anotherString);
- 把两个字符串进行左对齐,从左到右依次比较
- 如果相同则继续比较
- 如果不同则比较不同的字符ascii码的差值
- 如果都相同则返回两个字符串长度的差值
"hello".compareTo("hel"); // 返回2
6.截取子字符串
//从指定的位置开始截到末尾
public String substring(int beginIndex) {
return substring(beginIndex, count);
}
//指定截取开始和结束的索引
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);
}
7.字符串切割
public String[] split(String regex);
- 注意参数是一个正则表达式,在切割
.
的时候需要注意进行转义\\.
- 如果没有找到切割的字符串则返回一个长度为1的字符串数组,其中的元素就是原始字符串
"hello".split("abc"); //返回["hello"]
编译器优化
String a="helloworld";
String b="hello"+"world";
System.out.println((a==b)); //返回true,因为b在编译期的时候已经被优化成helloworld,因此在运行期a和b指向的是字符串常量池中同一个字符串
String a="helloworld";
String b="hello";
String c=b+"world";
System.out.println((a==c)); //返回flase,因为有符号引用b存在,所以在编译器不会优化
String a="helloworld";
final String b="hello";
String c=b+"world";
System.out.println((a==c)); //返回true,因为被final修饰的对象,在编译器会直接被替换成直接引用(但是如果final修饰的对象是通过方法返回赋值的,就不管用)
StringBuilder
可变字符串,线程不安全,性能较好
构造方法
public StringBuilder(); //默认开辟16个字符长度的空间
public StringBuilder(int capacity); //开辟capacity个字符长度的空间
public StringBuilder(String str); //开辟str.length+16个字符长度的空间
public StringBuilder(CharSequence seq); //开辟seq.length+16个字符长度的空间
常用方法
StringBuilder append(...); //添加数据,可以添加任意类型(int、float、String)
String toString(); // 转换成String对象
和String互换
- String -> StringBuilder:
StringBuilder stringBuilder = new StringBuilder(str);
- StringBuilder -> String:
String str = stringBuilder.toString();
StringBuffer
可变字符串,线程安全(大多数方法普通方法都加上了synchronized
关键字,这样就会有一定的性能消耗)
构造方法
public StringBuffer(); //默认开辟16个字符长度的空间
public StringBuffer(int capacity); //开辟capacity个字符长度的空间
public StringBuffer(String str); //开辟str.length+16个字符长度的空间
public StringBuffer(CharSequence seq); //开辟seq.length+16个字符长度的空间
常用方法
public synchronized int length(); //获取当前字符串长度(内容长度)
public synchronized int capacity(); //获取当前缓冲区长度(数组长度)
StringBuffer stringBuffer = new StringBuffer("hello");
System.out.println(stringBuffer.length()); //5
System.out.println(stringBuffer.capacity()); //5+16(默认长度)=21
StringBuffer没有重写equals
方法,所以调用该方法会使用父类Object
类的equals
方法,比较两个对象的地址值。
String、StringBuilder、StringBuffer三者区别
- 可变性:StringBuilder和StringBuffer是可变的,String是不可变的。原因:final修饰,一旦赋值不可以修改。目的:String不可变的目的
- 线程安全性:StringBuffer中的大部分方法都加上了同步锁,所以是线程安全的;StringBuilder和String的方法都没有加锁,是线程安全的。
- 性能方面:由于StringBuffer加了同步锁,导致性能变差;StringBuilder没有加锁,性能较于StringBuffer来说性能比较好;String每次都会生成新的对象,性能更差一点。StringBuffer和StringBuilder都不会生成新的对象,比String来说效率更高。
- 使用上来说:操作少量数据:String;单线程操作大量数据:StringBuilder;多线程操作大量数据:StringBuffer