【问题标题】:Java Unicode String lengthJava Unicode 字符串长度
【发布时间】:2013-04-03 14:26:07
【问题描述】:

我正在努力获取 unicode 字符串的数量并尝试了各种选项。看起来是个小问题,但影响很大。

这里我试图获取字符串 str1 的长度。我得到它为 6。但实际上它是 3。将光标移到字符串“குமார்”上也将其显示为 3 个字符。

基本上我想测量长度并打印每个字符。比如“கு”、“மா”、“ர்”。

 public class one {
    public static void main(String[] args) {
            String str1 = new String("குமார்");
            System.out.print(str1.length());
    }
}

PS:这是泰米尔语。

【问题讨论】:

标签: java string utf-8 utf-16 unicode-string


【解决方案1】:

这是一种考虑 Unicode 字符来计算 Java 字符串长度的新方法。

int unicodeLength = str.codePointCount(0, str.length);

【讨论】:

  • 3 个字符的泰米尔语字符串给出了 6 个代码点,如果您使用 codePointCount()codePoints() 查看它,结果与 str.length() 相同。但是,它可能适用于其他语言。 (我相信这是代码点的意图。)
【解决方案2】:

看看Normalizer 类。有一个解释可能是您的问题的原因。在 Unicode 中,您可以通过多种方式对字符进行编码,例如 Á:

  U+00C1    LATIN CAPITAL LETTER A WITH ACUTE

  U+0041    LATIN CAPITAL LETTER A
  U+0301    COMBINING ACUTE ACCENT

您可以尝试使用Normalizer 将您的字符串转换为组合形式,然后遍历字符。


编辑:根据上面@halex 建议的文章,用Java 试试这个:

    String str = new String("குமார்");

    ArrayList<String> characters = new ArrayList<String>();
    str = Normalizer.normalize(str, Form.NFC);
    StringBuilder charBuffer = new StringBuilder();
    for (int i = 0; i < str.length(); i++) {
        int codePoint = str.codePointAt(i);
        int category = Character.getType(codePoint);
        if (charBuffer.length() > 0
                && category != Character.NON_SPACING_MARK
                && category != Character.COMBINING_SPACING_MARK
                && category != Character.CONTROL
                && category != Character.OTHER_SYMBOL) {
            characters.add(charBuffer.toString());
            charBuffer.delete(0, charBuffer.length());
        }
        charBuffer.appendCodePoint(codePoint);
    }
    if (charBuffer.length() > 0) {
        characters.add(charBuffer.toString());
    }
    System.out.println(characters);

我得到的结果是[கு, மா, ர்]。如果它不适用于所有字符串,请尝试使用 if 块中的其他 Unicode 字符类别。

【讨论】:

  • 尝试标准化字符串并测量长度。还是得到6。如果浏览器编辑器可以通过光标导航将其识别为3个字符,我们在java中没有标准方法来获取它吗?
  • 在这种情况下是不正确的,但是对于其他问题来说是一个很好的提示。 +1
  • 文章还提到了“KSha”、“Sri”和“Ayudham”。我想这些必须作为特殊情况处理。
  • 规范化是当您的字符串中的每个字母都有一个预先组合的字母时的解决方案。预先组合的字母在 Unicode 中非常罕见,并且几乎完全以拉丁字母存在(主要是为了与传统的非 Unicode 编码的往返兼容)。
  • 我认为字符排序可能存在问题。我检查了排序算法,你是对的,标准化是多余的。
【解决方案3】:

找到解决问题的方法。

基于this SO answer,我制作了一个程序,它使用正则表达式字符类来搜索可能具有可选修饰符的字母。它将您的字符串拆分为单个(必要时组合)字符并将它们放入列表中:

import java.util.*;
import java.lang.*;
import java.util.regex.*;

