死磕 Hutool 源码系列(一)——StrUtil 源码解析

3,391 阅读3分钟

絮絮叨叨

记得在一年前就写过关于 String 类的源码解析的总结,在开始阅读 StrUtil之前,我再次温习了一遍,感兴趣的朋友可以先阅读下找个感觉:

既然已经阅读过了 String 源码了,为什么还要阅读 StrUtil源码呢? 在实际项目开发中 String 是我们最常使用的数据结构,这就导致我们对 String的操作代码相当多,而这些操作又是独立于具体业务之外的通用代码,为了代码的简洁性、可读性,一般会把对 String的各种操作封装成静态工具类,也就是本文的主角:StrUtil,它几乎涵盖了所有你能想到或者想不到的字符串的通用操作方法。

源码初探

本文是对 StrUtil 源码解读的首篇总结,StrUtil 就是封装了对 String 操作的静态工具类,内部全是静态方法和静态常量:

可以看到作者在 StrUtil 中很贴心的预设了很多我们开发中几乎能想到的所有字符,比如空字符、空格、制表符等等。为了防止硬编码,我们可以直接通过该类去调用,而不是直接把符号硬编码在业务代码中。

简单花了个归类脑图(部分方法)总结下:

从方法脑图可以看出,每个方法基本都是见名知意的。

判空类方法

  • 判断字符串是否为空白,空白的定义为:null、空白字符

    public static boolean isBlank(CharSequence str) {
        //字符串长度
    		int length;
    
      	//如果字符串为null 或者字符串长度等于0,直接返回 true
    		if ((str == null) || ((length = str.length()) == 0)) {
    			return true;
    		}
    
    		for (int i = 0; i < length; i++) {
    			// 迭代检查字符串每个字符,只要有一个非空字符即为非空字符串
    			if (false == CharUtil.isBlankChar(str.charAt(i))) {
    				return false;
    			}
    		}
    
    		return true;
    	}
    
  • 如果对象是字符串,判断其是否为空白

    public static boolean isBlankIfStr(Object obj) {
        //对象为null,直接返回 true
    		if (null == obj) {
    			return true;
    		} else if (obj instanceof CharSequence) {
          //如果是字符串,调用 isBlank 方法进行判断
    			return isBlank((CharSequence) obj);
    		}
    		return false;
    	}
    
  • 判断字符串非空白

    	public static boolean isNotBlank(CharSequence str) {
        //isBlank 方法返回 false 即可
    		return false == isBlank(str);
    	}
    
  • 判断给定字符串数组中是否包含空白字符串,这里的参数为变成参数 hasBlank(CharSequence... strs)

    public static boolean hasBlank(CharSequence... strs) {
        //如果数组为空,则返回true
    		if (ArrayUtil.isEmpty(strs)) {
    			return true;
    		}
    
        //遍历检查数组中的字符串,只要满足一个为空白字符串,则返回true
    		for (CharSequence str : strs) {
    			if (isBlank(str)) {
    				return true;
    			}
    		}
        //默认为false
    		return false;
    	}
    
  • 判断给定的数组字符串是否全部为空白字符串

    public static boolean isAllBlank(CharSequence... strs) {
        //如果数组为空,则返回true
    		if (ArrayUtil.isEmpty(strs)) {
    			return true;
    		}
    		//遍历检查数组中的字符串,只要满足一个不是空白字符串,则返回false
    		for (CharSequence str : strs) {
    			if (isNotBlank(str)) {
    				return false;
    			}
    		}
        //默认返回true
    		return true;
    	}
    
  • 判断字符串是否为空

    public static boolean isEmpty(CharSequence str) {
        //字符串为null或者长度等于0
    		return str == null || str.length() == 0;
    	}
    
  • 如果对象是字符串,判断其是否为空

    public static boolean isEmptyIfStr(Object obj) {
        //对象是null,返回true
    		if (null == obj) {
    			return true;
    		} else if (obj instanceof CharSequence) {
          //对象是字符串,判断长度是否等于0
    			return 0 == ((CharSequence) obj).length();
    		}
        //默认false
    		return false;
    	}
    
  • 判断字符串为非空

    public static boolean isNotEmpty(CharSequence str) {
        //调用isEmpty方法满足false即可
    		return false == isEmpty(str);
    	}
    
  • 如果字符串为null ,返回默认值

    public static String nullToDefault(CharSequence str, String defaultStr) {
        //就是一个简单的三目运算
    		return (str == null) ? defaultStr : str.toString();
    	}
    
  • 如果字符串为空,返回默认值

    public static String emptyToDefault(CharSequence str, String defaultStr) {
       //三目运算判断字符串是否为空
    		return isEmpty(str) ? defaultStr : str.toString();
    	}
    
  • 如果字符串为空白字符串,返回默认值

    public static String blankToDefault(CharSequence str, String defaultStr) {
        //三目运算判断字符串是否为空白字符串
    		return isBlank(str) ? defaultStr : str.toString();
    	}
    
  • 判断指定字符串数组中是否包含空字符串

    public static boolean hasEmpty(CharSequence... strs) {
      
      	//数组为空,返回true
    		if (ArrayUtil.isEmpty(strs)) {
    			return true;
    		}
    
      	//遍历检查字符串数组中的字符串,其中一个为空,则为true
    		for (CharSequence str : strs) {
    			if (isEmpty(str)) {
    				return true;
    			}
    		}
        //默认false
    		return false;
    	}
    
  • 判断指定字符串数组中所有字符串是否都为空

    public static boolean isAllEmpty(CharSequence... strs) {
        //数组为空,返回true
    		if (ArrayUtil.isEmpty(strs)) {
    			return true;
    		}
    
        //遍历检查字符串数组中的字符串,其中一个不为空,则为false
    		for (CharSequence str : strs) {
    			if (isNotEmpty(str)) {
    				return false;
    			}
    		}
       //默认true
    		return true;
    	}
    
  • 判断指定字符串数组中所有字符串不为空

    public static boolean isAllNotEmpty(CharSequence... args) {
        //只要调用 hasEmpty 检查数组中是否包含空字符串就行了
    		return false == hasEmpty(args);
    	}
    
  • 判断指定字符串数组中所有字符串不为空白字符

    public static boolean isAllNotBlank(CharSequence... args) {
        //只要调用 hasBlank 检查数组中是否包含空字符串就行了
    		return false == hasBlank(args);
    	}
    

