Java中String的應用無處不在,無論是算法題還是面試題,String都獨佔一方,甚至是無數面試者心中難以名狀的痛。本文著重對String(若無特地說明,默認是JDK 1.8版本)常見的問題來進行介紹:
字符串的不可變性
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.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);
String str = "我不是你最愛的小甜甜了嗎?";
str = str.substring(1,3);
System.out.println(str);
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所以在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);
}
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.
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).
matcher(this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
System.out.println(str.replaceAll("\\.", "\\\\"));
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
private final char value[];
而且在上文我們已經提到String具有不可變性,可當我們在使用「+」對字符串進行拼接時,卻可以成功。它的原理是什麼呢?舉個慄子:public static void main(String[] args) {
String str = "abc";
str += "123";
}
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);
}
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("我不是你最愛的小甜甜了嗎?");
sb.append("你是個好姑娘");
System.out.println(sb.toString());
}
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;
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
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();
}
public static void main(String[] args) {
int i = 1;
String integerTest = Integer.toString(i);
String stringTest = String.valueOf(i);
}
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);
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(int i) {
return Integer.toString(i);
}
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;
}
}
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;
}
}
.
}
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
打開HSDB,輸入進程號後使用Object Histogram找到相應類之後,可以找到兩個NY對象引用的字符串的地址是同一個。public class Test {
public static void main(String[] args) {
String s1 = "nyfor2020";
}
}
javap -verbose Test.class
在反編譯之後我們可以直接看到Class常量池中的內容,有類的全限定名、方法的描述符和欄位的描述符。也就是說,當Java文件被編譯成Class文件的過程之後,就會生成Class常量池。那麼運行時常量池又是什麼時候產生的呢?運行時常量池運行時常量池是方法區的一部分,用於存放Class文件編譯後生成的Class常量池等信息。接下來我們結合類加載過程來認識這幾個常量池之間的關係:/**
* 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);
}
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));
}
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虛擬機》