1. 场景描述

营销活动, 运营同学上传人群名单, 系统读取文件将人群落库, 有行数据混进了空格, 发奖失败.

account_no = ‘ 226610000XXXXXXXXXX’

image

排查代码, 感觉没什么问题,

    private CrowdDataInfo constructCrowdData(CrowdConfigInfo crowdConfigInfo, String data, int rowNum) {
        ... 
        CrowdDataInfo crowdDataInfo = new CrowdDataInfo();
        crowdDataInfo.setAccountNo(dataArray[0].trim());
        crowdDataInfo.setAccountType(crowdConfigInfo.getAccountType());
        crowdDataInfo.setCrowdNo(crowdConfigInfo.getCrowdNo());
        ...
        return crowdDataInfo;
    }

Debug trim() 函数源码, 发现并没有走到判断语句中, 显然, 我们应该碰到了假空格!!

    public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

1.1 分析 ‘ ‘ 和 ‘ ‘;

使用 python ord() 可以快速查出这两个空格的 Unicode 编号, 32 是普通的空格, 160 是特殊空格(unicode 编码是 \u00A0)

本文经过 MarkDown 语法转义, 测试代码中 “固定空格” 请使用 Option + Space 打印, Command + v 拷贝出来的是普通空格.

>>> ord(' ')
32
>>> ord(' ')
160
# 等同于
>>> ord('\u00A0')
160

也可以使用 Javascript 来感受 “特殊空格”, 直接打开浏览器 Console

> normal_space = ' '
' '
> special_space = '\u00A0'
' '
> normal_space.charCodeAt(0)
32
> special_space.charCodeAt(0)
160

Java 语言直接输出字符的整形即可.

char ch=' ';
System.out.println((int)ch);

1.2 几种语言的去空格函数

经过测试, 脚本语言自带的去除空格函数, 能去掉 '\uu00A0' 空格:

  1. Java 无法 String.trim() 特殊空格
  2. PHP 无法 trim(String) 特殊空格
  3. Javascript String.trim() 可以去除特殊空格
  4. Python String.strip() 可以去除特殊空格

经过谷歌, 发现 Unicode 为 160 的字符被称作 “Non-breaking space”, 下面仔细介绍一下.

2. 什么是 No-breaking Space

No-breaking Space, 也被成为 Fixed Space 姑且翻译成”固定空格”, 经常出现在 HTML 与 office 软件中. 前端同学会非常熟悉, 这货就是 '&nbsp'.

2.1 &nbsp 和普通空格的区别:

多个空格, 在 html 中会被合并成一个, &nbsp 不会被合并.

如下图所示: image

2.1 如何打印固定空格

系统 输入方式
Mac OS  Option + Space
Windows 系统 Ctrl + Alt + Space </br> Ctrl + Space </br> Alt + 0 + 1 + 6 + 0

3. 解决方式

既然知道该字符编码, 在 trim 之前, 用普通空格进行 replace 就可以了.

formattedString = originString.replace('\u00A0',' ').trim();

翻阅 JDK8 的文档, 自带了 [isWhitespace](http://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isWhitespace-char-), 总共有三个:

It is a Unicode space character (SPACE_SEPARATOR, LINE_SEPARATOR, or PARAGRAPH_SEPARATOR) but is not also a non-breaking space (‘\u00A0’, ‘\u2007’, ‘\u202F’).

处理字符串时, 转换成 char[] 遍历一遍也可以解决.

4. 参考文档

  1. https://en.wikipedia.org/wiki/Non-breaking_space

  2. http://htmlhelp.com/reference/charset/iso160-191.html

  3. http://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isWhitespace-char-isWhitespace

  4. https://unsplash.com/photos/Hv5vQ8AkbAs



blog comments powered by Disqus

Published

07 April 2017

Tags