class Main
{
    public static void main (String[] args)
    {
        String s="குமார்";
        List<String> characters=new ArrayList<String>();
        Pattern pat = Pattern.compile("\\p{L}\\p{M}*");
        Matcher matcher = pat.matcher(s);
        while (matcher.find()) {
            characters.add(matcher.group());            
        }

        // Test if we have the right characters and length
        System.out.println(characters);
        System.out.println("String length: " + characters.size());

    }
}

其中\\p{L} 表示Unicode 字母,\\p{M} 表示Unicode 标记。

sn-p的输出是:

கு
மா
ர்
String length: 3

请参阅https://ideone.com/Apkapn 以获得有效的演示


编辑

我现在用从http://en.wikipedia.org/wiki/Tamil_script 的表格中提取的所有有效泰米尔语字母检查了我的正则表达式。我发现使用当前的正则表达式无法正确捕获所有字母(Grantha 复合表最后一行中的每个字母都被拆分为两个字母),因此我将正则表达式改进为以下解决方案:

Pattern pat = Pattern.compile("\u0B95\u0BCD\u0BB7\\p{M}?|\\p{L}\\p{M}?");

使用此模式而不是上述模式,您应该能够将您的句子拆分为每个有效的泰米尔语字母(只要维基百科的表格完整)。

我用来检查的代码如下:

String s = "ஃஅஆஇஈஉஊஎஏஐஒஓஔக்ககாகிகீகுகூகெகேகைகொகோகௌங்ஙஙாஙிஙீஙுஙூஙெஙேஙைஙொஙோஙௌச்சசாசிசீசுசூசெசேசைசொசோசௌஞ்ஞஞாஞிஞீஞுஞூஞெஞேஞைஞொஞோஞௌட்டடாடிடீடுடூடெடேடைடொடோடௌண்ணணாணிணீணுணூணெணேணைணொணோணௌத்ததாதிதீதுதூதெதேதைதொதோதௌந்நநாநிநீநுநூநெநேநைநொநோநௌப்பபாபிபீபுபூபெபேபைபொபோபௌம்மமாமிமீமுமூமெமேமைமொமோமௌய்யயாயியீயுயூயெயேயையொயோயௌர்ரராரிரீருரூரெரேரைரொரோரௌல்லலாலிலீலுலூலெலேலைலொலோலௌவ்வவாவிவீவுவூவெவேவைவொவோவௌழ்ழழாழிழீழுழூழெழேழைழொழோழௌள்ளளாளிளீளுளூளெளேளைளொளோளௌற்றறாறிறீறுறூறெறேறைறொறோறௌன்னனானினீனுனூனெனேனைனொனோனௌஶ்ஶஶாஶிஶீஶுஶூஶெஶேஶைஶொஶோஶௌஜ்ஜஜாஜிஜீஜுஜூஜெஜேஜைஜொஜோஜௌஷ்ஷஷாஷிஷீஷுஷூஷெஷேஷைஷொஷோஷௌஸ்ஸஸாஸிஸீஸுஸூஸெஸேஸைஸொஸோஸௌஹ்ஹஹாஹிஹீஹுஹூஹெஹேஹைஹொஹோஹௌக்ஷ்க்ஷக்ஷாக்ஷிக்ஷீக்ஷுக்ஷூக்ஷெக்ஷேக்ஷைஷொக்ஷோஷௌ";
List<String> characters = new ArrayList<String>();
Pattern pat = Pattern.compile("\u0B95\u0BCD\u0BB7\\p{M}?|\\p{L}\\p{M}?");
Matcher matcher = pat.matcher(s);
while (matcher.find()) {
    characters.add(matcher.group());
}

System.out.println(characters);
System.out.println(characters.size() == 325);

