關於Java 字符串的全部,都在這份手冊裡了

2020-12-11 CSDN

作者 | 沉默王二

責編 | 屠敏

頭圖 | CSDN 下載自視覺中國

String 可以說是 Java 中最常見的數據類型,用來表示一串文本,它的使用頻率非常高,為了小夥伴們著想,我怒肝了一周,把字符串能寫的全都寫了出來。

來看一下腦圖吧,感受一下這份手冊涉及到的知識點,不是我吹,有了這份手冊,字符串的相關知識可以說全部掌握了。

多行字符串

每個作業系統對換行符的定義都不盡相同,所以在拼接多行字符串之前,需要先獲取到作業系統的換行符,Java 可以通過下面的方式獲取:

String newLine = System.getProperty("line.separator");

通過 System 類的 getProperty() 方法,帶上「line.separator」關鍵字就可以獲取到了。

有了換行符,就可以使用 String 類的 concat() 方法或者直接使用「+」號操作符拼接多行字符串了。

String mutiLine = "親愛的" .concat(newLine) .concat("我想你了") .concat(newLine) .concat("你呢?") .concat(newLine) .concat("有沒有在想我呢?");

String mutiLine1 = "親愛的"+ newLine + "你好幼稚啊" + newLine + "技術文章裡" + newLine + "你寫這些合適嗎";

Java 8 的 String 類加入了一個新的方法 join(),可以將換行符與字符串拼接起來,非常方便:

String mutiLine2 = String.join(newLine, "親愛的", "合適啊", "這叫趣味", "哈哈");

StringBuilder 當然也是合適的:

String mutiLine3 = new StringBuilder() .append("親愛的") .append(newLine) .append("看不下去了") .append(newLine) .append("肉麻") .toString();

StringBuffer 類似,就不再舉例了。另外,Java 還可以通過 Files.readAllBytes() 方法從源文件中直接讀取多行文本,格式和源文件保持一致:

String mutiLine4 = newString(Files.readAllBytes(Paths.get("src/main/resource/cmower.txt")));

檢查字符串是否為空

說到「空」這個概念,它在編程中有兩種定義,英文單詞分別是 empty 和 blank,來做一下區分。如果字符串為 null,或者長度為 0,則為 empty;如果字符串僅包含空格,則為 blank。

01、empty

Java 1.6 之後,String 類新添加了一個 empty() 方法,用於判斷字符串是否為 empty。

booleanisEmpty(String str){return str.isEmpty();}

為了確保不拋出 NPE,最好在判斷之前先判空,因為 empty() 方法只判斷了字符串的長度是否為 0:

所以我們來優化一下 isEmpty() 方法:

booleanisEmpty(String str){return str != null || str.isEmpty();}

02、blank

如果想檢查字符串是否為 blank,有一種變通的做法,就是先通過 String 類的 trim() 方法去掉字符串兩側的空白字符,然後再判斷是否為 empty:

booleanisBlank(String str){return str != null || str.trim().isEmpty();}

03、第三方類庫

在實際的項目開發當中,檢查字符串是否為空最常用的還是 Apache 的 commons-lang3 包,有各式各樣判空的方法。

更重要的是,可以省卻判 null 的操作,因為 StringUtils 的所有方法都是 null 安全的。

生成隨機字符串

有時候,我們需要生成一些隨機的字符串,比如說密碼。

int leftLimit = 97; // 'a'int rightLimit = 122; // 'z'int targetStringLength = 6;Random random = new Random();StringBuilder buffer = new StringBuilder(targetStringLength);for (int i = 0; i < targetStringLength; i++) {int randomLimitedInt = leftLimit + (int) (random.nextFloat() * (rightLimit - leftLimit + 1)); buffer.append((char) randomLimitedInt);}String generatedString = buffer.toString();System.out.println(generatedString);

這段代碼就會生成一串 6 位的隨機字符串,範圍是小寫字母 a - z 之間。

