你們好,我是沉默王二。java
不少初學編程的同窗,常常給我吐槽,說:「二哥,你在敲代碼的時候會不會有這樣一種感受,寫着寫着看不下去了,以爲本身寫出來的代碼就好像屎同樣?」git
這裏我必須得說一句,初入「江湖」的時候,確實會以爲本身的代碼寫得很爛,但這麼多年下來,這種感受已經蕩然無存了。程序員
(吹嘛,我也會,哈哈)github
那,怎麼才能讓寫出來的代碼不那麼爛呢?正則表達式
個人一個經驗就是,「拿來主義」,儘可能不去重複造輪子。使用那些已經被驗證過,足夠優質的開源庫不只可以讓咱們的代碼變得優雅,還可以讓咱們在不斷的使用過程中,學習到編程的精髓。編程
洋務運動的時候,有一句很響亮的口號叫作,「師夷長技以制夷」。先去用,再去學,天然而然就會變得牛逼。同窗們,大家說,是否是這個理?數組
我今天推薦的這款開源庫,名字叫作 strman-java,GitHub 上標星 1.3k,一款超讚的字符串處理工具庫,基於 Java 8,語法很是簡潔。app
接下來,咱們來看看怎麼用。dom
Maven 項目只須要在 pom.xml 文件中添加如下依賴便可。ide
<dependency> <groupId>com.shekhargulati</groupId> <artifactId>strman</artifactId> <version>0.4.0</version> </dependency>
好了,能夠肆無忌憚地調用 strman-java 的 API 了。我會在介紹的時候插入一些源碼的介紹,方便同窗們更深一步的學習,儘可能作到「知其然知其因此然」。
把可變字符串參數添加到指定的字符串尾部。
Strman.append("沉","默","王","二");
結果以下所示:
沉默王二
append 對應的方法是 prepend,把可變字符串參數前置到指定的字符串前面,使用方法以下。
Strman.prepend("沉","默","王","二");
結果以下所示:
默王二沉
把字符串數組添加到指定的字符串尾部。
String [] strs = {"默","王","二"}; Strman.appendArray("沉",strs);
結果以下所示:
沉默王二
append 內部其實調用的 appendArray,來看一下源碼:
public static String append(final String value, final String... appends) { return appendArray(value, appends); }
當使用可變參數的時候,其實是先建立了一個數組,該數組的大小就是可變參數的個數,而後將參數放入數組當中,再將數組傳遞給被調用的方法。
經過觀察反編譯後的字節碼,就能看獲得。
Strman.append("沉","默","王","二");
實際等同於:
Strman.append("沉", new String[]{"默", "王", "二"});
再來看一下 appendArray 方法的源碼:
public static String appendArray(final String value, final String[] appends) { StringJoiner joiner = new StringJoiner(""); for (String append : appends) { joiner.add(append); } return value + joiner.toString(); }
內部用的 StringJoiner,Java 8 時新增的一個類。構造方法有兩種。
第一種,指定分隔符:
public StringJoiner(CharSequence delimiter) { this(delimiter, "", ""); }
第二種,指定分隔符、前綴、後綴:
public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { this.prefix = prefix.toString(); this.delimiter = delimiter.toString(); this.suffix = suffix.toString(); }
雖然也能夠在 StringBuilder 類的幫助下在每一個字符串以後附加分隔符,但 StringJoiner 提供了更簡單的方法來實現,無需編寫大量的代碼。
獲取指定索引處上的字符。
Strman.at("沉默王二", 0); Strman.at("沉默王二", -1); Strman.at("沉默王二", 4);
結果以下所示:
Optional[沉] Optional[二] Optional.empty
也就是說,at 能夠處理 -(length-1)
到 (length-1)
以內的索引(當索引爲負數的時候將從末尾開始查找),若是超出這個範圍,將會返回 Optional.empty
,避免發生空指針。
來看一下源碼:
public static Optional<String> at(final String value, int index) { if (isNullOrEmpty(value)) { return Optional.empty(); } int length = value.length(); if (index < 0) { index = length + index; } return (index < length && index >= 0) ? Optional.of(String.valueOf(value.charAt(index))) : Optional.empty(); }
本質上,是經過 String 類的 charAt()
方法查找的,但包裹了一層 Optional,就巧妙地躲開了煩人的空指針。
Optional 是 Java 8 時新增的一個類,該類提供了一種用於表示可選值而非空引用的類級別解決方案。
按照指定起始字符和截止字符來返回一個字符串數組。
String [] results = Strman.between("[沉默王二][一枚有趣的程序員]","[", "]"); System.out.println(Arrays.toString(results));
結果以下所示:
[沉默王二, 一枚有趣的程序員]
來看一下源碼:
public static String[] between(final String value, final String start, final String end) { String[] parts = value.split(end); return Arrays.stream(parts).map(subPart -> subPart.substring(subPart.indexOf(start) + start.length())) .toArray(String[]::new); }
java.util.Arrays
類是爲數組而生的專用工具類,基本上常見的對數組的操做,Arrays 類都考慮到了,stream()
方法能夠將數組轉換成流:
String[] intro = new String[] { "沉", "默", "王", "二" }; Arrays.stream(intro);
Java 8 新增的 Stream 流在很大程度上提升了開發人員在操做集合(Collection)時的生產力。要想操做流,首先須要有一個數據源,能夠是數組或者集合。每次操做都會返回一個新的流對象,方便進行鏈式操做,但原有的流對象會保持不變。
map()
方法能夠把一個流中的元素轉化成一個新流中的元素,它能夠接收一個 Lambda 表達式做爲參數。Lambda 表達式描述了一個代碼塊(或者叫匿名方法),能夠將其做爲參數傳遞給構造方法或者普通方法以便後續執行。
考慮下面這段代碼:
() -> System.out.println("沉默王二")
來從左到右解釋一下,()
爲 Lambda 表達式的參數列表(本例中沒有參數),->
標識這串代碼爲 Lambda 表達式(也就是說,看到 ->
就知道這是 Lambda),System.out.println("沉默王二")
爲要執行的代碼,即將「沉默王二」打印到標準輸出流。
toArray()
方法能夠將流轉換成數組,你可能比較好奇的是 String[]::new
,它是什麼東東呢?來看一下 toArray()
方法的源碼。
<A> A[] toArray(IntFunction<A[]> generator);
也就是說 String[]::new
是一個 IntFunction,一個能夠產生所需的新數組的函數,能夠經過反編譯字節碼看看它究竟是什麼:
String[] strArray = (String[])list.stream().toArray((x$0) -> { return new String[x$0]; });
也就是至關於返回了一個指定長度的字符串數組。
返回組成字符串的單個字符的數組。
String [] results = Strman.chars("沉默王二"); System.out.println(Arrays.toString(results));
結果以下所示:
[沉, 默, 王, 二]
來看一下源碼:
public static String[] chars(final String value) { return value.split(""); }
內部是經過 String 類的 split()
方法實現的。
統計字符串中每一個字符出現的次數。
Map<Character, Long> map = Strman.charsCount("沉默王二的妹妹叫沉默王三"); System.out.println(map);
結果以下所示:
{的=1, 默=2, 三=1, 妹=2, 沉=2, 叫=1, 王=2, 二=1}
是否是瞬間以爲這個方法有意思多了,一步到位,統計出字符串中各個字符出現的次數,來看一下源碼吧。
public static Map<Character, Long> charsCount(String input) { return input.chars().mapToObj(c -> (char) c).collect(groupingBy(identity(), counting())); }
String 類的 chars()
方法是 Java 9 新增的,它返回一個針對基本類型 int 的流:IntStream。
mapToObj()
方法主要是將 Stream 中的元素進行裝箱操做, 轉換成一個引用類型的值, 它接收一個 IntFunction 接口, 它是一個 int -> R
的函數接口。
collect()
方法能夠把流轉成集合 Map。
用單個空格替換掉多個連續的空格。
Strman.collapseWhitespace("沉默王二 一枚有趣的程序員");
結果以下所示:
Strman.collapseWhitespace("沉默王二 一枚有趣的程序員")
來看一下源碼:
public static String collapseWhitespace(final String value) { return value.trim().replaceAll("\\s\\s+", " "); }
內部先用 trim()
方法去掉兩側的空格,而後再用正則表達式將多個連續的空格替換成單個空格。
驗證指定的字符串是否包含某個字符串。
System.out.println(Strman.contains("沉默王二", "沉")); System.out.println(Strman.contains("Abbc", "a", false));
結果以下所示:
true true
第三個參數 caseSensitive 是可選項,若是爲 false 則代表不區分大小寫。
來看一下源碼:
public static boolean contains(final String value, final String needle, final boolean caseSensitive) { if (caseSensitive) { return value.contains(needle); } return value.toLowerCase().contains(needle.toLowerCase()); }
內部經過 String 類的 contains()
方法實現,若是不區分大小寫,則先調用 toLowerCase()
方法轉成小寫。
驗證指定的字符串是否包含字符串數組中任意一個字符串,或更多。
System.out.println(Strman.containsAny("沉默王二", new String [] {"沉","三"})); System.out.println(Strman.containsAny("沉默王二", new String [] {"沉默","三"})); System.out.println(Strman.containsAny("沉默王二", new String [] {"不","三"}));
結果以下所示:
true true false
來看一下源碼:
public static boolean containsAny(final String value, final String[] needles, final boolean caseSensitive) { return Arrays.stream(needles).anyMatch(needle -> contains(value, needle, caseSensitive)); }
Stream 類提供了三個方法可供進行元素匹配,它們分別是:
anyMatch()
,只要有一個元素匹配傳入的條件,就返回 true。allMatch()
,只有有一個元素不匹配傳入的條件,就返回 false;若是所有匹配,則返回 true。noneMatch()
,只要有一個元素匹配傳入的條件,就返回 false;若是所有匹配,則返回 true。驗證字符串是否以某個字符串結尾。
System.out.println(Strman.endsWith("沉默王二","二")); System.out.println(Strman.endsWith("Abbc", "A", false));
結果以下所示:
true false
來看一下源碼:
public static boolean endsWith(final String value, final String search, final int position, final boolean caseSensitive) { int remainingLength = position - search.length(); if (caseSensitive) { return value.indexOf(search, remainingLength) > -1; } return value.toLowerCase().indexOf(search.toLowerCase(), remainingLength) > -1; }
內部經過 String 類的 indexOf()
方法實現。
確保字符串以某個字符串開頭,若是該字符串沒有以指定的字符串開頭,則追加上去。
System.out.println(Strman.ensureLeft("沉默王二", "沉")); System.out.println(Strman.ensureLeft("默王二", "沉"));
結果以下所示:
沉默王二 沉默王二
來看一下源碼:
public static String ensureLeft(final String value, final String prefix, final boolean caseSensitive) { if (caseSensitive) { return value.startsWith(prefix) ? value : prefix + value; } String _value = value.toLowerCase(); String _prefix = prefix.toLowerCase(); return _value.startsWith(_prefix) ? value : prefix + value; }
內部經過 String 類的 startsWith()
方法先進行判斷,若是結果爲 false,則經過「+」操做符進行鏈接。
ensureLeft 對應的還有 ensureRight,同理,這裏再也不贅述。
把字符串進行 base64 編碼。
Strman.base64Encode("沉默王二");
結果以下所示:
5rKJ6buY546L5LqM
Base64 是一種基於 64 個可打印字符來表示二進制數據的表示方法。來看一下源碼:
public static String base64Encode(final String value) { return Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8)); }
內部是經過 Base64 類實現的,Java 8 新增的一個類。
base64Encode 對應的解碼方法是 base64Decode,使用方法以下所示:
Strman.base64Decode("5rKJ6buY546L5LqM")
若是不可解碼的會,會拋出 IllegalArgumentException 異常。
Exception in thread "main" java.lang.IllegalArgumentException: Last unit does not have enough valid bits at java.base/java.util.Base64$Decoder.decode0(Base64.java:763) at java.base/java.util.Base64$Decoder.decode(Base64.java:535) at java.base/java.util.Base64$Decoder.decode(Base64.java:558) at strman.Strman.base64Decode(Strman.java:328) at com.itwanger.strman.Demo.main(Demo.java:58)
把字符串轉成二進制的 Unicode(16 位)。
Strman.binEncode("沉默王二");
結果以下所示:
0110110010001001100111101101100001110011100010110100111010001100
binEncode 對應的方法是 binDecode,把二進制的 Unicode 轉成字符串,使用方法以下所示:
Strman.binDecode("0110110010001001100111101101100001110011100010110100111010001100");
返回字符串的前 N 個字符。
System.out.println(Strman.first("沉默王二", 0)); System.out.println(Strman.first("沉默王二", 1)); System.out.println(Strman.first("沉默王二", 2));
結果以下所示:
Optional[] Optional[沉] Optional[沉默]
若是 N 爲負數的話,將會拋出 StringIndexOutOfBoundsException 異常:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end -1, length 4 at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3319) at java.base/java.lang.String.substring(String.java:1874) at strman.Strman.lambda$first$9(Strman.java:414) at java.base/java.util.Optional.map(Optional.java:265) at strman.Strman.first(Strman.java:414) at com.itwanger.strman.Demo.main(Demo.java:68)
針對 N 爲負數的狀況,我以爲沒有以前的 at 方法處理的巧妙。
來看一下源碼:
public static Optional<String> first(final String value, final int n) { return Optional.ofNullable(value).filter(v -> !v.isEmpty()).map(v -> v.substring(0, n)); }
內部是經過 String 類的 substring()
方法實現的,不過沒有針對 n 小於 0 的狀況作處理。
ofNullable()
方法能夠建立一個便可空又可非空的 Optional 對象。
filter()
方法的參數類型爲 Predicate(Java 8 新增的一個函數式接口),也就是說能夠將一個 Lambda 表達式傳遞給該方法做爲條件,若是表達式的結果爲 false,則返回一個 EMPTY 的 Optional 對象,不然返回過濾後的 Optional 對象。
map()
方法能夠按照必定的規則將原有 Optional 對象轉換爲一個新的 Optional 對象,原有的 Optional 對象不會更改。
first 對應的的是 last 方法,返回字符串的後 N 個字符。
返回字符串的第一個字符。
Strman.head("沉默王二");
結果以下所示:
Optional[沉]
來看一下源碼:
public static Optional<String> head(final String value) { return first(value, 1); }
內部是經過調用 first()
方法實現的,只不過 N 爲 1。
檢查兩個字符串是否不等。
Strman.unequal("沉默王二","沉默王三");
結果以下所示:
true
來看一下源碼:
public static boolean unequal(final String first, final String second) { return !Objects.equals(first, second); }
內部是經過 Objects.equals()
方法進行判斷的,因爲 String 類重寫了 equals()
方法,也就是說,實際上仍是經過 String 類的 equals()
方法進行判斷的。
把字符串插入到指定索引處。
Strman.insert("沉默二","王",2);
結果以下所示:
沉默王二
來看一下源碼:
public static String insert(final String value, final String substr, final int index) { if (index > value.length()) { return value; } return append(value.substring(0, index), substr, value.substring(index)); }
若是索引超出字符串長度,直接返回原字符串;不然調用 append()
方法將指定字符串插入到對應索引處。
對字符串重複指定次數。
Strman.repeat("沉默王二", 3);
結果以下所示:
沉默王二沉默王二沉默王二
來看一下源碼:
public static String repeat(final String value, final int multiplier) { return Stream.generate(() -> value).limit(multiplier).collect(joining()); }
Stream.generate()
生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。因爲它是無限的,在管道中,必須利用 limit 之類的操做限制 Stream 大小。
collect(joining())
能夠將流轉成字符串。
返回給定長度的新字符串,以便填充字符串的開頭。
Strman.leftPad("王二","沉默",6);
結果以下所示:
沉默沉默沉默沉默王二
來看一下源碼:
public static String leftPad(final String value, final String pad, final int length) { if (value.length() > length) { return value; } return append(repeat(pad, length - value.length()), value); }
內部會先調用 repeat()
方法進行補位,而後再調用 append()
方法拼接。
leftPad 方法對應的是 rightPad,填充字符串的末尾。
19)removeEmptyStrings,從字符串數組中移除空字符串。
String [] results = Strman.removeEmptyStrings(new String[]{"沉", " ", " ", "默王二"}); System.out.println(Arrays.toString(results));
結果以下所示:
[沉, 默王二]
來看一下源碼:
public static String[] removeEmptyStrings(String[] strings) { if (Objects.isNull(strings)) { throw new IllegalArgumentException("Input array should not be null"); } return Arrays.stream(strings).filter(str -> str != null && !str.trim().isEmpty()).toArray(String[]::new); }
經過 Stream 的 filter()
方法過濾掉了空格。
反轉字符串。
Strman.reverse("沉默王二");
結果以下所示:
二王默沉
來看一下源碼:
public static String reverse(final String value) { return new StringBuilder(value).reverse().toString(); }
內部是經過 StringBuilder
類的 reverse()
方法進行反轉的。
對字符串進行截斷,但不會破壞單詞的完整性。
Strman.safeTruncate("Java is the best",13,"...");
結果以下所示:
Java is...
來看一下源碼:
public static String safeTruncate(final String value, final int length, final String filler) { if (length == 0) { return ""; } if (length >= value.length()) { return value; } String[] words = words(value); StringJoiner result = new StringJoiner(" "); int spaceCount = 0; for (String word : words) { if (result.length() + word.length() + filler.length() + spaceCount > length) { break; } else { result.add(word); spaceCount++; } } return append(result.toString(), filler); }
先調用 words()
方法對字符串進行單詞分割,而後按照長度進行截斷,最後調用 append()
方法填充上補位符。
safeTruncate 對應的是 truncate,可能會破壞單詞的完整性,使用方法以下所示:
Strman.truncate("Java is the best",13,"...")
結果以下所示:
Java is th...
來看一下源碼:
public static String truncate(final String value, final int length, final String filler) { if (length == 0) { return ""; } if (length >= value.length()) { return value; } return append(value.substring(0, length - filler.length()), filler); }
就是單純的切割和補位,沒有對單詞進行保護。
對字符串從新洗牌。
Strman.shuffle("沉默王二");
結果以下所示:
王默二沉
來看一下源碼:
public static String shuffle(final String value) { String[] chars = chars(value); Random random = new Random(); for (int i = 0; i < chars.length; i++) { int r = random.nextInt(chars.length); String tmp = chars[i]; chars[i] = chars[r]; chars[r] = tmp; } return Arrays.stream(chars).collect(joining()); }
調用 chars()
方法把字符串拆分爲字符串數組,而後遍歷對其重排,最後經過 Stream 轉成新的字符串。
Strman 中還有不少其餘巧妙的字符串處理方法,好比說把字符串按照指定的先後綴進行包裹 surround 等等,同窗們能夠參考 Strman 的官方文檔進行學習:
https://github.com/shekhargul...
PS:最近有小夥伴私信我要一份優質的 Java 教程,我在 GitHub 花了很長時間才找到了一份,115k star,真的很是不錯,來看一下目錄:
花了三個半小時把這份教程整理成 PDF 後,我發給了小夥伴,他「啪」的一下就發過來了私信,很快啊,「二哥,你也太用心了,這份教程的質量真的高,不服不行!」
若是你也對這份 PDF 感興趣的話,能夠經過下面的方式獲取。
連接: https://pan.baidu.com/s/1rT0l5ynzAQLF--efyRHzQw 密碼:dz95
多說一句,遇到好的資源,在讓它吃灰的同時,能學一點就賺一點,對吧?知識是無窮無盡的,但只要咱們比其餘人多學到了那麼一點點,那是否是就超越了呢?
點個贊吧,但願更多的人看獲得!