在Java中,将字符串有效地分成每个1024字节的块的方法是什么?如果有多个块,则需要在所有后续块中重复头部(固定大小的字符串)。
在Java中,将字符串有效地分成每个1024字节的块的方法是什么?如果有多个块,则需要在所有后续块中重复头部(固定大小的字符串)。
你有两种方法,一种是快速的,一种是内存保守的。但首先,你需要知道字符串中有哪些字符。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));
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 ...
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 没有引入“平台默认编码”的概念,这也将有所帮助。字符串和字节是两个完全不同的东西,所以想将字符串拆分成字节就像想把一幅画拆分成诗句一样没有意义。
你真正想做什么?
要在字符串和字节之间进行转换,您需要指定一个能够编码字符串中所有字符的编码方式。根据编码方式和字符,有些字符可能会跨越多个字节。
您可以将字符串拆分为每个包含1024个字符的块,并将其编码为字节,但每个块可能超过1024个字节。
或者,您可以将原始字符串编码为字节,然后将它们拆分为每个包含1024个字节的块,但是在将整个字节流解码为字符串时,您必须确保在解码之前将它们作为字节附加在一起,否则当一个字符跨越多个字节时,在拆分点可能会出现乱码。
如果您担心字符串长度很长时的内存使用情况,您应该使用流(java.io包)来进行编解码和拆分,以避免多次复制数据并将其保存在内存中。理想情况下,您应该根本不将原始字符串作为一个整体,而是使用流从任何地方以小块读取它。
我知道我来晚了,不过我自己也在寻找解决方案,后来发现这个答案是最好的:
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
是的,大多数情况下以上所有方法都可以使用。
或者您可以查看this项目,它正是这样做的;它不仅可以分块字符串,还可以分块字节数组、输入流和文件。
它有两个类: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());
}
};
Datachunker
构造函数中的 blob
可以是字节数组、File
或者 InputStream