除了使用 JDK 原生的類庫之外,還可以使用 Apache 的 Commons Lang 包,RandomStringUtils.random() 方法剛好滿足需求:

int length = 6;boolean useLetters = true;// 不使用數字boolean useNumbers = false;String generatedString = RandomStringUtils.random(length, useLetters, useNumbers);System.out.println(generatedString);

刪除字符串最後一個字符

刪除字符串最後一個字符,最簡單的方法就是使用 substring() 方法進行截取,0 作為起始下標,length() - 1 作為結束下標。

不管怎麼樣,substring() 方法不是 null 安全的,需要先判空:

publicstatic String removeLastChar(String s) {return (s == null || s.length() == 0) ? null : (s.substring(0, s.length() - 1)); }

如果不想在操作之前判空,那麼就直接上 Apache 的 Commons Lang 包:

String s = "沉默王二";StringUtils.substring(s, 0, s.length() - 1);

當然了,如果目的非常明確——就是只刪除字符串的最後一個字符,還可以使用 StringUtils 類的 chop() 方法:

StringUtils.chop(s);

如果你看過源碼的話,你就會發現,它內部其實也是調用了 substring()方法。publicstatic String chop(final String str){if (str == null) {returnnull; }finalint strLen = str.length();if (strLen < 2) {return EMPTY; }finalint lastIdx = strLen - 1;final String ret = str.substring(0, lastIdx);finalchar last = str.charAt(lastIdx);if (last == CharUtils.LF && ret.charAt(lastIdx - 1) == CharUtils.CR) {return ret.substring(0, lastIdx - 1); }return ret;}

如果你對正則表達式了解的話,也可以使用 replaceAll() 方法進行替換,把最後一個字符 .$ 替換成空字符串就可以了。

s.replaceAll(".$", "")

當然了,replaceAll() 方法也不是 null 安全的,所以要提前判空:

String result= (s == null) ? null : s.replaceAll(".$", "");

如果對 Java 8 的 Lambda 表達式和 Optional 比較熟的話,還可以這樣寫:

String result1 = Optional.ofNullable(s) .map(str -> str.replaceAll(".$", "")) .orElse(s);

看起來就顯得高大上多了,一看就是有經驗的 Java 程式設計師。

統計字符在字符串中出現的次數

要統計字符在字符串中出現的次數,有很多方法,直接使用 JDK 的 API 就是最直接的一種:

String someString = "chenmowanger";char someChar = 'e';int count = 0;for (int i = 0; i < someString.length(); i++) {if (someString.charAt(i) == someChar) { count++; }}System.out.println(count);

這種方式很直白,但有沒有更優雅的呢?有,Java 8 就優雅多了:

long count = someString.chars().filter(ch -> ch == 'e').count();

如果想使用第三方類庫的話,可以繼續選擇 Apache 的 Commons Lang 包:

int count2 = StringUtils.countMatches("chenmowanger", "e");

也非常優雅,很容易看得懂。

拆分字符串

大多數情況下,String 類的 split() 方法就能夠滿足拆分字符串的需求:

String[] splitted = "沉默王二,一枚有趣的程式設計師".split(",");

當然了,該方法也不是 null 安全的,那想要 null 安全,小夥伴們應該能想到誰了吧?

之前反覆提到的 StringUtils 類,來自 Apache 的 Commons Lang 包:

String[] splitted = StringUtils.split("沉默王二,一枚有趣的程式設計師", ",");

字符串比較

對於初學者來說,最容易犯的錯誤就是使用「==」操作符來判斷兩個字符串的值是否相等,這也是一道很常見的面試題。

String string1 = "沉默王二";String string2 = "沉默王二";String string3 = newString("沉默王二");System.out.println(string1 == string2);System.out.println(string1 == string3);

這段程序的第一個結果是 true,第二個結果為 false,這是因為使用 new 關鍵字創建的對象和使用雙引號聲明的字符串不是同一個對象,而「==」 操作符是用來判斷對象是否相等的。

