判斷字串是否為空的org.apache.commons.lang3.StringUtils類方法isBlank()原始碼檢視
轉發請註明出處與作者。個人分析的,正確性歡迎大家一起探討,有錯誤還希望指正和批評
首先說結論:isBlank() 會把製表符(tab鍵 \t,換行符 \n ,回車鍵等一系列字元格式的unicode編碼)等作為空來處理;而我們平時使用的 if(s == null ||"".equals(s)); 不會把特殊字元作為空處理。
判斷字串是否為空,有很多種方法,下面是其中一種:
if(s == null ||"".equals(s));
但這樣寫看起來是不能從程式碼本身看到程式碼本身的業務含義,於是很多追求程式碼可讀性的程式碼編寫者會使用org.apache.commons.lang3.StringUtils類的isBlank()方法。該方法一看就知道程式碼是在判斷是不是空,但該方法的存在難道只是為了可讀性嗎?這個方法僅僅是封裝了下判斷邏輯嗎,還是還有其他的優勢?為了搞懂這個問題,我查找了下原始碼。
/** * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p> * * <pre> * StringUtils.isBlank(null) = true * StringUtils.isBlank("") = true * StringUtils.isBlank(" ") = true * StringUtils.isBlank("bob") = false * StringUtils.isBlank(" bob ") = false * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace * @since 2.0 * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) */ public static boolean isBlank(CharSequence cs) { int strLen; if (cs == null || (strLen = cs.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(cs.charAt(i)) == false) { return false; } } return true; }
該方法的形參,是CharSequence型別,我們判斷的是字串,卻用了這個型別接收。我們先看下該型別是類還是介面。
通過api查詢,jdk7線上文件,可以看到,CharSequence是java.long包下的介面。那麼按照向上轉型的原理,我們可以猜測,字串實現了該介面。檢視api
確實是String實現了該介面。
讀到這裡,就可以判斷,isBlank()方法不僅是通過簡單的封裝了 if(s == null ||"".equals(s));這個邏輯實現可讀性而存在的。
那麼這樣做的原因還有什麼?我繼續看了下原始碼實現:
int strLen; if (cs == null || (strLen = cs.length()) == 0) { return true; }
這一步,先判斷CharSequence物件是不是null,如果是null,那麼肯定是空,就不需要判斷長度,直接返回true,表示字串是空;如果不是null,那麼就判斷長度,如果長度是0,那麼也返回true,表示字串是空。
判斷字串是否是null這種情況兩種實現方式都有判斷。那麼這兩種實現方式是否是完全等價的?
"".equals(s)這個判斷,跟後面一系列的判斷是否本質上是一樣的呢?
我繼續查看了下String的equals()方法。
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
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方法的判斷,是先判斷是不是同一個引用,如果引用相同,那麼直接返回true。(這裡可以看到,對於String型別變數,同一個引用地址,指向的是同一個物件。這個容易理解,不可能指向的同一塊記憶體地址了,還不是同一個物件。但反過來,相同的物件,不一定存放在同一塊記憶體地址中)。接著判斷傳遞過來的物件是不是String型別,即便是StringBuffer或者StringBuilder也不行,因為StringBuffer和String都實現了CharSequence,但兩者並不存在繼承關係。如果不是,直接返回false。只有是string型別的物件,才會進行比較。然後比較兩個字串的長度,如果不同,返回false,表示不是同一個字串。如果長度相同,再轉換為字元,挨個比較字元。
看一下這段程式碼
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
之前知道,判斷為空,"".equals(str) 這種比str.equals("")效率要高。這裡可以看到,如果判斷為空的情況,"".equals(str)不會進入迴圈,而後者會進入迴圈判斷一下。如果字串的各個字元都相同,就認為是相同的字串。
上面對equals的判斷,我們可以看到,對於"".equals(str)判斷,就是根據型別和長度進行判斷是否是空的。
那麼再繼續看isBlank()方法後續的實現:
for (int i = 0; i < strLen; i++) {
if (Character.isWhitespace(cs.charAt(i)) == false) {
return false;
}
}
首先判斷長度,如果長度是0,直接返回是空;如果長度不為0,那麼依次判斷字元是否是表示空格的字元,繼續檢視
isWhitespace()方法:
/**
* Determines if the specified character (Unicode code point) is
* white space according to Java. A character is a Java
* whitespace character if and only if it satisfies one of the
* following criteria:
* <ul>
* <li> It is a Unicode space character ({@link #SPACE_SEPARATOR},
* {@link #LINE_SEPARATOR}, or {@link #PARAGRAPH_SEPARATOR})
* but is not also a non-breaking space ({@code '\u005Cu00A0'},
* {@code '\u005Cu2007'}, {@code '\u005Cu202F'}).
* <li> It is {@code '\u005Ct'}, U+0009 HORIZONTAL TABULATION.
* <li> It is {@code '\u005Cn'}, U+000A LINE FEED.
* <li> It is {@code '\u005Cu000B'}, U+000B VERTICAL TABULATION.
* <li> It is {@code '\u005Cf'}, U+000C FORM FEED.
* <li> It is {@code '\u005Cr'}, U+000D CARRIAGE RETURN.
* <li> It is {@code '\u005Cu001C'}, U+001C FILE SEPARATOR.
* <li> It is {@code '\u005Cu001D'}, U+001D GROUP SEPARATOR.
* <li> It is {@code '\u005Cu001E'}, U+001E RECORD SEPARATOR.
* <li> It is {@code '\u005Cu001F'}, U+001F UNIT SEPARATOR.
* </ul>
* <p>
*
* @param codePoint the character (Unicode code point) to be tested.
* @return {@code true} if the character is a Java whitespace
* character; {@code false} otherwise.
* @see Character#isSpaceChar(int)
* @since 1.5
*/
public static boolean isWhitespace(int codePoint) {
return CharacterData.of(codePoint).isWhitespace(codePoint);
}
註釋全是unicode編碼,看不懂,繼續檢視,
// Character <= 0xff (basic latin) is handled by internal fast-path
// to avoid initializing large tables.
// Note: performance of this "fast-path" code may be sub-optimal
// in negative cases for some accessors due to complicated ranges.
// Should revisit after optimization of table initialization.
static final CharacterData of(int ch) {
if (ch >>> 8 == 0) { // fast-path
return CharacterDataLatin1.instance;
} else {
switch(ch >>> 16) { //plane 00-16
case(0):
return CharacterData00.instance;
case(1):
return CharacterData01.instance;
case(2):
return CharacterData02.instance;
case(14):
return CharacterData0E.instance;
case(15): // Private Use
case(16): // Private Use
return CharacterDataPrivateUse.instance;
default:
return CharacterDataUndefined.instance;
}
}
}
往下再繼續看,就是字元編碼的處理了,看不懂了。回頭看下api文件,
- It is a Unicode space character (
SPACE_SEPARATOR
,LINE_SEPARATOR
, orPARAGRAPH_SEPARATOR
) but is not also a non-breaking space ('\u00A0'
,'\u2007'
,'\u202F'
). - It is
'\t'
, U+0009 HORIZONTAL TABULATION. - It is
'\n'
, U+000A LINE FEED. - It is
'\u000B'
, U+000B VERTICAL TABULATION. - It is
'\f'
, U+000C FORM FEED. - It is
'\r'
, U+000D CARRIAGE RETURN. - It is
'\u001C'
, U+001C FILE SEPARATOR. - It is
'\u001D'
, U+001D GROUP SEPARATOR. - It is
'\u001E'
, U+001E RECORD SEPARATOR. - It is
'\u001F'
, U+001F UNIT SEPARATOR.
public class BlankTest {
public static void main(String[] args) {
char c = '\n';
String str = String.valueOf(c);
System.out.println(str.length());
String ss = "a"+str+"dd";
System.out.println(ss);
}
}
輸出結果如下:
1
a
dd
說明換行起作用了,並且佔用位元組為一個位元組。 繼續測試:
public class BlankTest {
public static void main(String[] args) {
System.out.println("換行符是否是空字元:"+newLineCharIsOrNotBlank());
}
public static boolean newLineCharIsOrNotBlank(){
return "".equals('\n');
}
}
結果:
換行符是否是空字元:false
至此可以得出結論:
1.換行符等字元 使用 if(s == null ||"".equals(s));語句判斷,結果不是空字串。
2.換行符等字元 使用org.apache.commons.lang3.StringUtils類的isBlank()方法判斷,結果是空字串。
因此,使用StringUtils的isBlank()進行字串為空的判斷時,會考慮換行符,製表符,回車鍵等。這些特殊字元都會作為空字串進行處理;而if(s == null ||"".equals(s));把換行符,製表符,回車鍵等字元作為非空字元進行處理了。
結果驗證:
public static void main(String[] args) {
System.out.println("s==null||\"\".equals('\\n')換行符是否是空字元:"+newLineCharIsOrNotBlank(String.valueOf('\n')));
System.out.println("isBlank()判斷換行符是否是空字元:"+StringUtils.isBlank(String.valueOf('\n')));
}
public static boolean newLineCharIsOrNotBlank(String s){
return s==null||"".equals('\n');
}
結果: s==null||"".equals('\n')換行符是否是空字元:false
isBlank()判斷換行符是否是空字元:true