String
1:字符串的不可变性
什么是不可变对象?不可变对象是指创建后无法变更的对象
String为什么是不可变的?String类为final,并且内部字符数组也为final。所以String对象是不可变对象。
String类为什么要设计为不可变?
主要出于对效率和安全的考量。
当你复制一个对象的时候,如果你知道它是不可变的,那么你只需要复制此对象的引用即可,一般引用会比对象小很多,所以能提高效率;String是不可变的,所以字符串常量池才可以存在,减少很多heap内存的占用;因为String的不可变性,所以在创建的时候hashcode就可以缓存,很适合作为map的key值;
安全方面:不可变对象是线程安全的。在多线程情况下,可变对象的内部状态可能会被其他线程改变,导致不可预期的结果。比如数据库连接,socket连接的IP PORT,类加载器等,都是通过String传参的,如果String是可变的,那会引起很大的安全问题。
2:JDK 6和JDK 7中substring的原理及区别
subString(int beginIndex, int endIndex)方法用来截取字符串
String x = "qwertt";
x = x.substring(1,2);
System.out.println(x);
结果输出:
w
JDK6中的subString
String类有三个属性:
char[] value:字符数组
int offset:起始位置
int count:字符串长度
对于subString方法,生成的String对象,value相同,只是改变了offset和count。这样会导致一个严重的问题:本来只需要很短的字符串,但是因为指向了一个很长的字符串,导致这个长字符串无法回收,存在内存泄漏的风险。
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
jdk6中,为解决上述问题,一般生成一个新的字符串并引用它:
x = x.substring(x, y) + ""
JDK7中的subString
在jdk7中,对上述问题进行了优化。每次执行subString的时候,都会去生成一个新的char[] ,从而避免了上述问题
jdk7源码如下:
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
3:replaceFirst、replaceAll、replace区别
先看一个示例:
String s = "my.test.txt";
System.out.println(s.replace(".", "#"));
System.out.println(s.replaceAll(".", "#"));
System.out.println(s.replaceFirst(".", "#"));
System.out.println(s.replaceFirst("\\.", "#"));
执行结果:
my#test#txt
###########
#y.test.txt
my#test.txt
原因:
replace方法有两个实现,一个是传入字符,循环匹配;一个是传入字符串,使用Pattern的逐个按字符进行匹配;
replaceFirst和replaceAll是使用Pattern,进行正则表达式的匹配。因为“.”在正则表达式中,表示任一字符,所以出现了“###########”的结果。
附源代码实现(jdk1.8版)
replace():
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
replace():
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
replaceAll():
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
replaceFirst():
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
4:String对“+”的重载、字符串拼接的几种方式和区别
字符串的拼接方式:“+”、StringBuffer、new String().concat、StringBuilder
“+”:底层是使用StringBuilder实现。如:
String s1 = "11";
String s2 = "22";
String s = s1+s2;
System.out.println(s);
其实此段代码基本等价于:
String s1= "11";
String s2= "22";
StringBuilder sb = new StringBuilder();
sb.append(s1);
sb.append(s2);
String str = sb.toString();
System.out.println(str);
在大量使用“+”进行字符串拼接的时候,会产生大量的StringBuilder和String对象,会严重影响效率
concat:
concat其实是申请一个新的数组,进行数组的拷贝,然后用来创建新的String对象。底层是调用:System.arraycopy()
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
StringBuffer & StringBuilder
两者调用的父类方法如下,区别在于StringBuffer 方法用了synchronized,是线程安全的
与concat的区别在于:
扩容逻辑不同,concat为需要多少扩多少,StringBuilder等是指数级扩容;
concat每次会生成新的String对象,而StringBuilder不会
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;
}
5:String.valueOf和Integer.toString的区别
直接看源代码就好
public static String valueOf(int i) {
return Integer.toString(i);
}
//对null进行了处理
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
6:switch对String的支持
Java JDK7中switch添加了对String的支持,之前仅支持(short、int、byte、char),并且底层最终都会转为int类型。那么对于String,是如何支持的呢?
请看以下代码:
public static void test(String status) {
switch (status) {
case "INIT":
System.out.println("INIT");
break;
case "PAY_ING":
System.out.println("PAY_ING");
break;
case "PAY_SUCCESS":
System.out.println("PAY_SUCCESS");
break;
case "PAY_FAIL":
System.out.println("PAY_FAIL");
break;
default:
System.out.println("default");
break;
}
}
反编译class文件得到:
public void test(String status)
{
String str;
switch ((str = status).hashCode())
{
case -2113017739:
if (str.equals("PAY_FAIL")) break label129; break;
case -68158581:
if (str.equals("PAY_ING")) break label107; break;
case 2252048:
if (str.equals("INIT")) break label96; break;
case 1643683628:
if (!(str.equals("PAY_SUCCESS"))) { break label140:
System.out.println("INIT");
return;
System.out.println("PAY_ING");
return;
}
System.out.println("PAY_SUCCESS");
return;
label129: System.out.println("PAY_FAIL");
label140: break;
default:
label96: label107: System.out.println("default");
}
}
虽然看不懂有些带标签的break语句(break label;)但是很明显可以看出来支持String的方式:
将String转为了int类型的hashCode,因为hashCode可能会冲突,又加入了equals判断。
7:字符串池、常量池(运行时常量池、Class常量池)、intern
偷个懒,先放个链接,后续再慢慢完善
https://www.cnblogs.com/tiancai/p/9321338.html