如果單純的比較兩個字符串的值是否相等,應該使用 equals() 方法:

String string1 = "沉默王二";String string2 = "沉默王二";String string3 = newString("沉默王二");System.out.println(string1.equals(string2));System.out.println(string1.equals(string3));

這段程序輸出的結果就是兩個 true,因為 equals() 方法就是用來單純的判斷字符串的值是否相等。

字符串拼接

01、「+」號操作符

要說姿勢,「+」號操作符必須是字符串拼接最常用的一種了,沒有之一。

String chenmo = "沉默";String wanger = "王二";System.out.println(chenmo + wanger);

我們把這段代碼使用 JAD 反編譯一下。

String chenmo = "\u6C89\u9ED8"; // 沉默String wanger = "\u738B\u4E8C"; // 王二System.out.println((new StringBuilder(String.valueOf(chenmo))).append(wanger).toString());

我去,原來編譯的時候把「+」號操作符替換成了 StringBuilder 的 append 方法。也就是說,「+」號操作符在拼接字符串的時候只是一種形式主義,讓開發者使用起來比較簡便,代碼看起來比較簡潔,讀起來比較順暢。算是 Java 的一種語法糖吧。

02、StringBuilder

除去「+」號操作符,StringBuilder 的 append 方法就是第二個常用的字符串拼接姿勢了。

先來看一下 StringBuilder 類的 append 方法的源碼:

public StringBuilder append(String str) {super.append(str);returnthis;}

這 3 行代碼沒啥可看的,可看的是父類 AbstractStringBuilder 的 append 方法:

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;returnthis;}

1)判斷拼接的字符串是不是 null,如果是,當做字符串「null」來處理。appendNull 方法的源碼如下:

private AbstractStringBuilder appendNull() {int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l'; count = c;returnthis;}

2)拼接後的字符數組長度是否超過當前值,如果超過,進行擴容並複製。ensureCapacityInternal 方法的源碼如下:

privatevoidensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeif (minimumCapacity - value.length > 0) {value = Arrays.copyOf(value, newCapacity(minimumCapacity)); }}

3)將拼接的字符串 str 複製到目標數組 value 中。

str.getChars(0, len, value, count)

03、StringBuffer

先有 StringBuffer 後有 StringBuilder,兩者就像是孿生雙胞胎,該有的都有,只不過大哥 StringBuffer 因為多呼吸兩口新鮮空氣,所以是線程安全的。

publicsynchronized StringBuffer append(String str){ toStringCache = null;super.append(str);returnthis;}

StringBuffer 類的 append 方法比 StringBuilder 多了一個關鍵字 synchronized,可暫時忽略 toStringCache = null。

synchronized 是 Java 中的一個非常容易臉熟的關鍵字,是一種同步鎖。它修飾的方法被稱為同步方法,是線程安全的。

04、String 類的 concat 方法

單就姿勢上來看,String 類的 concat 方法就好像 StringBuilder 類的 append。

String chenmo = "沉默";String wanger = "王二";System.out.println(chenmo.concat(wanger));

文章寫到這的時候,我突然產生了一個奇妙的想法。假如有這樣兩行代碼:

chenmo += wangerchenmo = chenmo.concat(wanger)

它們之間究竟有多大的差別呢?

之前我們已經了解到,chenmo += wanger 實際上相當於 (new StringBuilder(String.valueOf(chenmo))).append(wanger).toString()。

要探究「+」號操作符和 concat 之間的差別,實際上要看 append 方法和 concat 方法之間的差別。

append 方法的源碼之前分析過了。我們就來看一下 concat 方法的源碼吧。

public String concat(String str) {int otherLen = str.length();if (otherLen == 0) {returnthis; }int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len);returnnew String(buf, true);}

1)如果拼接的字符串的長度為 0,那麼返回拼接前的字符串。