去前后空格类方法

  • 去除字符串前后空格

    public static String trim(CharSequence str) {
        //三目运算判断字符串为null返回 null ,否则调用 trim(CharSequence str, int mode) 方法去除前后空格
    		return (null == str) ? null : trim(str, 0);
    	}
    
  • 去除给定字符串数组全部字符串前后空格

    public static void trim(String[] strs) {
        //如果数组为null,不做任何处理
    		if (null == strs) {
    			return;
    		}
        //初始化动态字符串变量
    		String str;
        //迭代字符串数组
    		for (int i = 0; i < strs.length; i++) {
          //获取每个字符串
    			str = strs[i];
          //字符串不为null,调用String的trim方法处理每个字符串,并重新赋值给当前数组指定索引处的字符串
    			if (null != str) {
    				strs[i] = str.trim();
    			}
    		}
    	}
    
  • 除去字符串头尾空格,如果字符串为null ,则返回空字符

    public static String trimToEmpty(CharSequence str) {
        //字符串为null返回空字符串,否则调用 trim(str)方法去首尾空格
    		return str == null ? EMPTY : trim(str);
    	}
    
  • 除去字符串头尾空格,如果字符串为空字符,则返回 null

    public static String trimToNull(CharSequence str) {
        //先调用 trim(str) 去除前后空格
    		final String trimStr = trim(str);
        //如果是空白字符则返回null,否则返回处理后的结果
    		return EMPTY.equals(trimStr) ? null : trimStr;
    	}
    
  • 除去字符串头部空格

    public static String trimStart(CharSequence str) {
       // 调用 trim(CharSequence str, int mode) 方法处理,mode 参数传 -1 即可
    		return trim(str, -1);
    	}
    
  • 除去字符串尾部空格

    public static String trimEnd(CharSequence str) {
        //调用 trim(CharSequence str, int mode) 方法处理,mode 参数传 1 即可
    		return trim(str, 1);
    	}
    
  • 除去字符串前后空格,可通过传参控制模式

    /**
    	 * 除去字符串头尾部的空白符,如果字符串是<code>null</code>,依然返回<code>null</code>。
    	 *
    	 * @param str  要处理的字符串
    	 * @param mode <code>-1</code>表示trimStart,<code>0</code>表示trim全部, <code>1</code>表示trimEnd
    	 * @return 除去指定字符后的的字符串,如果原字串为<code>null</code>,则返回<code>null</code>
    	 */
    	public static String trim(CharSequence str, int mode) {
        //字符串为null,直接返回null
    		if (str == null) {
    			return null;
    		}
    
        //字符串长度
    		int length = str.length();
        //开始位置索引为0
    		int start = 0;
        //结束位置索引为字符串长度
    		int end = length;
    
    		// 当 mode<=0 时,扫描字符串头部,确定开始裁剪的索引位置
    		if (mode <= 0) {
          //当 start < end 并且索引除的字符为空字符的时候,不停的向后迭代扫描,直到扫描到的字符不是空字符或者索引越界为止
    			while ((start < end) && (CharUtil.isBlankChar(str.charAt(start)))) {
            //不停的累加起始索引位置
    				start++;
    			}
    		}
    
    		// 当 mode>=0 时, 扫描字符串尾部,确定结束裁剪的索引位置
    		if (mode >= 0) {
          // 当 start < end并且从字符串往后扫描到的字符为空字符时,不停的向前迭代扫描,直到扫描到的字符不是空字符或者扫描到字符串长度结束为止
    			while ((start < end) && (CharUtil.isBlankChar(str.charAt(end - 1)))) {
            //不停的递减结束索引位置
    				end--;
    			}
    		}
    
        //如果开始裁剪的所有位置大于0,或者 结束位置索引小于字符串长度,说明存在空白字符
    		if ((start > 0) || (end < length)) {
          //直接调用字符串的substring方法进行裁剪,即可移除前后空白字符
    			return str.toString().substring(start, end);
    		}
        
        //字符串前后不存在空白字符串情况下,直接返回原字符串
    		return str.toString();
    	}
    

