在java中将String拆分为1024字节块的有效方法是什么?如果有多个块,则需要在所有后续块中重复标头(固定大小的字符串)。
5 回答
您有两种方式,快速方式和记忆保守方式。但首先,您需要知道字符串中有哪些字符。ASCII?是否有变音符号(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() 并将临时缓冲区写入缓冲。
代码如下所示(未经测试):
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。
[编辑] 在评论中对字符串中的二进制数据进行了长时间的讨论。首先,将二进制数据放入 String 是完全安全的,只要您在创建它并将其存储在某个地方时要小心。要创建这样的字符串,请使用 byte[] 数组并:
String safe = new String (array, "iso-8859-1");
在 Java 中,ISO-8859-1(又名 ISO-Latin1)是 1:1 映射。这意味着数组中的字节不会以任何方式被解释。现在您可以对数据使用 substring() 等或使用索引搜索它,对其运行正则表达式等。例如,找到一个 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 元标记、特殊编码注释等)。
ASCII 的时代已经结束。
字符串和字节是两个完全不同的东西,所以想把一个字符串分割成字节就和想把一幅画分割成诗一样毫无意义。
你真正想做的是什么?
要在字符串和字节之间进行转换,您需要指定一种可以对 String 中的所有字符进行编码的编码。根据编码和字符,其中一些可能跨越一个字节以上。
您可以将字符串拆分为 1024 个字符的块并将它们编码为字节,但是每个块可能超过 1024 个字节。
或者您可以将原始字符串编码为字节,然后将它们拆分为 1024 的块,但是您必须确保在再次将整个字符串解码为字符串之前将它们附加为字节,否则您可能会在拆分点出现乱码一个字符跨越超过 1 个字节。
如果您担心String可能很长时内存使用情况,您应该使用流(java.io包)来进行en/decode和split,以避免将数据多次保存在内存中作为副本。理想情况下,您应该完全避免将原始字符串放在一个片段中,而是使用流从您获取它的任何地方以小块的形式读取它。
我知道我迟到了,但是我自己在寻找解决方案,然后发现我的答案是最佳答案:
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
我正在为自己尝试这个,我需要将一个巨大的字符串(近 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
是的,如果不是全部,上述大多数肯定会起作用。
或者你可以看看这个项目就是这样做的;只有它不仅可以分块字符串,还可以分块字节数组、输入流和文件。
它有 2 个类:DataChunker
和StringChunker
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());
}
};
构造函数blob
构造Datachunker's
函数中的 要么是字节数组,要么是File
aInputStream