if (otherLen == 0) {returnthis;}

2)將原字符串的字符數組 value 複製到變量 buf 數組中。

char buf[] = Arrays.copyOf(value, len + otherLen);

3)把拼接的字符串 str 複製到字符數組 buf 中,並返回新的字符串對象。

str.getChars(buf, len);returnnewString(buf, true);

通過源碼分析我們大致可以得出以下結論:

1)如果拼接的字符串是 null,concat 時候就會拋出 NullPointerException,「+」號操作符會當做是「null」字符串來處理。

2)如果拼接的字符串是一個空字符串(""),那麼 concat 的效率要更高一點。畢竟不需要 new StringBuilder 對象。

3)如果拼接的字符串非常多,concat 的效率就會下降,因為創建的字符串對象越多,開銷就越大。

注意了!!!

弱弱地問一下啊,還有在用 JSP 的同學嗎?EL 表達式中是不允許使用「+」操作符來拼接字符串的,這時候就只能用 concat 了。

${chenmo.concat('-').concat(wanger)}

05、String 類的 join 方法

JDK 1.8 提供了一種新的字符串拼接姿勢:String 類增加了一個靜態方法 join。

String chenmo = "沉默";String wanger = "王二";String cmower = String.join("", chenmo, wanger);System.out.println(cmower);

第一個參數為字符串連接符,比如說:

String message = String.join("-", "王二", "太特麼", "有趣了");

輸出結果為:王二-太特麼-有趣了

我們來看一下 join 方法的源碼:

publicstatic String join(CharSequence delimiter, CharSequence... elements) { Objects.requireNonNull(delimiter); Objects.requireNonNull(elements);// Number of elements not likely worth Arrays.stream overhead. StringJoiner joiner = new StringJoiner(delimiter);for (CharSequence cs: elements) { joiner.add(cs); }return joiner.toString();}

發現了一個新類 StringJoiner,類名看起來很 6,讀起來也很順口。StringJoiner 是 java.util 包中的一個類,用於構造一個由分隔符重新連接的字符序列。限於篇幅,本文就不再做過多介紹了,感興趣的同學可以去了解一下。

06、StringUtils.join

實戰項目當中,我們處理字符串的時候,經常會用到這個類——org.apache.commons.lang3.StringUtils,該類的 join 方法是字符串拼接的一種新姿勢。

String chenmo = "沉默";String wanger = "王二";StringUtils.join(chenmo, wanger);

該方法更善於拼接數組中的字符串,並且不用擔心 NullPointerException。

StringUtils.join(null) = nullStringUtils.join([]) = ""StringUtils.join([null]) = ""StringUtils.join(["a", "b", "c"]) = "abc"StringUtils.join([null, "", "a"]) = "a"

通過查看源碼我們可以發現,其內部使用的仍然是 StringBuilder。

publicstatic String join(final Object[] array, String separator, final int startIndex, final int endIndex) {if (array == null) {returnnull; }if (separator == null) { separator = EMPTY; }final StringBuilder buf = new StringBuilder(noOfItems * 16);for (int i = startIndex; i < endIndex; i++) {if (i > startIndex) { buf.append(separator); }if (array[i] != null) { buf.append(array[i]); } }return buf.toString();}

大家讀到這,不約而同會有這樣一種感覺:我靠(音要拖長),沒想到啊沒想到,字符串拼接足足有 6 種姿勢啊,晚上回到家一定要一一嘗試下。

07、為什麼阿里開發手冊不建議在 for 循環中使用」+」號操作符進行字符串拼接

來看兩段代碼。

第一段,for 循環中使用」+」號操作符。

String result = "";for (int i = 0; i < 100000; i++) { result += "六六六";}

第二段,for 循環中使用 append。

StringBuilder sb = new StringBuilder();for (int i = 0; i < 100000; i++) { sb.append("六六六");}

這兩段代碼分別會耗時多長時間呢?在我的 iMac 上測試出的結果是:

1)第一段代碼執行完的時間為 6212 毫秒

