咱們經常聽人說,HashMap 的 key 建議使用不可變類,好比說 String 這種不可變類。這裏說的不可變指的是類值一旦被初始化,就不能再被改變了,若是被修改,將會是新的類,咱們寫個 demo 來演示一下。java
String s ="hello"; s ="world";
從代碼上來看,s 的值好像被修改了,但從 debug 的日誌來看,實際上是 s 的內存地址已經被修改了,也就說 s =「world」 這個看似簡單的賦值,其實已經把 s 的引用指向了新的 String,debug 的截圖顯示內存地址已經被修改,兩張截圖以下:面試
圖片描述圖片描述咱們從源碼上查看一下緣由:segmentfault
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }
咱們能夠看出來兩點:數組
以上兩點就是 String 不變性的緣由,充分利用了 final 關鍵字的特性,若是你自定義類時,但願也是不可變的,也能夠模仿 String 的這兩點操做。緩存
由於 String 具備不變性,因此 String 的大多數操做方法,都會返回新的 String,以下面這種寫法是不對的:數據結構
String str ="hello world !!"; // 這種寫法是替換不掉的 str.replace("l","dd"); // 必須接受 replace 方法返回的參數才行 str = str.replace("l","dd");
在生活中,咱們常常碰到這樣的場景,進行二進制轉化操做時,本地測試的都沒有問題,到其它環境機器上時,有時會出現字符串亂碼的狀況,這個主要是由於在二進制轉化操做時,並無強制規定文件編碼,而不一樣的環境默認的文件編碼不一致致使的。app
咱們也寫了一個 demo 來模仿一下字符串亂碼:工具
String str ="nihao 你好 喬亂"; // 字符串轉化成 byte 數組 byte[] bytes = str.getBytes("ISO-8859-1"); // byte 數組轉化成字符串 String s2 = new String(bytes); log.info(s2); // 結果打印爲: nihao ?? ??
打印的結果爲??,這就是常見的亂碼錶現形式。這時候有同窗說,是否是我把代碼修改爲 String s2 = new String(bytes,"ISO-8859-1"); 就能夠了?測試
這是不行的。主要是由於 ISO-8859-1 這種編碼對中文的支持有限,致使中文會顯示亂碼。惟一的解決辦法,就是在全部須要用到編碼的地方,都統一使用 UTF-8,對於 String 來講,getBytes 和 new String 兩個方法都會使用到編碼,咱們把這兩處的編碼替換成 UTF-8 後,打印出的結果就正常了。this
若是咱們的項目被 Spring 託管的話,有時候咱們會經過 applicationContext.getBean(className); 這種方式獲得 SpringBean,這時 className 必須是要知足首字母小寫的,除了該場景,在反射場景下面,咱們也常常要使類屬性的首字母小寫,這時候咱們通常都會這麼作:
name.substring(0, 1).toLowerCase() + name.substring(1);
使用 substring 方法,該方法主要是爲了截取字符串連續的一部分,substring 有兩個方法:
// beginIndex:開始位置,endIndex:結束位置; public String substring(int beginIndex, int endIndex) // beginIndex:開始位置,結束位置爲文本末尾。 public String substring(int beginIndex)
substring 方法的底層使用的是字符數組範圍截取的方法 :Arrays.copyOfRange(字符數組, 開始位置, 結束位置); 從字符數組中進行一段範圍的拷貝。
相反的,若是要修改爲首字母大寫,只須要修改爲 name.substring(0, 1).toUpperCase() + name.substring(1) 便可。
咱們判斷相等有兩種辦法,equals 和 equalsIgnoreCase。後者判斷相等時,會忽略大小寫,近期看見一些面試題在問:若是讓你寫判斷兩個 String 相等的邏輯,應該如何寫,咱們來一塊兒看下 equals 的源碼,整理一下思路:
public boolean equals(Object anObject) { // 判斷內存地址是否相同 if (this == anObject) { return true; } // 待比較的對象是不是 String,若是不是 String,直接返回不相等 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 的源碼能夠看出,邏輯很是清晰,徹底是根據 String 底層的結構來編寫出相等的代碼。這也提供了一種思路給咱們:若是有人問如何判斷二者是否相等時,咱們能夠從二者的底層結構出發,這樣能夠迅速想到一種貼合實際的思路和方法,就像 String 底層的數據結構是 char 的數組同樣,判斷相等時,就挨個比較 char 數組中的字符是否相等便可。
替換在工做中也常用,有 replace 替換全部字符、replaceAll 批量替換字符串、replaceFirst 替換遇到的第一個字符串三種場景。
其中在使用 replace 時須要注意,replace 有兩個方法,一個入參是 char,一個入參是 String,前者表示替換全部字符,如:name.replace('a','b'),後者表示替換全部字符串,如:name.replace("a","b"),二者就是單引號和多引號的區別。
須要注意的是, replace 並不僅是替換一個,是替換全部匹配到的字符或字符串哦。
寫了一個 demo 演示一下三種場景:
public void testReplace(){ String str ="hello word !!"; log.info("替換以前 :{}",str); str = str.replace('l','d'); log.info("替換全部字符 :{}",str); str = str.replaceAll("d","l"); log.info("替換所有 :{}",str); str = str.replaceFirst("l",""); log.info("替換第一個 l :{}",str); } //輸出的結果是: 替換以前 :hello word !! 替換全部字符 :heddo word !! 替換所有 :hello worl !! 替換第一個 :helo worl !!
固然咱們想要刪除某些字符,也可使用 replace 方法,把想刪除的字符替換成 「」 便可。
拆分咱們使用 split 方法,該方法有兩個入參數。第一個參數是咱們拆分的標準字符,第二個參數是一個 int 值,叫 limit,來限制咱們須要拆分紅幾個元素。若是 limit 比實際能拆分的個數小,按照 limit 的個數進行拆分,咱們演示一個 demo:
String s ="boo:and:foo"; // 咱們對 s 進行了各類拆分,演示的代碼和結果是: s.split(":") 結果:["boo","and","foo"] s.split(":",2) 結果:["boo","and:foo"] s.split(":",5) 結果:["boo","and","foo"] s.split(":",-2) 結果:["boo","and","foo"] s.split("o") 結果:["b","",":and:f"] s.split("o",2) 結果:["b","o:and:foo"]
從演示的結果來看,limit 對拆分的結果,是具備限制做用的,還有就是拆分結果裏面不會出現被拆分的字段。
那若是字符串裏面有一些空值呢,拆分的結果以下:
String a =",a,,b,"; a.split(",") 結果:["","a","","b"]
從拆分結果中,咱們能夠看到,空值是拆分不掉的,仍然成爲結果數組的一員,若是咱們想刪除空值,只能本身拿到結果後再作操做,但 Guava(Google 開源的技術工具) 提供了一些可靠的工具類,能夠幫助咱們快速去掉空值,以下:
String a =",a, , b c ,"; // Splitter 是 Guava 提供的 API List<String> list = Splitter.on(',') .trimResults()// 去掉空格 .omitEmptyStrings()// 去掉空值 .splitToList(a); log.info("Guava 去掉空格的分割方法:{}",JSON.toJSONString(list)); // 打印出的結果爲: ["a","b c"]
從打印的結果中,能夠看到去掉了空格和空值,這正是咱們工做中經常指望的結果,因此推薦使用 Guava 的 API 對字符串進行分割。
合併咱們使用 join 方法,此方法是靜態的,咱們能夠直接使用。方法有兩個入參,參數一是合併的分隔符,參數二是合併的數據源,數據源支持數組和 List,在使用的時候,咱們發現有兩個不太方便的地方:
而 Guava 正好提供了 API,解決上述問題,咱們來演示一下:
// 依次 join 多個字符串,Joiner 是 Guava 提供的 API Joiner joiner = Joiner.on(",").skipNulls(); String result = joiner.join("hello",null,"china"); log.info("依次 join 多個字符串:{}",result); List<String> list = Lists.newArrayList(new String[]{"hello","china",null}); log.info("自動刪除 list 中空值:{}",joiner.join(list)); // 輸出的結果爲; 依次 join 多個字符串:hello,china 自動刪除 list 中空值:hello,china
從結果中,咱們能夠看到 Guava 不只僅支持多個字符串的合併,還幫助咱們去掉了 List 中的空值,這就是咱們在工做中經常須要獲得的結果。
Long 最被咱們關注的就是 Long 的緩存問題,Long 本身實現了一種緩存機制,緩存了從 -128 到 127 內的全部 Long 值,若是是這個範圍內的 Long 值,就不會初始化,而是從緩存中拿,緩存初始化源碼以下:
private static class LongCache { private LongCache(){} // 緩存,範圍從 -128 到 127,+1 是由於有個 0 static final Long cache[] = new Long[-(-128) + 127 + 1]; // 容器初始化時,進行加載 static { // 緩存 Long 值,注意這裏是 i - 128 ,因此再拿的時候就須要 + 128 for(int i = 0; i < cache.length; i++) cache[i] = new Long(i - 128); } }
3.1 爲何使用 Long 時,你們推薦多使用 valueOf 方法,少使用 parseLong 方法
答:由於 Long 自己有緩存機制,緩存了 -128 到 127 範圍內的 Long,valueOf 方法會從緩存中去拿值,若是命中緩存,會減小資源的開銷,parseLong 方法就沒有這個機制。
3.2 如何解決 String 亂碼的問題
答:亂碼的問題的根源主要是兩個:字符集不支持複雜漢字、二進制進行轉化時字符集不匹配,因此在 String 亂碼時咱們能夠這麼作:
全部能夠指定字符集的地方強制指定字符集,好比 new String 和 getBytes 這兩個地方;
咱們應該使用 UTF-8 這種能完整支持複雜漢字的字符集。
3.3 爲何你們都說 String 是不可變的
答:主要是由於 String 和保存數據的 char 數組,都被 final 關鍵字所修飾,因此是不可變的,具體細節描述能夠參考上文。