将Java字符串分割为每个1024字节的块

8

在Java中,将字符串有效地分成每个1024字节的块的方法是什么?如果有多个块,则需要在所有后续块中重复头部(固定大小的字符串)。


1
只是想确认您是否知道在Java中,字符串由字符而不是字节组成。一个字符可能由多个字节组成。 - mparaz
谢谢,我非常清楚这一点。但是你可以使用String.getBytes()方法获取字符串的相应byte[]。例如,当您想通过网络发送字符串内容时,这是一个常见的问题。 - user54729
你为什么需要精确地重复标题? - Richard Campbell
5个回答

10

你有两种方法,一种是快速的,一种是内存保守的。但首先,你需要知道字符串中有哪些字符。ASCII?是否有umlauts(128到255之间的字符),甚至Unicode(s.getChar()返回> 256的内容)。根据这个,你需要使用不同的编码。如果你有二进制数据,请尝试使用"iso-8859-1",因为它将保留字符串中的数据。如果你有Unicode,请尝试使用"utf-8"。我假设你有二进制数据:

String encoding = "iso-8859-1";

最快的方式:

ByteArrayInputStream in = new ByteArrayInputStream (string.getBytes(encoding));

请注意,字符串是Unicode编码的,因此每个字符需要两个字节。您需要指定编码(不要依赖于“平台默认值”)。否则,这将在以后带来麻烦。
现在,您可以使用1024块读取它。
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) { ... }

这需要比原始字符串多约三倍的RAM。

一种更节省内存的方法是编写一个转换器,它接受一个StringReader和一个OutputStreamWriter(它包装了一个ByteArrayOutputStream)。从读取器复制字节到写入器,直到底层缓冲区包含一块数据:

当它这样做时,将数据复制到实际输出(前置标题),将额外的字节(Unicode->字节转换可能生成的字节)复制到临时缓冲区中,调用buffer.reset()并将临时缓冲区写入buffer。

代码看起来像这样(未经测试):

StringReader r = new StringReader (string);
ByteArrayOutputStream buffer = new ByteArrayOutputStream (1024*2); // Twice as large as necessary
OutputStreamWriter w = new OutputStreamWriter  (buffer, encoding);

char[] cbuf = new char[100];
byte[] tempBuf;
int len;
while ((len = r.read(cbuf, 0, cbuf.length)) > 0) {
    w.write(cbuf, 0, len);
    w.flush();
    if (buffer.size()) >= 1024) {
        tempBuf = buffer.toByteArray();
        ... ready to process one chunk ...
        buffer.reset();
        if (tempBuf.length > 1024) {
            buffer.write(tempBuf, 1024, tempBuf.length - 1024);
        }
    }
}
... check if some data is left in buffer and process that, too ...

这只需要几千字节的RAM。
[编辑]在评论中有关于字符串中二进制数据的讨论。首先,只要您在创建和存储时小心,将二进制数据放入字符串是完全安全的。要创建这样的字符串,请取一个byte[]数组并:
String safe = new String (array, "iso-8859-1");

在Java中,ISO-8859-1(又称为ISO-Latin1)是一种1:1映射。这意味着数组中的字节不会以任何方式进行解释。现在您可以在数据上使用substring()等函数或使用index搜索它,运行正则表达式等操作。例如,查找0字节的位置:

int pos = safe.indexOf('\u0000');

如果您不知道数据的编码并且想在某些编解码器对其进行处理之前查看它,则此方法特别有用。

要将数据写入某个位置,反向操作为:

byte[] data = safe.getBytes("iso-8859-1");

永远不要使用默认方法new String(array)String.getBytes()有一天,您的代码将在不同的平台上执行,并且会出现错误。

现在是字符串中字符>255的问题。如果您使用此方法,则您的字符串中不会有任何这样的字符。也就是说,如果由于某种原因存在任何这样的字符,则getBytes()会抛出异常,因为无法用ISO-Latin1表示所有Unicode字符,因此从这个意义上讲,您的代码是安全的,不会默默地失败。

有人可能会认为这还不够安全,您永远不应该混合字节和字符串。在当前这个时代,我们没有这种奢侈。许多数据没有明确的编码信息(例如文件没有像访问权限或名称那样的“编码”属性)。 XML是少数具有显式编码信息的格式之一,而且有像Emacs或jEdit这样的编辑器,它们使用注释来指定此关键信息。这意味着,在处理字节流时,您必须始终知道它们的编码方式。目前为止,不可能编写代码,无论数据来自何处,都可以始终正常工作。

即使对于XML,您也必须将文件头读取为字节以确定编码方式,然后才能解码其内容。

重要的一点是坐下来弄清楚生成要处理的数据流时使用了哪种编码。如果您这样做了,那么您就很好,如果没有,那么您就注定会失败。混乱源于大多数人不知道同一个字节可能意味着不同的含义,具体取决于编码方式甚至是否存在多种编码方式。此外,如果 Sun 没有引入“平台默认编码”的概念,这也将有所帮助。
初学者需要注意以下几点:
- 存在多种编码(字符集)。 - 有比英语使用更多的字符。甚至有几个数字数字集(ASCII、全角、阿拉伯-印度、孟加拉国)。 - 您必须知道生成要处理的数据时使用的编码方式。 - 您必须知道写入要处理的数据时应使用的编码方式。 - 您必须知道指定此编码信息的正确方法,以便下一个程序可以解码您的输出(XML 标头、HTML meta 标签、特殊编码注释等)。
ASCII 的时代已经过去了。

