【问题标题】:Splitting a string with byte length limits in java在java中拆分具有字节长度限制的字符串
【发布时间】:2018-02-19 14:55:06
【问题描述】:

我想将String 拆分为String[] 数组,其元素满足以下条件。

  • s.getBytes(encoding).length 不应超过maxsize(int)

  • 如果我用StringBuilder+ 运算符加入拆分后的字符串,结果应该是原始字符串。

  • 输入字符串可能有 unicode 字符,在编码时可以有多个字节,例如UTF-8。

所需的原型如下所示。

public static String[] SplitStringByByteLength(String src,String encoding, int maxsize)

以及测试代码:

public boolean isNice(String str, String encoding, int max)
{
    //boolean success=true;
    StringBuilder b=new StringBuilder();
    String[] splitted= SplitStringByByteLength(str,encoding,max);
    for(String s: splitted)
    {
        if(s.getBytes(encoding).length>max)
            return false;
        b.append(s);
    }
    if(str.compareTo(b.toString()!=0)
        return false;
    return true;
}

虽然输入字符串只有 ASCII 字符时看起来很容易,但它可以包含多字节字符的事实让我感到困惑。

提前谢谢你。

编辑:我添加了我的代码实现。 (低效)

public static String[] SplitStringByByteLength(String src,String encoding, int maxsize) throws UnsupportedEncodingException
{
    ArrayList<String> splitted=new ArrayList<String>();
    StringBuilder builder=new StringBuilder();
    //int l=0;
    int i=0;
    while(true)
    {
        String tmp=builder.toString();
        char c=src.charAt(i);
        if(c=='\0')
            break;
        builder.append(c);
        if(builder.toString().getBytes(encoding).length>maxsize)
        {
            splitted.add(new String(tmp));
            builder=new StringBuilder();
        }
        ++i;
    }
    return splitted.toArray(new String[splitted.size()]);
}

这是解决这个问题的唯一方法吗?

【问题讨论】:

  • 为什么你甚至转换为字节而不是在字符基础上工作?
  • 您应该能够只计算字符数,然后在该索引处拆分。
  • 因为我必须将字符串发送到另一个接受 UTF-8 VNOTE 文件的设备,并且它无法处理超过 400 字节的字符串。所以我必须将我的字符串拆分成不超过 400 字节的字符串。
  • 我很困惑,你想在空终止符上拆分还是在空格上拆分?
  • 由于当前的代码并不能真正满足完整的要求,我不确定它是否真的离题了。

标签: java string character-encoding


【解决方案1】:

CharsetEncode 类可满足您的要求。摘自Encode方法的Javadoc:

public final CoderResult encode(CharBuffer in,
                            ByteBuffer out,
                            boolean endOfInput)

从给定的输入缓冲区编码尽可能多的字符,将结果写入给定的输出缓冲区...

该方法除了从输入缓冲区读取字符并将字节写入输出缓冲区外,还返回一个 CoderResult 对象来描述其终止原因:

...

CoderResult.OVERFLOW 表示输出缓冲区中没有足够的空间来编码更多字符。应该使用具有更多剩余字节的输出缓冲区再次调用此方法。这通常是通过从输出缓冲区中排出任何编码字节来完成的。

可能的代码是:

public static String[] SplitStringByByteLength(String src,String encoding, int maxsize) {
    Charset cs = Charset.forName(encoding);
    CharsetEncoder coder = cs.newEncoder();
    ByteBuffer out = ByteBuffer.allocate(maxsize);  // output buffer of required size
    CharBuffer in = CharBuffer.wrap(src);
    List<String> ss = new ArrayList<>();            // a list to store the chunks
    int pos = 0;
    while(true) {
        CoderResult cr = coder.encode(in, out, true); // try to encode as much as possible
        int newpos = src.length() - in.length();
        String s = src.substring(pos, newpos);
        ss.add(s);                                  // add what has been encoded to the list
        pos = newpos;                               // store new input position
        out.rewind();                               // and rewind output buffer
        if (! cr.isOverflow()) {
            break;                                  // everything has been encoded
        }
    }
    return ss.toArray(new String[0]);
}

这会将原始字符串拆分成块,当以字节编码时,这些块将尽可能适合给定大小的字节数组(当然假设 maxsize 不是小得离谱)。

【讨论】:

    【解决方案2】:

    问题在于存在 Unicode“补充字符”(参见 Character 类的 Javadoc),它们在字符串中占据了两个“字符位置”(代理对),您不应该将字符串拆分为这样一对的中间。

    一种简单的拆分方法是坚持最坏的情况,即单个 Unicode 代码点在 UTF-8 中最多可以占用四个字节,并在每 99 个代码点之后拆分字符串(使用 string.offsetByCodePoints(pos, 99) )。在大多数情况下,您不会填充 400 个字节,但您会比较安全。


    关于代码点和字符的一些话

    当 Java 开始时,Unicode 的字符数少于 65536,因此 Java 决定 16 位足以容纳一个字符。后来 Unicode 标准超过了 16 位的限制,Java 出现了一个问题:单个 Unicode 元素(现在称为“代码点”)不再适合单个 Java 字符。

    他们决定对 16 位实体进行编码,对于大多数常见的代码点是 1:1,并为超出 16 位限制的奇异代码点占用两个“字符”(从 so-从低于 65535 的备用代码范围中称为“代理字符”)。所以现在它可能会发生,例如string.charAt(5)string.charAt(6) 必须组合在一起,作为“代理对”,一起编码一个 Unicode 代码点。

    这就是为什么您不应该在任意索引处拆分字符串的原因。

    为了帮助应用程序程序员,String 类获得了一组新的方法,在代码点单元中工作,例如string.offsetByCodePoints(pos, 99) 表示:从索引 pos 向前推进 99 个代码点,给出的索引通常为 pos+99(如果字符串不包含任何外来内容),但可能高达 pos+198 , 如果以下所有字符串元素恰好是代理对。

    使用代码点方法,您可以安全地不要落在代理对中间。

    【讨论】:

    • 嗯。我想你明白了。但我对 string.offsetByCodePoints(pos, 99) 没有任何基本概念。能否详细解释一下方法和代码点?
    • 感谢您快速而亲切的回答,但恐怕我在下面得到了更有效的答案。抱歉反转。无论如何,我感谢您的帮助!
    • 我完全同意。 Serge 的回答比我的要好,我投了赞成票。
    猜你喜欢
    • 1970-01-01
    • 2019-02-18
    • 2012-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-23
    相关资源
    最近更新 更多