有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java正则表达式在ascii范围之外不匹配,其行为与python正则表达式不同

我想像sklearn的CountVectorizer一样从文档中筛选字符串。它使用以下正则表达式:(?u)\b\w\w+\b。 此java代码的行为方式应相同:

Pattern regex = Pattern.compile("(?u)\\b\\w\\w+\\b");
Matcher matcher = regex.matcher("this is the document.!? äöa m²");

while(matcher.find()) {
    String match = matcher.group();
    System.out.println(match);
}

但这不会像python中那样产生所需的输出:

this
is
the
document
äöa
m²

它反而输出:

this
is
the
document

像python正则表达式那样,如何包含非ascii字符


共 (2) 个答案

  1. # 1 楼答案

    正如Wiktor在评论中所建议的,您可以使用(?U)来打开标志UNICODE_CHARACTER_CLASS。虽然这允许匹配äöa,但仍然不匹配。这是因为UNICODE_CHARACTER_CLASSwith \w不能将²识别为有效的字母数字字符。作为\w的替代,您可以使用[\pN\pL_]。这与Unicode数字\pN和Unicode字母\pL(加上_)匹配。{}Unicode字符类包括{}字符类,其中包括拉丁1增补-拉丁-1标点符号字符类(包括{})。或者,您可以将\pNoUnicode字符类添加到具有\w的字符类中。这意味着以下正则表达式与字符串正确匹配:

    [\pN\pL_]{2,}         # Matches any Unicode number or letter, and underscore
    (?U)[\w\pNo]{2,}      # Uses UNICODE_CHARACTER_CLASS so that \w matches Unicode.
                          # Adds \pNo to additionally match ²³¹
    

    那么为什么\w在Java中不匹配²而在Python中匹配呢


    Java的解释

    查看OpenJDK 8-b132's ^{} implementation,我们得到以下信息(我删除了与回答问题无关的信息):

    Unicode support

    The following Predefined Character classes and POSIX character classes are in conformance with the recommendation of Annex C: Compatibility Properties of Unicode Regular Expression, when UNICODE_CHARACTER_CLASS flag is specified.

    \w A word character: [\p{Alpha}\p{gc=Mn}\p{gc=Me}\p{gc=Mc}\p{Digit}\p{gc=Pc}\p{IsJoin_Control}]

    太好了!现在,当使用(?U)标志时,我们有了\w定义。将这些Unicode字符类插入this amazing tool将准确地告诉您每个Unicode字符类匹配的内容。在不让这篇文章过长的情况下,我将继续告诉您以下两个类都不匹配²

    • \p{Alpha}
    • \p{gc=Mn}
    • \p{gc=Me}
    • \p{gc=Mc}
    • \p{Digit}
    • \p{gc=Pc}
    • \p{IsJoin_Control}

    Python的解释

    那么,当u标志与\w一起使用时,为什么Python会匹配²³¹?这一个很难找到,但我深入了Python's source code (I used Python 3.6.5rc1 - 2018-03-13)。在消除了许多关于如何调用此函数的漏洞之后,基本上会发生以下情况:

    • \w定义为CATEGORY_UNI_WORD,然后以SRE_作为前缀SRE_CATEGORY_UNI_WORD调用SRE_UNI_IS_WORD(ch)
    • SRE_UNI_IS_WORD定义为(SRE_UNI_IS_ALNUM(ch) || (ch) == '_')
    • SRE_UNI_IS_ALNUM调用Py_UNICODE_ISALNUM,它又被定义为(Py_UNICODE_ISALPHA(ch) || Py_UNICODE_ISDECIMAL(ch) || Py_UNICODE_ISDIGIT(ch) || Py_UNICODE_ISNUMERIC(ch))
    • 这里重要的是Py_UNICODE_ISDECIMAL(ch),定义为Py_UNICODE_ISDECIMAL(ch) _PyUnicode_IsDecimalDigit(ch)

    现在,让我们看看方法{{CD46}}:

    int _PyUnicode_IsDecimalDigit(Py_UCS4 ch)
    {
        if (_PyUnicode_ToDecimalDigit(ch) < 0)
            return 0;
        return 1;
    }
    

    正如我们所看到的,这个方法返回1如果_PyUnicode_ToDecimalDigit(ch) < 0。那么_PyUnicode_ToDecimalDigit是什么样子的呢

    int _PyUnicode_ToDecimalDigit(Py_UCS4 ch)
    {
        const _PyUnicode_TypeRecord *ctype = gettyperecord(ch);
    
        return (ctype->flags & DECIMAL_MASK) ? ctype->decimal : -1;
    }
    

    很好,所以基本上,如果字符的UTF-32编码字节具有DECIMAL_MASK标志,那么这将计算为true,并返回大于或等于0的值

    ²的UTF-32编码字节值为0x000000b2,我们的标志DECIMAL_MASK0x020x000000b2 & 0x02的计算结果为true,因此²在python中被视为有效的Unicode字母数字字符,因此\wu标志匹配²

  2. # 2 楼答案

    还有一个步骤:您需要指定\w也包含unicode字符Pattern.UNICODE_CHARACTER_CLASS救援:

        Pattern regex = Pattern.compile("(?u)\\b\\w\\w+\\b", Pattern.UNICODE_CHARACTER_CLASS);
                                                       // ^^^^^^^^^^
        Matcher matcher = regex.matcher("this is the document.!? äöa m²");
    
        while(matcher.find()) {
            String match = matcher.group();
            System.out.println(match);
        }