查找类方法

  • 字符串是否以给定字符开始

    public static boolean startWith(CharSequence str, char c) {
        //获取字符串第一个字符判断是否等于目标字符
    		return c == str.charAt(0);
    	}
    
  • 是否以指定字符串开头,可控制是否忽略大小写

    public static boolean startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) {
        //字符串为null 或者 待检测的开头字符串为null
    		if (null == str || null == prefix) {
          //直接返回两个字符串是否都为null 就行,因为 null 肯定以null 开头啊,哈哈哈
    			return null == str && null == prefix;
    		}
        
        //如果忽略大小写
    		if (isIgnoreCase) {
          //全部转为小写,再调用字符串的 startsWith 方法进行判断
    			return str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase());
    		} else {
          //不忽略大小写,直接调用 startsWith 方法判断即可
    			return str.toString().startsWith(prefix.toString());
    		}
    	}
    
  • 是否以指定字符串开头,不忽略大小写

    public static boolean startWith(CharSequence str, CharSequence prefix) {
        //直接调用 startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) 方法,isIgnoreCase 传 false 即可
    		return startWith(str, prefix, false);
    	}
    
  • 是否以指定字符串开头,忽略大小写

    public static boolean startWithIgnoreCase(CharSequence str, CharSequence prefix) {
        //直接调用 startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) 方法,isIgnoreCase 传 true 即可
    		return startWith(str, prefix, true);
    	}
    
  • 给定字符串是否以字符串数组中的任意一个字符串开头

    	public static boolean startWithAny(CharSequence str, CharSequence... prefixes) {
        //字符串为空 或者 数组为空,直接返回false
    		if (isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
    			return false;
    		}
    
        //遍历检查字符串是否以数组中任意一个字符串开头,并且不忽略大小写
    		for (CharSequence suffix : prefixes) {
          //只要查找到一个成立,直接返回 true
    			if (startWith(str, suffix, false)) {
    				return true;
    			}
    		}
        //找不到则默认返回false
    		return false;
    	}
    
  • 是否以给定的字符结尾

    	public static boolean endWith(CharSequence str, char c) {
        //获取字符串最后一个字符,判断是否与给定字符相等就行了
    		return c == str.charAt(str.length() - 1);
    	}
    
  • 是否以给定字符串结尾,可控制是否忽略大小写

    public static boolean endWith(CharSequence str, CharSequence suffix, boolean isIgnoreCase) {
        //待检查字符串等于null 或者 前缀字符串等于null
    		if (null == str || null == suffix) {
          //只要其中一个等于null ,直接判断两个都等于null ,就成立了,毕竟 null 肯定以null 结尾嘛
    			return null == str && null == suffix;
    		}
        
        //如果忽略大小写
    		if (isIgnoreCase) {
          //先转为小写,再调用字符串的 endsWith 方法判断即可
    			return str.toString().toLowerCase().endsWith(suffix.toString().toLowerCase());
    		} else {
          //不忽略大小写,直接调用字符串的 endsWith 方法判断即可
    			return str.toString().endsWith(suffix.toString());
    		}
    	}
    
  • 是否以指定字符串结尾,不忽略大小写

    public static boolean endWith(CharSequence str, CharSequence suffix) {
      //直接调用 endWith(CharSequence str, CharSequence suffix, boolean isIgnoreCase),isIgnoreCase传 false 即可
    		return endWith(str, suffix, false);
    	}
    
  • 是否以指定字符串结尾,忽略大小写

    public static boolean endWithIgnoreCase(CharSequence str, CharSequence suffix) {
      //直接调用 endWith(CharSequence str, CharSequence suffix, boolean isIgnoreCase),isIgnoreCase传 true 即可
    		return endWith(str, suffix, true);
    	}
    
  • 给定字符串是否以字符串数组中任意一个字符串结尾

    public static boolean endWithAny(CharSequence str, CharSequence... suffixes) {
        //字符串为空 或者数组为空,直接返回false
    		if (isEmpty(str) || ArrayUtil.isEmpty(suffixes)) {
    			return false;
    		}
    
        //遍历字符串数组,检查给定字符串中是否以该字符串结尾,不忽略大小写
    		for (CharSequence suffix : suffixes) {
          //一旦找到一个成立的字符串,直接返回true
    			if (endWith(str, suffix, false)) {
    				return true;
    			}
    		}
       //没找到,默认返回false
    		return false;
    	}
    
  • 指定字符是否在字符串中出现过

    public static boolean contains(CharSequence str, char searchChar) {
        //直接调用 indexOf 方法获取字符索引位置,大于 -1 表示存在
    		return indexOf(str, searchChar) > -1;
    	}
    
  • 指定字符串是否在字符串中出现过

    public static boolean contains(CharSequence str, CharSequence searchStr) {
        //字符串等于null或者被查找的字符串等于null 直接返回false
    		if (null == str || null == searchStr) {
    			return false;
    		}
       //调用字符串的 contains 方法判断是否存在查找的字符串
    		return str.toString().contains(searchStr);
    	}
    
  • 查找指定字符串是否包含指定字符串列表中的任意一个字符串

    public static boolean containsAny(CharSequence str, CharSequence... testStrs) {
        //直接调用 getContainsStr 获取字符串数组中存在的字符串,不为null表示存在
    		return null != getContainsStr(str, testStrs);
    	}
    
  • 查找指定字符串是否包含指定字符列表中的任意一个字符

    public static boolean containsAny(CharSequence str, char... testChars) {
        //字符串不为空进行处理
    		if (false == isEmpty(str)) {
          //字符串长度
    			int len = str.length();
          //遍历字符串字符,查找包含字符列表中是否包含该字符
    			for (int i = 0; i < len; i++) {
            //检查到一个直接返回true
    				if (ArrayUtil.contains(testChars, str.charAt(i))) {
    					return true;
    				}
    			}
    		}
        //默认返回false
    		return false;
    	}
    
  • 检查指定字符串中是否只包含给定的字符

    	public static boolean containsOnly(CharSequence str, char... testChars) {
        //字符串不为空进行处理
    		if (false == isEmpty(str)) {
          //字符串长度
    			int len = str.length();
          //遍历字符串字符,查找字符串中的字符是否在字符列表中
    			for (int i = 0; i < len; i++) {
            //检查到一个不存在直接返回false
    				if (false == ArrayUtil.contains(testChars, str.charAt(i))) {
    					return false;
    				}
    			}
    		}
        //默认返回true
    		return true;
    	}
    
  • 给定字符串是否包含空白字符

    public static boolean containsBlank(CharSequence str) {
        //字符串等于null,直接返回false
    		if (null == str) {
    			return false;
    		}
        //字符串长度
    		final int length = str.length();
        //字符串长度等于0,返回false
    		if (0 == length) {
    			return false;
    		}
    		
      	//遍历检查字符串中字符是否存在空字符
    		for (int i = 0; i < length; i += 1) {
          //检查到一个空字符,直接返回true
    			if (CharUtil.isBlankChar(str.charAt(i))) {
    				return true;
    			}
    		}
        //默认false
    		return false;
    	}
    
  • 查找指定字符串是否包含指定字符串列表中的任意一个字符串,包含则返回第一个字符串

    public static String getContainsStr(CharSequence str, CharSequence... testStrs) {
        //指定字符串为空 或者字符串列表为空,直接返回null
    		if (isEmpty(str) || ArrayUtil.isEmpty(testStrs)) {
    			return null;
    		}
        //遍历字符串列表,检查是否在指定字符串中
    		for (CharSequence checkStr : testStrs) {
          //检查到在指定的字符串中,直接返回该字符串
    			if (str.toString().contains(checkStr)) {
    				return checkStr.toString();
    			}
    		}
        //获取不到则默认返回null
    		return null;
    	}
    
  • 是否包含给定字符串,忽略大小写

    	public static boolean containsIgnoreCase(CharSequence str, CharSequence testStr) {
    		if (null == str) {
    			// 如果被监测字符串和
    			return null == testStr;
    		}
        //先转换为小写再调用 contains 方法判断
    		return str.toString().toLowerCase().contains(testStr.toString().toLowerCase());
    	}
    
  • 查找指定字符串是否包含指定字符串列表中的任意一个字符串,忽略大小写

    public static boolean containsAnyIgnoreCase(CharSequence str, CharSequence... testStrs) {
        //调用getContainsStrIgnoreCase查找包含的第一个字符串,不为null表示存在
    		return null != getContainsStrIgnoreCase(str, testStrs);
    	}
    
  • 查找指定字符串是否包含指定字符串列表中的任意一个字符串,存在则返回第一个字符串,忽略大小写

    public static String getContainsStrIgnoreCase(CharSequence str, CharSequence... testStrs) {
        //字符串为空 或者字符串列表为空,直接返回null
    		if (isEmpty(str) || ArrayUtil.isEmpty(testStrs)) {
    			return null;
    		}
        //遍历查找字符串列表中是都存在字符串,包含在指定的字符串中,并忽略大小写
    		for (CharSequence testStr : testStrs) {
          //检查到一个存在,直接返回该字符串
    			if (containsIgnoreCase(str, testStr)) {
    				return testStr.toString();
    			}
    		}
        //找不到则默认返回null
    		return null;
    	}
    

最后

StrUtil包含太多静态方法了,开篇先记录这些方法,后续待更新.....

推荐阅读

参考:

更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学