String是最常使用的Java類之一,位于java的lang包下面,是唯一一個(gè)不需要引包的包。
下面我們來(lái)討論一下String類的一些基本問(wèn)題
1、什么是String,它是什么數(shù)據(jù)類型?
String是定義在 java.lang 包下的一個(gè)類。它不是8種基本數(shù)據(jù)類型之一。String是不可變的,JVM使用字符串池來(lái)存儲(chǔ)所有的字符串對(duì)象。
2、創(chuàng)建String對(duì)象的不同方式有哪些?
1)、 和其他類一樣通過(guò)new一個(gè)對(duì)象來(lái)創(chuàng)建。
使用這種方式時(shí),JVM創(chuàng)建字符串對(duì)象但不存儲(chǔ)于字符串池,存儲(chǔ)在棧或者堆當(dāng)中。我們可以調(diào)用intern()方法將該字符串對(duì)象存儲(chǔ)在字符串池,如果字符串池已經(jīng)有了同樣值的字符串,則返回引用。
2)、使用雙引號(hào)直接創(chuàng)建。
使用這種方式時(shí),JVM去字符串池找有沒有值相等字符串,如果有,則返回找到的字符串引用。否則創(chuàng)建一個(gè)新的字符串對(duì)象并存儲(chǔ)在字符串池。
String str = new String("abc");
String str1 = "abc";
3、寫一個(gè)方法來(lái)判斷一個(gè)String是否是回文(順讀和倒讀都一樣的詞)?
這里介紹兩個(gè)方法:
1)、使用StringBuffer和StringBuilder有reverse方法
首先將String轉(zhuǎn)換為StringBuffer或StringBuilder
private static boolean isPalindrome(String str) {
if (str == null)
return false;
StringBuilder strBuilder = new StringBuilder(str);
strBuilder.reverse();
return strBuilder.toString().equals(str);
}
2)、首尾一一對(duì)比
private static boolean isPalindromeString(String str) {
if (str == null)
return false;
int length = str.length();
for (int i = 0; i < length / 2; i++) {
if (str.charAt(i) != str.charAt(length - 1- i))
return false;
}
return true;
}
4、如何讓一個(gè)字符串變成小寫或大寫形式?
String提供兩個(gè)方法,toUpperCase 和 toLowerCase 方法,可以讓一個(gè)字符串全部變?yōu)?大寫或小寫。
5、如何比較兩個(gè)字符串的大???
在String類當(dāng)中,有兩個(gè)比較方法,實(shí)現(xiàn)了Comparable接口:
直接比較:compareTo(String anotherString) ,如果小于傳入的字符串返回負(fù)數(shù),如果大于則返回正數(shù)。當(dāng)兩個(gè)字符串值相等時(shí),返回0。
忽略大小寫的比較:compareToIgnoreCase(String str)。該方法與compareTo方法類似,區(qū)別只是內(nèi)部利用了Character.toUpperCase等方法進(jìn)行了大小寫轉(zhuǎn)換后進(jìn)行比較。
6、如何將String轉(zhuǎn)換為char,反過(guò)來(lái)呢?
1)、String轉(zhuǎn)換成char
這是一個(gè)誤導(dǎo)題,String是一系列字符,所以沒有辦法直接轉(zhuǎn)換成一個(gè)單一的char,這里應(yīng)該說(shuō)的是轉(zhuǎn)換為字符串?dāng)?shù)組,我們可以通過(guò)調(diào)用toCharArray() 方法將字符串轉(zhuǎn)成字符數(shù)組。
String str = "Hello world!";
char[] chars = str.toCharArray();
System.out.println(chars.length);
2)、char轉(zhuǎn)換成string:
char轉(zhuǎn)換成string有6種方法:
①. String s = String.valueOf('a'); //效率最高的方法
②. String s = String.valueOf(new char[]{'a'}); //將一個(gè)char數(shù)組轉(zhuǎn)換成String
③. String s = Character.toString('a');
// Character.toString(char)方法實(shí)際上直接返回String.valueOf(char)
④. String s = new Character('a').toString();
⑤. String s = 'a'+"";
// 雖然這個(gè)方法很簡(jiǎn)單,但這是效率最低的方法
// Java中的String Object的值實(shí)際上是不可變的,是一個(gè)final的變量。
// 所以我們每次對(duì)String做出任何改變,都是初始化了一個(gè)全新的String Object并將原來(lái)的變量指向了這個(gè)新String。
// 而Java對(duì)使用+運(yùn)算符處理String相加進(jìn)行了方法重載。
// 字符串直接相加連接實(shí)際上調(diào)用了如下方法:
// new StringBuilder().append('c').append("").toString();
⑥. String s = new String(new char[]{'c'});
7、如何將String轉(zhuǎn)換為byte array,反過(guò)來(lái)呢?
1)、String轉(zhuǎn)換為byte array
使用String的getBytes()方法將String轉(zhuǎn)成byte數(shù)組
2)byte array轉(zhuǎn)換為String
使用String的構(gòu)造方法 new String(byte[] arr) 將byte數(shù)據(jù)轉(zhuǎn)為String。
public class StringToByteArray {
public static void main(String[] args) {
String str = "Hello World!";
byte[] b = str.getBytes();
// print the byte[] elements
for(int i=0;i<b.length;i++){
System.out.println(i);
}
}
}
public class ByteArrayToString {
public static void main(String[] args) {
byte[] b1 = { 'H', 'e', 'l', 'l', '0' };
byte[] b2 = { 72, 101, 108, 108, 111};
String str1 = new String(b1);
String str2 = new String(b2);
System.out.println(str1);
System.out.println(str2);
}
}
8、淺談一下String, StringBuffer,StringBuilder的區(qū)別?
String是不可變類,每當(dāng)我們對(duì)String進(jìn)行操作的時(shí)候,如果字符串常量池沒有該字符串,就會(huì)創(chuàng)建新的字符串。操作String很耗資源,所以Java提供了兩個(gè)工具類來(lái)操作String,StringBuffer和StringBuilder。
StringBuffer和StringBuilder是可變類,StringBuffer是線程安全的,StringBuilder則不是線程安全的。所以在多線程對(duì)同一個(gè)字符串操作的時(shí)候,我們應(yīng)該選擇用StringBuffer。由于不需要處理多線程的情況,StringBuilder的效率比StringBuffer高。
9、String是不可變的有什么好處?
1)、由于String是不可變類,所以在多線程中使用是安全的。
2)、String是不可變的,它的值也不能被改變,所以用來(lái)存儲(chǔ)數(shù)據(jù)密碼很安全。
3)、 因?yàn)閖ava字符串是不可變的,可以在java運(yùn)行時(shí)節(jié)省大量java堆空間。因?yàn)椴煌淖址兞靠梢砸贸刂械南嗤淖址?。如果字符串是可變的,那么任何一個(gè)變量的值改變,就會(huì)反射到其他變量,所有引用該字符串對(duì)象的變量都會(huì)產(chǎn)生變化,字符串池也就沒有任何意義了。
10、如何分割一個(gè)String?
String類中自帶兩種實(shí)現(xiàn)方法:
1)、public String[] split(String regex):
根據(jù)傳入的正則字符串進(jìn)行分割,注意,如果最后一位剛好有傳入的字符,返回?cái)?shù)組最后一位不會(huì)有空字符串。
String str = "vrevdfsdsl";
System.out.println(Arrays.toString(str.split("s")));
//以上代碼輸出為 [vrevdf, d, l].
2)、 public String[] split(String regex, int limit):
限制分割結(jié)果數(shù)組中有幾個(gè)字符串。傳入2,則結(jié)果分割后數(shù)組長(zhǎng)度為2。
String s = "vfe,vef,ve,we";
String[] data = s.split(",", 2);
for(int i=0;i<data.length;i++){
System.out.println(i);
}
實(shí)際上第一個(gè)方法調(diào)用了第二個(gè)方法,只是沒有限制數(shù)組長(zhǎng)度。
public String[] split(String regex) {
return split(regex, 0);
}
11、如何判斷兩個(gè)String是否相等?
有兩種方式判斷字符串是否相等,使用"=="或者使用equals方法。
1)、當(dāng)使用"=="操作符時(shí),不僅比較字符串的值,還會(huì)比較引用的內(nèi)存地址。
2)、equals方法,只判斷值是否相等,大多數(shù)時(shí)候也就是判斷這兩個(gè)值是否相等,所以使用這個(gè)方法比較多。
補(bǔ)充:還有一個(gè)equalsIgnoreCase可以用來(lái)忽略大小寫進(jìn)行比較。
String s1 = "abc";
String s2 = "abc";
String s3= new String("abc");
System.out.println("s1 == s2 ? "+(s1==s2)); //true
System.out.println("s1 == s3 ? "+(s1==s3)); //false
System.out.println("s1 equals s3 ? "+(s1.equals(s3))); //true
12、什么是字符串池?
顧名思義,字符串常量池就是用來(lái)存儲(chǔ)字符串的。它存在于Java 堆內(nèi)存。
下圖解釋了字符串池在java堆空間如何存在以及當(dāng)我們使用不同方式創(chuàng)建字符串時(shí)的情況。

