Разделенная строка Java на куски по 1024 байта - PullRequest
7 голосов
/ 06 февраля 2009

Какой эффективный способ разбить строку на куски по 1024 байта в Java? Если имеется более одного фрагмента, тогда заголовок (строка фиксированного размера) должен повторяться во всех последующих фрагментах.

Ответы [ 5 ]

8 голосов
/ 06 февраля 2009

У вас есть два способа: быстрый и консервативный. Но сначала вам нужно знать, какие символы находятся в строке. 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));

Обратите внимание, что String является Unicode, поэтому каждому символу требуется два байта. Вам нужно будет указать кодировку (не полагайтесь на «платформу по умолчанию». Это только потом вызовет боль).

Теперь вы можете прочитать его в 1024 кусках, используя

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) { ... }

Для этого требуется примерно в три раза больше оперативной памяти, чем для исходной строки.

Более консервативный способ памяти - написать конвертер, который принимает 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 ...

Для этого требуется всего несколько килобайт оперативной памяти.

[РЕДАКТИРОВАТЬ] В комментариях было долгое обсуждение двоичных данных в строках. Прежде всего, совершенно безопасно помещать двоичные данные в строку, если вы осторожны при их создании и хранении где-либо. Чтобы создать такую ​​строку, возьмите массив byte [] и:

String safe = new String (array, "iso-8859-1");

В Java ISO-8859-1 (a.k.a 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 () сгенерировал бы исключение, потому что нет способа выразить все символы Unicode в ISO-Latin1, так что вы в безопасности в том смысле, что код не потерпит молчания.

Некоторые могут утверждать, что это недостаточно безопасно, и вы никогда не должны смешивать байты и строку. В наши дни, у нас нет такой роскоши. Многие данные не имеют явной информации о кодировании (например, файлы не имеют атрибута «кодировка» так же, как они имеют права доступа или имя). XML - один из немногих форматов, который имеет явную информацию о кодировке, и есть редакторы, такие как Emacs или jEdit, которые используют комментарии для указания этой важной информации. Это означает, что при обработке потоков байтов вы всегда должны знать, в какой кодировке они находятся. На данный момент невозможно написать код, который будет работать всегда, независимо от того, откуда поступают данные.

Даже с XML вы должны прочитать заголовок файла в байтах, чтобы определить кодировку, прежде чем сможете декодировать мясо.

Важным моментом является сесть и выяснить, какая кодировка использовалась для генерации потока данных, который вы должны обработать. Если ты делаешь это, ты в порядке, если нет, ты обречен. Путаница возникает из-за того, что большинство людей не знают, что один и тот же байт может означать разные вещи в зависимости от кодировки или даже от того, что существует более одной кодировки. Кроме того, это помогло бы, если бы Sun не ввела понятие «кодировка платформы по умолчанию».

Важные моменты для начинающих:

  • Существует более одной кодировки (кодировки).
  • Есть больше символов, чем использует английский язык. Существует даже несколько наборов цифр (ASCII, полная ширина, арабский-индийский, бенгальский).
  • Вы должны знать, какая кодировка использовалась для генерации данных, которые вы обрабатываете.
  • Вы должны знать, какую кодировку следует использовать для записи данных, которые вы обрабатываете.
  • Вы должны знать правильный способ указания этой информации о кодировке, чтобы следующая программа могла декодировать ваш вывод (заголовок XML, метатег HTML, специальный комментарий кодировки и т. Д.)

Дни ASCII прошли.

5 голосов
/ 06 февраля 2009

Строки и байты - это две совершенно разные вещи, поэтому желание разбить строку на байты так же бессмысленно, как желание разбить картину на стихи.

Что вы на самом деле хотите сделать?

Для преобразования между строками и байтами необходимо указать кодировку, которая может кодировать все символы в строке. В зависимости от кодировки и символов некоторые из них могут занимать более одного байта.

Вы можете разделить строку на блоки по 1024 символа и закодировать их как байты, но тогда каждый блок может быть больше 1024 байта.

Или вы можете закодировать исходную строку в байты, а затем разбить их на куски по 1024, но затем вы должны обязательно добавить их как байты, прежде чем снова декодировать целое в строку, или вы можете получить искаженные символы в точки разделения, когда символ занимает более 1 байта.

Если вы беспокоитесь об использовании памяти, когда String может быть очень длинным, вам следует использовать потоки (пакет java.io) для en / decoding и split, чтобы избежать несколькихкратного хранения данных в памяти. копии. В идеале, вам следует избегать того, чтобы исходная строка вообще была в одном фрагменте, и вместо этого использовать потоки, чтобы читать ее небольшими порциями, откуда бы вы ее не брали.

3 голосов
/ 15 июня 2015

Я знаю, что опоздал, однако я сам искал решение, а затем нашел свой ответ как лучший ответ:

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 голосов
/ 16 июня 2019

Да, большинство, если не все вышеперечисленное, безусловно, будет работать.

Или вы можете проверить этот проект, который делает именно это; только он может разбивать на блоки не только строки, но и байтовые массивы, входные потоки и файлы.

Имеется 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, либо InputStream

0 голосов
/ 10 августа 2018

Я пробовал это для себя, мне нужно разбить огромную строку (почти 10 МБ) на 1 МБ. Это помогает разбить данные на части за минимальное время. (менее секунды).

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;
}

Logcat:

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
...