Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 249|回复: 0

[默认分类] 【源码分析】你必须知道的string.IsNullOrEmpty && string.IsNullOrWhiteSpace

  [复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-3-29 10:15:34 | 显示全部楼层 |阅读模式
    写在前面
    之前自信撸码时踩了一次小坑,代码如下:
    1. [code]  private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
    2.         {
    3.             if (string.IsNullOrEmpty(value))
    4.              {
    5.                 return;
    6.              }
    7.             value = HttpUtility.UrlDecode(value);                    
    8.             SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);         
    9.              //具体业务...
    10.         }
    复制代码
    [/code]
    就是这段代码在测试环境抛错,说起来全是泪啊。这段代码的具体业务场景是Websocket即时通讯接收来自客户端的消息,消息以json字符串的形式传输。首先判断是否空字符串,如果不是,为了防止乱码进行Url解码,然后反序列化消息解析成需要的数据格式,最后执行具体的业务操作。
    测试环境抛的错是万恶的“未将对象引用到对象的实例”,很简单就可以定位到问题的所在——反序列化失败了,只要在序列化之后执行具体业务逻辑之前加上非空判断就可以解决掉这个问题。这也怪自己思维还不够严密,没有养成防御性编码的习惯。
    1. [code]  private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
    2.         {
    3.             if (string.IsNullOrEmpty(value))
    4.              {
    5.                 return;
    6.              }
    7.             value = HttpUtility.UrlDecode(value);                    
    8.             SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);
    9.             if(model==null)
    10.             {
    11.                 return;
    12.             }   
    13.              //具体业务...
    14.         }
    复制代码
    [/code]
    通过日志分析反序列失败的原因,日志中记录的消息是空白的,但是代码中明明有
    1. string.IsNullOrEmpty(value)
    复制代码
    的判断,为啥还会出现空的情况呢?仔细一看,原来是多个连续的空格,吐血。于是乎立马把
    1. string.IsNullOrEmpty(value)
    复制代码
    改为
    1. string.IsNullOrWhiteSpace(value)
    复制代码
    ,当value是多个连续的空格时,直接返回,不会继续往下执行。
    1. [code]  private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
    2.         {
    3.             if (string.IsNullOrWhiteSpace(value))
    4.              {
    5.                 return;
    6.              }
    7.             value = HttpUtility.UrlDecode(value);                    
    8.             SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);
    9.             if(model==null)
    10.             {
    11.                 return;
    12.             }   
    13.              //具体业务...
    14.         }
    复制代码
    [/code]
    我们都知道,string.IsNullOrEmpty方法是判断字符串是否为:null或者string.Empty;string.IsNullOrWhiteSpace方法是判断null或者所有空白字符,功能相当于string.IsNullOrEmpty和str.Trim().Length总和。那么具体方法内部是怎么实现的呢?我们可以通过ILSpy反编译窥探一番。
    string.IsNullOrEmpty源码分析
    1. [code]// string
    2. /// <summary>Indicates whether the specified string is null or an <see cref="F:System.String.Empty" /> string.</summary>
    3. /// <param name="value">The string to test. </param>
    4. /// <returns>true if the <paramref name="value" /> parameter is null or an empty string (""); otherwise, false.</returns>
    5. [__DynamicallyInvokable]
    6. public static bool IsNullOrEmpty(string value)
    7. {
    8.     return value == null || value.Length == 0;
    9. }
    复制代码
    [/code]
    string.IsNullOrEmpty实现很简单,无非就是判断传入的字符串参数,当是null或者空字符串string.Empty就返回true;否则返回false。
    string.IsNullOrWhiteSpace源码分析
    1. [code]// string
    2. /// <summary>Indicates whether a specified string is null, empty, or consists only of white-space characters.</summary>
    3. /// <param name="value">The string to test.</param>
    4. /// <returns>true if the <paramref name="value" /> parameter is null or <see cref="F:System.String.Empty" />, or if <paramref name="value" /> consists exclusively of white-space characters. </returns>
    5. [__DynamicallyInvokable]
    6. public static bool IsNullOrWhiteSpace(string value)
    7. {
    8.     if (value == null)
    9.     {
    10.         return true;
    11.     }
    12.     for (int i = 0; i < value.Length; i++)
    13.     {
    14.         if (!char.IsWhiteSpace(value[i]))
    15.         {
    16.             return false;
    17.         }
    18.     }
    19.     return true;
    20. }
    复制代码
    [/code]
    string.IsNullOrWhiteSpace的实现就稍微复杂一些,首先当传入的字符串参数为null时肯定返回true;如果不是就开始遍历字符串,取出字符执行char.IsWhiteSpace(value)方法,如果char.IsWhiteSpace(value)方法返回false,就终止遍历,返回fasle;否则返回true。所以char.IsWhiteSpace方法应该判断的是传入的字符是否为空字符,是空字符返回true,不是返回false。我们可以进入char.IsWhiteSpace方法看一下具体实现:
    1. [code]// char
    2. /// <summary>Indicates whether the specified Unicode character is categorized as white space.</summary>
    3. /// <param name="c">The Unicode character to evaluate. </param>
    4. /// <returns>true if <paramref name="c" /> is white space; otherwise, false.</returns>
    5. [__DynamicallyInvokable]
    6. public static bool IsWhiteSpace(char c)
    7. {
    8.     if (char.IsLatin1(c))
    9.     {
    10.         return char.IsWhiteSpaceLatin1(c);
    11.     }
    12.     return CharUnicodeInfo.IsWhiteSpace(c);
    13. }
    复制代码
    [/code]
    可以发现方法内部判断了char.IsLatin1(c),符合的话执行char.IsWhiteSpaceLatin1(c),看不明白,继续往下走。
    1. [code]// char
    2. private static bool IsLatin1(char ch)
    3. {
    4.     return ch <= "ÿ";
    5. }
    复制代码
    [/code]
    "ÿ"是什么鬼?看不懂。但是char.IsWhiteSpace方法调用了CharUnicodeInfo.IsWhiteSpace(c)方法,那应该是和Unicode有关。而且到了char字符的级别上,更加可以肯定和Unicode编码有关。从Unicode字符列表搜索"ÿ",果然搜到了。

    我们可以发现"ÿ"是拉丁字母辅助的最后一个字符,再往后的的字符基本不会出现,所以在大多数情况下
    1. ch <= "ÿ"
    复制代码
    可以满足的。当满足
    1. ch <= "ÿ"
    复制代码
    时执行下面的方法:
    1. [code]// char
    2. private static bool IsWhiteSpaceLatin1(char c)
    3. {
    4.     return c == " " || (c >= "\t" && c <= "\r") || c == "\u00a0" || c == "\u0085";
    5. }
    复制代码
    [/code]
    IsWhiteSpaceLatin1(char c)负责判断字符c是否是空白字符。
    1. c == " "
    复制代码
    很好理解,判断c是不是空格字符。对应下图:

    1. c >= "\t" && c <= "\r"
    复制代码
    对照Unicode字符列表就可以理解。下图红框圈出的5个字符都认定为空白字符。


    1. c == "\u00a0"
    复制代码
    如下图被认定为空白字符。

    1. c == "\u0085"
    复制代码
    如下图被认定为空白字符。

    满足①②③④其中任意一个,便会被判定为空白字符。
    那么假设char.IsLatin1(c)返回false呢?此时执行CharUnicodeInfo.IsWhiteSpace(c)。
    1. [code]// System.Globalization.CharUnicodeInfo
    2. internal static bool IsWhiteSpace(char c)
    3. {
    4.     switch (CharUnicodeInfo.GetUnicodeCategory(c))
    5.     {
    6.     case UnicodeCategory.SpaceSeparator:
    7.     case UnicodeCategory.LineSeparator:
    8.     case UnicodeCategory.ParagraphSeparator:
    9.         return true;
    10.     default:
    11.         return false;
    12.     }
    13. }
    复制代码
    [/code]
    CharUnicodeInfo.GetUnicodeCategory(c)会返回一个UnicodeCategory枚举类型。
    1. [code]// System.Globalization.CharUnicodeInfo
    2. /// <summary>Gets the Unicode category of the specified character.</summary>
    3. /// <param name="ch">The Unicode character for which to get the Unicode category. </param>
    4. /// <returns>A <see cref="T:System.Globalization.UnicodeCategory" /> value indicating the category of the specified character.</returns>
    5. [__DynamicallyInvokable]
    6. public static UnicodeCategory GetUnicodeCategory(char ch)
    7. {
    8.     return CharUnicodeInfo.InternalGetUnicodeCategory((int)ch);
    9. }
    复制代码
    [/code]
    CharUnicodeInfo是一个静态类,根据MSDN说明,Unicode标准定义了许多Unicode字符类别。例如,一个字符可能被分类为大写字母,小写字母,小数位数字,字母数字,段落分隔符,数学符号或货币符号。所述UnicodeCategory枚举定义了可能的字符的类别。
    使用CharUnicodeInfo类来获取特定字符的UnicodeCategory值。该CharUnicodeInfo类定义了返回下面的Unicode字符值的方法:

    字符或代理对所属的特定类别。返回的值是UnicodeCategory枚举的成员。
    数字值。仅适用于数字字符,包括分数,下标,上标,罗马数字,货币分子,圈出的数字和脚本特定的数字。
    数字值。适用于可与其他数字字符组合的数字字符,以表示编号系统中的整数。
    十进制数字值。仅适用于表示小数点(基10)系统中的十进制数字的字符。十进制数字可以是十个数字之一,从零到九。这些字符是UnicodeCategory的成员DecimalDigitNumber类别。

    当GetUnicodeCategory方法返回的枚举值是UnicodeCategory.SpaceSeparatorUnicodeCategory.LineSeparatorUnicodeCategory.ParagraphSeparator其中任意之一,则判定为空白字符,返回true。
    总结
    踩坑不要紧,要紧的是要知道为什么会有这个坑。
    软件80%的bug都拜“未将对象引用到对象的实例”所赐,要养成防御性编码的好习惯。


    本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。
    如果您认为还不错,不妨点击一下下方的【推荐】按钮,谢谢支持。
    转载与引用请注明出处。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-25 22:49 , Processed in 0.415511 second(s), 37 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表