这会遇到kdgregory提到的问题吗?根据您的平台默认编码,您可能会将单个字符拆分为两个无意义的部分。 - user54729
请不要使用"iso-8859-1"。请使用"utf8"。UTF8可以在一个字节中处理几乎所有的iso-8859-1,但可以扩展以处理所有字符。是的,未知情况下,这可能会将单个字符分为两个无意义的部分...或者将它们丢弃,这就是iso-8859-1会做的事情。 - Richard Campbell
理查德:我猜他在那个字符串中有二进制数据,这种情况下 iso-8859-1 是完美的(它不会改变数据)。 - Aaron Digulla
不行,如果你这样做,几乎肯定会破坏你的数据。这是一种可怕的滥用,任何认为自己是专业程序员的人都不应该考虑。说真的,这只是一个非常糟糕的想法。 - Michael Borgwardt
2
请看我的修改。简而言之,虽然通常不建议混合使用字节和 Unicode,但有时候是必须的。例如,在解析器中解码 XML 时,必须将标头作为字节读取以确定编码。结论:如果你不知道自己在做什么,它会出问题。 - Aaron Digulla
显示剩余12条评论

5

字符串和字节是两个完全不同的东西,所以想将字符串拆分成字节就像想把一幅画拆分成诗句一样没有意义。

你真正想做什么?

要在字符串和字节之间进行转换,您需要指定一个能够编码字符串中所有字符的编码方式。根据编码方式和字符,有些字符可能会跨越多个字节。

您可以将字符串拆分为每个包含1024个字符的块,并将其编码为字节,但每个块可能超过1024个字节。

或者,您可以将原始字符串编码为字节,然后将它们拆分为每个包含1024个字节的块,但是在将整个字节流解码为字符串时,您必须确保在解码之前将它们作为字节附加在一起,否则当一个字符跨越多个字节时,在拆分点可能会出现乱码。

如果您担心字符串长度很长时的内存使用情况,您应该使用流(java.io包)来进行编解码和拆分,以避免多次复制数据并将其保存在内存中。理想情况下,您应该根本不将原始字符串作为一个整体,而是使用流从任何地方以小块读取它。


3

我知道我来晚了,不过我自己也在寻找解决方案,后来发现这个答案是最好的:

private static String chunk_split(String original, int length, String separator) throws IOException {
    ByteArrayInputStream bis = new ByteArrayInputStream(original.getBytes());
    int n = 0;
    byte[] buffer = new byte[length];
    String result = "";
    while ((n = bis.read(buffer)) > 0) {
        for (byte b : buffer) {
            result += (char) b;
        }
        Arrays.fill(buffer, (byte) 0);
        result += separator;
    }
    return result;
}

例子:

public static void main(String[] args) throws IOException{
       String original = "abcdefghijklmnopqrstuvwxyz";
       System.out.println(chunk_split(original,5,"\n"));
}

输出:

abced
fghij
klmno
pqrst
uvwxy
z

0

我自己尝试了一下,需要将一个巨大的字符串(近10 MB)按1 MB进行分块。 这有助于在最短的时间内对数据进行分块。(少于一秒钟)。

private static ArrayList<String> chunkLogMessage(String logMessage) throws Exception {
    ArrayList<String> messages = new ArrayList<>();
    if(logMessage.getBytes().length > CHUNK_SIZE) {
        Log.e("chunk_started", System.currentTimeMillis()+"");
        byte[] buffer = new byte[CHUNK_SIZE];
        int start = 0, end = buffer.length;
        long remaining = logMessage.getBytes().length;
        ByteArrayInputStream inputStream = new ByteArrayInputStream(logMessage.getBytes());
        while ((inputStream.read(buffer, start, end)) != -1){
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            outputStream.write(buffer, start, end);
            messages.add(outputStream.toString("UTF-8"));
            remaining = remaining - end;
            if(remaining <= end){
                end = (int) remaining;
            }
        }
        Log.e("chunk_ended", System.currentTimeMillis()+"");
        return messages;
    }
    messages.add(logMessage);
    return messages;
}

日志记录:

22:08:00.262 3382-3425/com.sample.app E/chunk_started: 1533910080261
22:08:01.228 3382-3425/com.sample.app E/chunk_ended: 1533910081228
22:08:02.468 3382-3425/com.sample.app E/chunk_started: 1533910082468
22:08:03.478 3382-3425/com.sample.app E/chunk_ended: 1533910083478
22:09:19.801 3382-3382/com.sample.app E/chunk_started: 1533910159801
22:09:20.662 3382-3382/com.sample.app E/chunk_ended: 1533910160662

0

是的,大多数情况下以上所有方法都可以使用。

或者您可以查看this项目,它正是这样做的;它不仅可以分块字符串,还可以分块字节数组、输入流和文件。

它有两个类:DataChunkerStringChunker


DataChunker chunker = new DataChunker(8192, blob) {
@Override 
public void chunkFound(byte[] foundChunk, int bytesProcessed) {
//process chunk here
}
@Override 
public void chunksExhausted(int bytesProcessed) { 
//called when all the blocks have been exhausted
} 
};


String blob = "Experience is wasted if history does not repeat itself...Gbemiro Jiboye";

 final StringBuilder builder = new StringBuilder();
        StringChunker chunker = new StringChunker(4, blob) {
            @Override
            public void chunkFound(String foundChunk, int bytesProcessed) {
                builder.append(foundChunk);
                System.out.println("Found: "+foundChunk+", bytesProcessed: "+bytesProcessed+" bytes");
            }

            @Override
            public void chunksExhausted(int bytesProcessed) {
                System.out.println("Processed all of: "+bytesProcessed+" bytes. Rebuilt string is: "+builder.toString());
            }
        };

Datachunker 构造函数中的 blob 可以是字节数组、File 或者 InputStream


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接