String不可变性
String的不变性指的是类值一旦被初始化,就不能被改变。我们从源码出发,可以看到String类由final修饰,即类不能被继承,String中的方法不能被继承重写。String通过一个char数组value来保存数据,同样是final修饰的,即value数组一旦被赋值,内存地址无法修改。String的不变性,充分利用了final关键字的性质。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}
String被设计成不可变的主要目的是为了安全和高效。
第一,安全方面。String中有些方法比较底层,如果被继承并重写方法会有隐患;同时不可变对象一定是线程安全的,可以在线程之间共享,无需同步。
第二,设计者希望用户用到的String就是JDK中的String,不希望被继承和重写,确保String可控。
第三,性能方面。基于不变性,编译器和JVM可以对String进行优化,提高性能。
String是不可变的,其hashcode值也是固定不变的。String通过hash来缓存当前字符串的hashcode值,保证只需要计算一次。
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
方法中首先对hash属性值做了判断,如果计算过hashCode,结果会被缓存到hash属性,不需要重复计算。
replaceFirst()、replaceAll()、replace()三个方法的区别
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
将字符串中目标文本字符序列(非正则表达式)替换为指定字符序列。
替换从字符串的开头到结尾,例如,在字符串“aaa”中将“aa”替换为“b”将导致“ba”而不是“ab”。
小结:
replace和replaceAll都是全部替换,replace替换的只能是字符或字符串形式,而replaceAll是基于正则表达式替换。replaceAll和replaceFirst()是基于正则表达式的替换,replaceFirst仅替换首次出现。replace的另一个方法参数类型是char,对字符串中所有匹配字符做替换。replace(String)方法还可以用于对字符串中字符的删除。
"Awecoder".replace("e","") // 值为Awcodr
扩展:对于替换,特别注意转移字符’’。
反斜杠'\'即是Java语言的转移字符,也是正则表达式的转义字符。反斜杠\,在正则表达式表示反斜杠用\\,而Java代码需要用\\\\来表示。
// 输出a\b
System.out.println("a/b".replaceAll("/","\\\\"));
System.out.println("a/b".replace("/","\\"));
System.out.println("a/b".replace('/', '\\'));
如何解决String乱码问题
出现乱码主要是因为字符集不支持复杂汉字、二进制转化时字符集不匹配等原因。在出现String乱码时,我们通常会采取以下操作。
- 在可以指定字符集的地方强制指定字符集,例如
new String和getBytes。 - 使用UTF-8这种能完整支持复杂汉字的字符集。
示例:
String str = "字符串";
byte[] bytes = str.getBytes("ISO-8859-1"); // 字符串转化成byte数组
String s2 = new String(bytes); // byte数组转化成字符串
System.out.println(s2);
运行结果:
???
首字母大小写转换
首字母大小写我们通常采用下面的代码来实现。
String str = "Awecoder";
str.substring(0, 1).toLowerCase() + str.substring(1) // awecoder
底层源码分析
public String substring(int beginIndex, int endIndex) {
// 边界处理
int subLen = endIndex - beginIndex;
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
// 边界处理
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
substring()方法会创建一个新的字符串,底层采用了字符数组范围截取: Arrays.copyOfRange(value, offset, offset+count)。
String的相等判断
在String中通常使用equals和equalsIgnoreCase两个方法。其中,equalsIgnoreCase方法忽略大小写比较,参数类型是String。equals方法是重写方法,其参数类型是Object。在进行相等判断设计时,equals方法更具代表性。
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;
}
equals的源码设计逻辑是很清晰的,分为下面四个步骤。
- 判断内存地址是否相同
- 判断对象类型是否相同
- 判断数据长度是否相同
- 挨个比较每个数据元素是否相同
字符串拆分
字符串拆分采用的是split方法,底层split方法有两个参数,分别是分隔符和分隔的最大子字符串个数。
public String[] split(String regex, int limit)
实例:
String str = "awe|coder";
str.split("|"); // ["awe","coder"]
str.split("|", 1); // ["awecoder"]
str.split("|", 2); // ["awe","coder"]
如果分隔符之间存在空值,空值是拆分不掉的,作为空串成为结果数组一部分。借助外部工具例如Guava,可以快速并且可靠地去除空格、空值等。
String str ="|awe||coder";
str.split("|") 结果:["","awe","","coder"]

被折叠的 条评论
为什么被折叠?



