在java

时间:2018-02-19 14:55:07

标签: java string character-encoding

我想将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()]);
}

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

2 个答案:

答案 0 :(得分:6)

班级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不是很小)。

答案 1 :(得分:2)

问题在于存在Unicode&#34;补充字符&#34; (参见角色类的Javadoc),它占据了两个&#34;角色位置&#34;字符串中的(代理对),你不应该在这样的一对中拆分你的字符串。

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

关于代码点和字符的一些说法

当Java启动时,Unicode的字符数少于65536个,因此Java认为16位对于一个字符就足够了。后来Unicode标准超过了16位限制,Java出现了问题:单个Unicode元素(现在称为&#34;代码点&#34;)不再适合单个Java字符。

他们决定对16位实体进行编码,对于大多数常用代码点为1:1,占用两个&#34;字符&#34;对于超出16位限制的异域代码点(由所谓的&#34;代理字符&#34;从低于65535的备用代码范围构建的对)。所以现在它可能发生在例如string.charAt(5)string.charAt(6)必须组合使用,作为代理对&#34;,共同编码一个Unicode代码点。

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

为了帮助应用程序员,String类得到了一组新的方法,在代码点单元中工作,例如string.offsetByCodePoints(pos, 99)表示:从索引pos开始,前进99个代码点,给出一个通常为pos+99的索引(如果字符串不包含任何异国情况),但如果所有以下字符串元素恰好是代理对,则可能最多为pos+198

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