1. 程式人生 > >判斷字串是否為空的org.apache.commons.lang3.StringUtils類方法isBlank()原始碼檢視

判斷字串是否為空的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. 
判斷的就是這些字元,如果是這些字元,那麼就作為空字串返回true。有哪些會被認為是空呢? 1.\t (tab鍵) 2.\n(換行符) 3.\r(軟空格) 4.\f(...) ... 也就是說,isBlank()方法不僅判斷的是空格,還判斷了各種換行符,製表符,enter鍵等的unicode編碼表示。 由於 if(s == null ||"".equals(s)); 這種判斷字串為空的方式,最終是通過判斷字串長度進行比較的。那麼如果\t \n \r  \f 等特殊字元不佔長度的話,兩種實現方式是等價的;否則,兩種判斷方式是不等價的。 寫了一個測試類
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