如果面试官问 String,就把这篇文章丢给他!
字符串的不可变性
JDK 1.6和JDK 1.7中substring的原理及区别
replaceFirst、replaceAll、replace区别
String对“+”的“重载”
字符串拼接的几种方式和区别
Integer.toString()和String.valueOf()的区别
switch对String的支持(JDK 1.7及其后版本)
字符串常量池、Class常量池、运行时常量池
String.intern()方法
1. 字符串的不可变性
/** 公众号:Java后端 */
public class Test {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println("str1 == str2:" + str1 == str2);
}
}
在解释之前,先介绍一下System.identityHashCode():
System.identityHashCode()的作用是用来判断两个对象是否是内存中同一个对象,跟用==判断内存地址是否一样的效果一样。
System.out.println("str1:" + System.identityHashCode(str1));
System.out.println("str2:" + System.identityHashCode(str2));
String str3 = str1;
System.out.println("str1 == str3:" + str1 == str3);
str3 += "ny";
System.out.println("str1 == str3:" + str1 == str3);
2. JDK 1.6和JDK 1.7中substring的原理及区别
String str = "我不是你最爱的小甜甜了吗?";
str = str.substring(1,3);
System.out.println(str);
不是
JDK 1.6的substring
public String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
这种结构看上去挺好的,只需要创建一个字符数组,然后可以通过调整offset和count就可以返回不同的字符串了。但事实证明,这种情况还是比较少见的,更常见的是从一个很长很长的字符串中切割出需要用到的一小段字符序列,这种结构会导致很长的字符数组一直在被使用,无法回收,可能导致内存泄露。所以一般都是这么解决的,原理就是生成一个新的字符并引用它。
str = str.substring(1, 3) + "";
JDK 1.7的substring
public String(char value[], int offset, int count) {
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
3. replaceFirst、replaceAll、replace区别
public static void main(String[] args) {
String str = "I.am.fine.";
System.out.println(str.replace(".", "\\"));
System.out.println(str.replaceAll(".", "\\\\"));
System.out.println(str.replaceFirst(".", "\\\\"));
}
I\am\fine\
\\\\\\\\\\
\.am.fine.
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);
}
System.out.println(str.replaceAll("\\.", "\\\\"));
replaceFirst
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
4. String对“+”的“重载”
private final char value[];
public static void main(String[] args) {
String str = "abc";
str += "123";
}
运算符重载:在计算机程序设计中,运算符重载(operator overloading)是多态的一种。运算符重载就是对已有的运算符进行定义,赋予其另一种功能,以适应不同的数据类型。
5. 字符串拼接的几种方式和区别
String.concat()拼接
public static void main(String[] args) {
String str = "我不是你最爱的小甜甜了吗?";
str = str.concat("你是个好姑娘");
System.out.println(str);
}
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);
}
可以看到,concat()的拼接实际上是,创建一个长度为已有字符串和待拼接字符串的长度之和的字符数组,然后将两个字符串的值赋值到新的字符数组中,最后利用这个字符数组创建一个新的String对象。StringBuilder.append()拼接
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("我不是你最爱的小甜甜了吗?");
sb.append("你是个好姑娘");
System.out.println(sb.toString());
}
char[] value;
int count;
public StringBuilder append(String str) {
super.append(str);
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null)
returpublic 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;
}n appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
StringBuffer.append()拼接
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringUtils.join()拼接
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("我不是你最爱的小甜甜了吗?");
list.add("你是个好姑娘");
String s = new String();
s = StringUtils.join(list, s);
System.out.println(s);
}
public static String join(Collection var0, String var1) {
StringBuffer var2 = new StringBuffer();
for(Iterator var3 = var0.iterator();
var3.hasNext(); var2.append((String)var3.next())) {
if (var2.length() != 0) {
var2.append(var1);
}
}
return var2.toString();
}
使用StringBuilder.append()的方式是效率最高的; 如果不是在循环体中进行字符串拼接,用"+"方式就行了; 如果在并发场景中进行字符串拼接的话,要使用StringBuffer代替StringBuilder。
6. Integer.toString()和String.valueOf()的区别
public static void main(String[] args) {
int i = 1;
String integerTest = Integer.toString(i);
String stringTest = String.valueOf(i);
}
Integer.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);
}
String.valueOf()方法
public static String valueOf(Object obj)
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[])
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(boolean b)
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(int i)
public static String valueOf(int i) {
return Integer.toString(i);
}
7. switch对String的支持(JDK 1.7及其后版本)
public static void main(String[] args) {
String str = "abc";
switch (str) {
case "ab":
System.out.println("ab");
break;
case "abc":
System.out.println("abc");
break;
default:
break;
}
}
ab
public static void main(String[] var0) {
String var1 = "abc";
byte var3 = -1;
switch(var1.hashCode()) {
case 3105:
if (var1.equals("ab")) {
var3 = 0;
}
break;
case 96354:
if (var1.equals("abc")) {
var3 = 1;
}
}
......
}
8. 字符串常量池、Class常量池、运行时常量池
字符串常量池
JDK 1.7之前,字符串常量池在永久代中 JDK 1.7,将字符串常量池移出了永久代,搬到了DataSegument中,一个在堆中一个相对特殊的位置(失去唯一引用也不会被回收) JDK 1.8,永久代被元空间取代了
class NY{
String str = "nyfor2020";
}
public class Test {
public static void main(String[] args) {
NY ny1 = new NY();
NY ny2 = new NY();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB
Class常量池
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法去的运行时常量池中存放。
类和接口的全限定名 字段的名称和描述符 方法的名称和描述符
public class Test {
public static void main(String[] args) {
String s1 = "nyfor2020";
}
}
javap -verbose Test.class
运行时常量池
9. String.intern()方法
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
public static void main(String[] args) {
String str1 = new String("1") + new String("1");
str1.intern();
String str2 = "11";
System.out.println(str1 == str2);
}
在JDK 1.6中的intern()
在JDK 1.7中的intern()
public static void main(String[] args) {
String str1 = new String("1") + new String("1")
String str2 = "11";
str1.intern();
System.out.println(str1 == str2);
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
}
false
22307196
10568834
结语
Java中String对象的不可变性 https://www.cnblogs.com/qingergege/p/5701011.html
在Java虚拟机中,字符串常量到底存放在哪
https://blog.csdn.net/weixin_34413802/article/details/88009934
了解JDK 6和JDK 7中substring的原理及区别
https://www.cnblogs.com/dsitn/p/7151624.html
java的replaceFirst和(反斜杠)[replace、replaceAll和replaceFirst的区别]
https://blog.csdn.net/lonfee88/article/details/7333883
String 重载 “+” 原理分析
https://blog.csdn.net/codejas/article/details/78662146
字符串拼接的几种方式和区别
https://www.cnblogs.com/lujiahua/p/11408689.html
Java—String.valueof()和Integer.toString()的不同
https://blog.csdn.net/whp1473/article/details/79935082
java switch是如何支持String类型的?
https://blog.csdn.net/guohengcook/article/details/81267768
JVM | 运行时常量池和字符串常量池及 intern()
https://hacpai.com/article/1581300080734
Java中几种常量池的区分
http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/
《深入理解Java虚拟机》