【讨论】:

  • 是的,我不知道它是否能处理泰米尔语中可能发生的所有情况,但它绝对是优雅的。
  • 一组漂亮的字母!
  • 非常感谢。是的,你是对的。只有 Grantha 表中的最后一行由两个字母组成。即 3 - 4 个 unicode 符号。您在维基百科中引用的表格是正确的。这是完整的列表。
  • 如果我必须包含像“_”这样的标点符号,那么正则表达式应该是什么。例如“குமார_கு”应返回计数 5。
  • @user1611248 将|\\p{P} 添加到正则表达式。 \\p{P} 是标点符号。见ideone.com/NvfDDq
【解决方案4】:

事实证明这真的很丑...... 我已经调试了您的字符串,它包含以下字符(及其十六进制位置):

க 0x0b95
ு 0x0bc1
ம 0x0bae
ா 0x0bbe
ர 0x0bb0
் 0x0bcd

所以泰米尔语显然使用类似变音符号的序列来获得 不幸的是,所有字符都被视为单独的实体。

这不是 UTF-8 / UTF-16 错误声称的问题 其他答案,它是泰米尔语的 Unicode 编码所固有的 语言。

建议的 Normalizer 不起作用,泰米尔语似乎有 由 Unicode“专家”设计,明确使用组合 无法归一化的序列。啊。

我的下一个想法不是计算字符,而是字形,视觉 字符的表示。

String str1 = new String(Normalizer.normalize("குமார்", Normalizer.Form.NFC ));

Font display = new Font("SansSerif",Font.PLAIN,12);
GlyphVector vec = display.createGlyphVector(new FontRenderContext(new AffineTransform(),false, false),str1);

System.out.println(vec.getNumGlyphs());
for (int i=0; i<str1.length(); i++)
        System.out.printf("%s %s %s %n",str1.charAt(i),Integer.toHexString((int) str1.charAt(i)),vec.getGlyphVisualBounds(i).getBounds2D().toString());

结果:

க b95 [x=0.0,y=-6.0,w=7.0,h=6.0]
ு bc1 [x=8.0,y=-6.0,w=7.0,h=4.0]
ம bae [x=17.0,y=-6.0,w=6.0,h=6.0]
ா bbe [x=23.0,y=-6.0,w=5.0,h=6.0]
ர bb0 [x=30.0,y=-6.0,w=4.0,h=8.0]
் bcd [x=31.0,y=-9.0,w=1.0,h=2.0]

由于字形相交,需要使用 Java 字符类型 功能与其他解决方案类似。

解决方案:

我正在使用这个链接:http://www.venkatarangan.com/blog/content/binary/Counting%20Letters%20in%20an%20Unicode%20String.pdf

public static int getTamilStringLength(String tamil) {
    int dependentCharacterLength = 0;
    for (int index = 0; index < tamil.length(); index++) {
        char code = tamil.charAt(index);
        if (code == 0xB82)
            dependentCharacterLength++;
        else if (code >= 0x0BBE && code <= 0x0BC8)
            dependentCharacterLength++;
        else if (code >= 0x0BCA && code <= 0x0BD7)
            dependentCharacterLength++;
    }
    return tamil.length() - dependentCharacterLength;
  }

您需要排除组合字符并相应地计数。

【讨论】:

    【解决方案5】:

    如前所述,您的字符串包含 6 个不同的代码点。其中一半是字母,另一半是元音符号。 (组合标记)

    您可以使用 ICU4J 库中内置的transformations,使用规则删除所有不是字母的元音符号:

    [:^字母:] 删除

    并计算结果字符串。在他们的演示网站上试用:

    http://demo.icu-project.org/icu-bin/translit

    我不会将结果字符串显示给最终用户,而且我不是专家,因此可能需要调整规则以适应一般情况,但这是一个想法。

    【讨论】:

    • 是6个字符还是3个字符,完全取决于你对“字符”的定义。不幸的是,这个词没有很好的定义,并且以各种不兼容的方式使用。只有当您将“字符”表示为“代码点”时,您的陈述才是正确的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-15
    • 2013-05-07
    • 1970-01-01
    • 2019-09-23
    • 2011-12-04
    相关资源
    最近更新 更多