一、概述
平时工作中可能使用最多的就是String字符串了,所以我们有必要深入的了解一下String的实现。
二、类头
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
final说明String不可被继承,可序列化,实现了Comparable接口(具体比较逻辑见源码),所以我们可以直接使用Collections.sort(List<String>)对字符串进行排序,默认调用自己的方法来做比较,如果达不到你想要的要求就要自己编写比较器实现Comparator接口,然后调用Collection.sort(List,Comparator)来实现定制排序,方便灵活。
“CharSequence is a readable sequence of char values. This interface provides uniform, read-only access to many different kinds of char sequences.”CharSequence 是 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。
三、内部属性
private final char value[];//char数组用来存放String内部的char
private final int offset;//偏移量,标示了截取value数组的开始位置
private final int count;//记录字符串中字符的个数
private int hash; // Default to 0
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];//作用不清楚,请高手不吝赐教
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();//重写的比较器,比较忽略大小写时使用
四、常见方法
常用的构造器
public String(String original) //通过String来构造新字符串
public String(char value[])//通过字符数组来构造,新的字符串里面的char数组是重新生成的,没有指向传入数组
public String(byte bytes[], String charsetName)//通过byte数组,跟字符编码来初始化String
public String(byte bytes[])//使用默认编码解析bytes数组,window为GBK,Linux为UTF-8
public String(StringBuffer buffer)//使用SB来初始化
String(int offset, int count, char value[]) {//包级私有构造函数,外面调用不了,注意这里新生成的字符串的字符数组指向了传入的value
this.value = value;//这里有潜在内存泄露危险,下面会看到。
this.offset = offset;
this.count = count;
}
常用的方法
查找方法:
public int codePointAt(int index)//通过序列找到对应字符的Unicode编码
public char charAt(int index) //通过索引找到对应字符
public int indexOf(int ch)//找到对应首次出现的字符序号,没有返回-1
public int lastIndexOf(int ch) //找到最后出现ch的位置,没有找到返回-1
比较方法
public boolean equalsIgnoreCase(String anotherString)//忽略大小写比较
public boolean equals(Object anObject) {//判断是否相同,判断的依据是内容完全相同
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
public int compareTo(String anotherString) {//比较方法,策略是从头比较,找到第一个不同的返回差值
int len1 = count;
int len2 = anotherString.count;
int n = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
if (i == j) {
int k = i;
int lim = n + i;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;//不同长度,按照首次不一致char值来比较大小的
}
k++;
}
} else {
while (n-- != 0) {
char c1 = v1[i++];
char c2 = v2[j++];
if (c1 != c2) {
return c1 - c2;
}
}
}
return len1 - len2;
}
public int compareToIgnoreCase(String str) {//忽略大小写比较字符串
return CASE_INSENSITIVE_ORDER.compare(this, str);//自己实现了比较器
}
截取方法:
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);//重新生成一个字符串,这里新生成的字符串使用的原有字符串的字符数组,本意是通过复用提高效率,但是在原串很大的情况下,新截取的是很小的一部分,会产生垃圾数据,导致内存泄露。建议截取之后再重新
new String(s.toCharArray)。
}
替换分割:
public String replaceAll(String regex, String replacement) {//该方法经常使用的话并且正则表达式不经常改变,建议把Pattern对象提取出来,
//重复使用,来提高效率,同理其他替换方法。
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
public static Pattern compile(String regex) {
return new Pattern(regex, 0);//调用compile方法每次都会重新生成一个pattern类
}
public String[] split(String regex) {//按照正则表达式分割,注意如果想要截取. 或者|,需要\\.和\\|,另外如果想要截取条件多个可以and|or两个都截取
return split(regex, 0);
}
其他方法
public char[] toCharArray() {//获得String的char数组,得到的是新的数组
char result[] = new char[count];
getChars(0, count, result, 0);
return result;
}
public native String intern();//本地方法,去字符串池里找是否存在该字符串,有直接返回,没有添加到字符串池中,默认池中数据时在编译时写入,该方法提供动态添加池中数据功能
public String concat(String str) {//字符串连接方法,如果低于3个一下字符串连接可以使用,效率高,过多的字符串连接应该使用SB系列
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
五、总结
String字符串对象是不可变对象,线程安全,不需要同步。
为提高效率,jvm会创建缓冲池,存放编译期间的字符串,来达到复用目的,通过intern方法可以动态添加。