2)第二段代碼執行完的時間為 1 毫秒

差距也太大了吧!為什麼呢?

我相信有不少同學已經有了自己的答案:第一段的 for 循環中創建了大量的 StringBuilder 對象,而第二段代碼至始至終只有一個 StringBuilder 對象。

相關焦點

  • Java中的字符串常用方法
    字符串是由多個字符組成的一串數據String的特點:字符串是常量,一旦被創建就不能改變,這是因為字符串的值是存放在方法區的常量池裡面,但是引用可以改變。public boolean contains(String str): 判斷字符串中是否包含傳遞進來的字符串public boolean startsWith(String str): 判斷字符串是否以傳遞進來的字符串開頭public boolean endsWith(String str): 判斷字符串是否以傳遞進來的字符串結尾
  • Java基礎學習:一篇文章讓你搞懂Java字符串的前世今生
    中的 char 字符都是以 unicode 編碼的,從外界不同的編碼(如 gbk,utf-8)傳過來的 byte[] 最終到 java 中的 char 都統一了1.4 int[] 數組創建有時候我們還需要用兩個 char 表示一個字符,比如 這個笑哭的字符,它用 unicode 編碼表示為 0x1F602,存儲範圍已經超過了
  • Java字符串拼接效率分析及最佳實踐
    java連接字符串有多種方式,比如+操作符,StringBuilder.append方法,這些方法各有什麼優劣(可以適當說明各種方式的實現細節)? 按照高效的原則,那麼java中字符串連接的最佳實踐是什麼? 有關字符串處理,都有哪些其他的最佳實踐?
  • 驚,Java 字符串拼接竟然有這麼多玩法!|CSDN 原力計劃
    作者 | 沉默王二責編 | 屠敏出品 | CSDN 博客二哥,我今年大二,看你分享的《阿里巴巴 Java 開發手冊》上有一段內容說:「循環體內,拼接字符串最好使用 StringBuilder 的 append 方法,而不是 + 號操作符。」
  • 關於Java裡面的字符串拼接,你了解多少?
    關於Java裡面的字符串拼接,你了解多少?前言字符串拼接是我們日常開發中很常見的操作,雖然常見,但要是使用不當的的話,很有可能讓你的程序處理效率降低一大半,所以我們有必要來重新了解一下Java裡面的字符串操作。
  • 字符串比較和大小寫轉換
    字符串的比較兩個字符串對象除了判斷相等外,還可以比較字符串。String類提供了compareTo()方法比較字符串的大小,compareTo()方法按照字典順序比較兩個字符串。比較大小有三種結果:如果兩個字符串相等,返回0;如果當前字符串按照字典順序位於待比較的字符串之前返回一個負整數;如果當前字符串按照字典順序位於待比較的字符串之前返回一個正整數。
  • 跟我學java編程—Java的Scanner類
    語句「import java.util.*;」,用於導入包含Scanner的類庫,Scanner包含在Java 核心類庫util中,要使用Scanner類,必須導入java.util庫,類庫導入關鍵字為import。
  • Java中 休眠(sleep)
    Hi,大家好久不見,今天我們在這裡給大家介紹一下關於Java的小知識,在Java中 休眠(sleep),至於運用呢就不和大家做介紹了;接下來就給大家詳細介紹一下如何實現。那我們該如何創建使用呢?這只是作者用的哦,還有很多就不做具體介紹了),然後點擊File --> new -->Javaproject,然後給自己的Javaproject取一個名字,我們在這裡取名為GetTime,然後打開新建一個package名為sleep的package,然後在package中新建一個名為sleepTime的class,如圖:接下來就開始進行代碼實現,全部代碼如下
  • java常用幾大類庫
    在輸入框裡輸入,然後回車。 看包。java.lang下的類不需要導包,其他需要。 看類的解釋和說明。 學習構造方法。 使用成員方法。類代表字符串。Java程序中所有的字符串文字(例如"abc")都可以被看作是實現此類的實例。類 String 中包括用於檢查各個字符串的方法,比如用於比較字符串,搜索字符串,提取子字符串以及創建具有翻譯為大寫或小寫的所有字符的字符串的副本。特點1.
  • Java字符串地查找操作
    在一個字符串中查找字符或子串是經常使用的操作。String類提供了兩種查找字符串的方法,分別是indexOf()和lastIndexOf(),這兩種方法都返回待查找字符或子串在字符串的起始索引位置。調用語法如下:str.indexOf(s)其中,str是已創建的字符串對象,s待查找的字符串。
  • Java技能應用之JSON工具包的使用
    因此,Java培訓[1]成為很多想要從事java開發人士的選擇。愛尚小課堂2018年第一期開課啦!2、在控制層的方法中創建JSONObject對象//創建JSONObject對象 JSONObject json = new JSONObject();//創建字符串
  • java SE基礎筆記之string用法
    文檔注釋:/** */-----寫在類和方法及常量(3個地方)的開頭,可以通過javadoc工具,轉換為HTML文檔生成文檔:項目名右鍵---Export----java----javadoc---生成文檔string 是採用了
  • java之Scanner類的簡單介紹
    另外,只有java.lang包下的內容不需要導包,其他的包都需要import語句。package dayone;import java.util.Scanner;//1.導包public class DemoScanner {public static void main(String args[]){//2.
  • Java之包裝類中基本數據類型與字符串類型之間的相互轉換
    Java基本數據類型的簡單介紹與字符串類型之間的相互轉換。基本類型與字符串類型之間的相互轉換:基本類型->字符串(String)1.基本類型的值+"",最簡單的方法,工作中常用2.包裝類的靜態方法toString(參數),不是Object類的toString()重載static
  • java.util.Scanner的幾種next方法
    java.util.Scanner,這個類,想必大家都不怎麼陌生,在初學Java這門程式語言時,都見過,使用過吧。今天就來說說java.util.Scanner類的幾種next方法。scanner.nextInt()方法上面的兩個示例代碼都是獲取輸入的字符串,那如果是要獲取數值呢。看看下面的這段示例代碼。
  • java如何快速入門?
    下面是一些可能幫助您成長為Java開發人員並獲得更多關於該語言的知識的技巧。相信我,Java是一種程式語言,如果你集中Java的一個知識面首先去專注學習,那麼學習Java還是挺容易的;但是,如果你想一下子把Java的各個應用領域知識都去接觸,試圖花最短的時間去學習完Java,那麼最後的結果可能會令你失望。
  • Java String、StringBuffer和StringBuilder 三劍客
    今天主要說說 Java中的 String、StringBuffer和StringBuilder ,都是我們經常使用的,可能有些你只是使用,並不知道他好在哪裡,或者有些你現在的使用是錯誤的。
  • Java之 Scanner類
    Scanner 類java.util.Scanner 是 Java5 的新特徵,我們可以通過 Scanner 類來獲取用戶的輸入,並通過 Scanner 類的 next() 與 nextLine() 方法獲取輸入的字符串,在讀取前我們一般需要 使用 hasNext 與 hasNextLine 判斷是否還有輸入的數據。
  • Java字符串替換( )replaceFirst( )&replaceAll(),你學會了嗎?
    Java字符串有三種類型的替換方法取代替換所有取代第一。在這些幫助下,您可以替換字符串中的字符。(新的新角色)返回值此函數通過用newch替換oldCh來返回字符串。用「a」代替「t」之後的字符串:ahe quick fox jumped(一隻敏捷的狐狸跳了起來)2.Java String Replaceall( )描述java String replaceAll()方法返回一個字符串,替換匹配正則表達式和替換字符串的所有字符序列