以下是上圖的一個(gè)編程例子
public class StringPool {
public static void main(String[] args) {
String s1 = "aaa";
String s2 = "aaa";
String s3 = new String("aaa");
System.out.println("s1 == s2 :"+(s1==s2));//true
System.out.println("s1 == s3 :"+(s1==s3));//false
}
}
一些java題中,可能會(huì)問(wèn)一段代碼中有幾個(gè)對(duì)象被創(chuàng)建,例如:
String str = new String("aaa");
上面一行代碼將會(huì)創(chuàng)建1或2個(gè)字符串。如果在字符串常量池中已經(jīng)有一個(gè)字符串“aaa”,那么就只會(huì)創(chuàng)建一個(gè)“aaa”字符串。如果字符串常量池中沒有“aaa”,那么首先會(huì)在字符串池中創(chuàng)建字符串對(duì)象,然后才在堆內(nèi)存中創(chuàng)建String對(duì)象,這種情況就會(huì)創(chuàng)建2個(gè)對(duì)象了。
13、String的intern()方法
當(dāng)intern()方法被調(diào)用,如果字符串池中含有一個(gè)字符串和當(dāng)前調(diào)用方法的字符串的值相等,那么就會(huì)返回池中的字符串。如果池中沒有的話,則首先將當(dāng)前字符串加入到池中,然后返回引用。
14、String是線程安全的嗎?
String是是線程安全的,因?yàn)樗遣豢勺冾?,一旦?chuàng)建了String對(duì)象,我們就無(wú)法改變它的值。因此,它是線程安全的,可以安全地用于多線程環(huán)境中。
15、為什么我們?cè)谑褂肏ashMap的時(shí)候總是用String做key?
因?yàn)樽址遣豢勺兊模?dāng)創(chuàng)建字符串時(shí),它的它的hashcode被緩存下來(lái),不需要再次計(jì)算。因?yàn)镠ashMap內(nèi)部實(shí)現(xiàn)是通過(guò)key的hashcode來(lái)確定value的存儲(chǔ)位置,所以相比于其他對(duì)象更快。這也是為什么我們平時(shí)都使用String作為HashMap對(duì)象的key。
16、String編程題
1、下面的代碼輸出什么
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);
輸出:
false
2、下面的代碼輸出什么
String str1 = "abc";
StringBuffer str2 = new StringBuffer(str1);
System.out.println(str1.equals(str2));
輸出:
false,因?yàn)閟tr2不是String類型,String的equals方法進(jìn)行了類型判斷。
3、下面的代碼輸出什么
String str1 = "abc";
String str2 = new String("abc");
str2.intern();
System.out.println(str1 ==str2);
輸出:
false,intern()方法將返回從字符串池中的字符串對(duì)象的引用,但因?yàn)槲覀儧]有分配到str2,str2沒有變化,如果該第三行代碼為
str2 = str2.intern(),則輸入true。
4、下面的代碼將創(chuàng)建幾個(gè)字符串對(duì)象。
String str1 = new String("Hello");
String str2 = new String("Hello");
會(huì)創(chuàng)建3個(gè)對(duì)象。首先會(huì)在字符穿常量池創(chuàng)建一個(gè)對(duì)象,只是這個(gè)對(duì)象創(chuàng)建后就是一個(gè)常量“Hello”,不可以更改, 并且這個(gè)對(duì)象是放在串池里面的。然后在堆內(nèi)存中創(chuàng)建一個(gè)對(duì)象str1,指向常量池中的“Hello”,最后又創(chuàng)建一個(gè)對(duì)象str2,指向常量池中的“Hello”,這里“hello”字符串池中的字符串被重用。