2010-08-03 3 views
2

Позвольте мне предисловие к этому, сказав, что я довольно новичок в Java.Java: вставьте новую строку после каждого 309-го символа

У меня есть файл, который содержит одну строку. Размер файла составляет около 200 МБ. Мне нужно вставить символ новой строки после каждого 309-го символа. Я считаю, что у меня есть код, чтобы сделать это правильно, но я все время сталкиваюсь с ошибками памяти. Я пробовал увеличивать пространство кучи безрезультатно.

Есть ли менее интенсивный в памяти способ обращения с этим?

BufferedReader r = new BufferedReader(new FileReader(fileName)); 

String line; 

while ((line=r.readLine()) != null) { 
    System.out.println(line.replaceAll("(.{309})", "$1\n")); 
} 
+0

Просто комментируя часть регулярного выражения (это НЕ лучший способ решить эту проблему): группа 1 не нужна в этих случаях. Вы можете обратиться к группе 0, например. 'replaceAll (". {309} "," $ 0 \ n ")' вместо этого. – polygenelubricants

+0

Должна быть стандартная утилита Unix для этого, нет? Что-то вроде 'columnify 309 text> out'? В любом случае, я думаю, что Java слишком многословна для чего-то подобного. – polygenelubricants

+1

@poly: Я действительно использовал регулярное выражение из этого кода sed, который использовал: sed 's/\ (. \ {309 \} \)/\ 1 \ n/g' file.txt> file_parsed.txt Мы начали использовать инструмент Talend ETL, поэтому я хотел иметь возможность сделать это на Java. – Jesse

ответ

15

Ваш код имеет две проблемы:

  1. Вы нагружает весь файл в памяти сразу, предполагая, что это одна линия, так что вы будете нуждаться по крайней мере 200МБ кучного пространства для что; и

  2. Это ужасно неэффективный способ добавления новых строк для использования регулярного выражения. Простое решение кода будет на порядок быстрее.

Обе эти проблемы легко фиксируются.

Используйте FileReader и FileWriter, чтобы загрузить 309 символов за один раз, добавить новую строку и написать их.

Обновление: добавлен тест как посимвольного, так и буферизованного чтения. Буферизованное чтение фактически добавляет много сложности, потому что вам нужно удовлетворить возможную (но обычно чрезвычайно редкую) ситуацию, когда read() возвращает меньше байтов, чем вы запрашиваете и, все еще есть байты для чтения.

Во-первых, простая версия:

private static void charRead(boolean verifyHash) { 
    Reader in = null; 
    Writer out = null; 
    long start = System.nanoTime(); 
    long wrote = 0; 
    MessageDigest md = null; 
    try { 
    if (verifyHash) { 
     md = MessageDigest.getInstance("SHA1"); 
    } 
    in = new BufferedReader(new FileReader(IN_FILE)); 
    out = new BufferedWriter(new FileWriter(CHAR_FILE)); 
    int count = 0; 
    for (int c = in.read(); c != -1; c = in.read()) { 
     if (verifyHash) { 
     md.update((byte) c); 
     } 
     out.write(c); 
     wrote++; 
     if (++count >= COUNT) { 
     if (verifyHash) { 
      md.update((byte) '\n'); 
     } 
     out.write("\n"); 
     wrote++; 
     count = 0; 
     } 
    } 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } catch (NoSuchAlgorithmException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(in); 
    safeClose(out); 
    long end = System.nanoTime(); 
    System.out.printf("Created %s size %,d in %,.3f seconds. Hash: %s%n", 
     CHAR_FILE, wrote, (end - start)/1000000000.0d, hash(md, verifyHash)); 
    } 
} 

И "блок" версия:

private static void blockRead(boolean verifyHash) { 
    Reader in = null; 
    Writer out = null; 
    long start = System.nanoTime(); 
    long wrote = 0; 
    MessageDigest md = null; 
    try { 
    if (verifyHash) { 
     md = MessageDigest.getInstance("SHA1"); 
    } 
    in = new BufferedReader(new FileReader(IN_FILE)); 
    out = new BufferedWriter(new FileWriter(BLOCK_FILE)); 
    char[] buf = new char[COUNT + 1]; // leave a space for the newline 
    int lastRead = in.read(buf, 0, COUNT); // read in 309 chars at a time 
    while (lastRead != -1) { // end of file 
     // technically less than 309 characters may have been read 
     // this is very unusual but possible so we need to keep 
     // reading until we get all the characters we want 
     int totalRead = lastRead; 
     while (totalRead < COUNT) { 
     lastRead = in.read(buf, totalRead, COUNT - totalRead); 
     if (lastRead == -1) { 
      break; 
     } else { 
      totalRead++; 
     } 
     } 

     // if we get -1, it'll eventually signal an exit but first 
     // we must write any characters we have read 
     // note: it is assumed that the trailing number, which may be 
     // less than 309 will still have a newline appended. this may 
     // note be the case 
     if (totalRead == COUNT) { 
     buf[totalRead++] = '\n'; 
     } 
     if (totalRead > 0) { 
     out.write(buf, 0, totalRead); 
     if (verifyHash) { 
      md.update(new String(buf, 0, totalRead).getBytes("UTF-8")); 
     } 
     wrote += totalRead; 
     } 

     // don't try and read again if we've already hit EOF 
     if (lastRead != -1) { 
     lastRead = in.read(buf, 0, 309); 
     } 
    } 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } catch (NoSuchAlgorithmException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(in); 
    safeClose(out); 
    long end = System.nanoTime(); 
    System.out.printf("Created %s size %,d in %,.3f seconds. Hash: %s%n", 
     CHAR_FILE, wrote, (end - start)/1000000000.0d, hash(md, verifyHash)); 
    } 
} 

И метод, чтобы создать тестовый файл:

private static void createFile() { 
    Writer out = null; 
    long start = System.nanoTime(); 
    try { 
    out = new BufferedWriter(new FileWriter(IN_FILE)); 
    Random r = new Random(); 
    for (int i = 0; i < SIZE; i++) { 
     out.write(CHARS[r.nextInt(CHARS.length)]); 
    } 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(out); 
    long end = System.nanoTime(); 
    System.out.printf("Created %s size %,d in %,.3f seconds%n", 
     IN_FILE, SIZE, (end - start)/1000000000.0d); 
    } 
} 

Это все взять на себя:

private static final int SIZE = 200000000; 
private static final int COUNT = 309; 
private static final char[] CHARS; 
private static final char[] BYTES = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 
private static final String IN_FILE = "E:\\temp\\in.dat"; 
private static final String CHAR_FILE = "E:\\temp\\char.dat"; 
private static final String BLOCK_FILE = "E:\\temp\\block.dat"; 

static { 
    char[] chars = new char[1000]; 
    int nchars = 0; 
    for (char c = 'a'; c <= 'z'; c++) { 
    chars[nchars++] = c; 
    chars[nchars++] = Character.toUpperCase(c); 
    } 
    for (char c = '0'; c <= '9'; c++) { 
    chars[nchars++] = c; 
    } 
    chars[nchars++] = ' '; 
    CHARS = new char[nchars]; 
    System.arraycopy(chars, 0, CHARS, 0, nchars); 
} 

Выполнение этого теста:

public static void main(String[] args) { 
    if (!new File(IN_FILE).exists()) { 
    createFile(); 
    } 
    charRead(true); 
    charRead(true); 
    charRead(false); 
    charRead(false); 
    blockRead(true); 
    blockRead(true); 
    blockRead(false); 
    blockRead(false); 
} 

дает этот результат (Intel Q9450, Windows 7 64bit, 8 Гб оперативной памяти, пробный пуск на 7200rpm 1.5TB диск):

Created E:\temp\char.dat size 200,647,249 in 29.690 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 18.177 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 7.911 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 7.867 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 8.018 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 7.949 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 3.958 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 3.909 seconds. Hash: (not calculated) 

Вывод: в SHA1 хеш-проверка действительно дорогая, поэтому я запускал версии с и без. В основном после разогрева «эффективная» версия составляет всего около 2x так же быстро. Наверное, к этому времени файл действительно находится в памяти.

Если я изменить порядок блока и символ читает, результат:

Created E:\temp\char.dat size 200,647,249 in 8.071 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 8.087 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 4.128 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 3.918 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 18.020 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 17.953 seconds. Hash: 0x22ce9e17e17a67e5ea6f8fe929d2ce4780e8ffa4 
Created E:\temp\char.dat size 200,647,249 in 7.879 seconds. Hash: (not calculated) 
Created E:\temp\char.dat size 200,647,249 in 8.016 seconds. Hash: (not calculated) 

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

Так, как обычно, это выбор между эффективностью и простотой.

+0

Удивительно, это сработало отлично! Большое спасибо! – Jesse

2

Откройте его и прочитайте персонажа за раз, и напишите этот персонаж туда, куда ему нужно идти. Держите счетчик и каждый раз, когда счетчик достаточно велик, выпишите новую строку и установите счетчик равным нулю.

+1

-1. Чтение одного символа одновременно будет болезненно медленным. –

+0

Затем заверните его в BufferedReader. Я делал это просто. –

1

Не знаете, насколько лучше это решение, но вы всегда можете прочитать его в персонаже по характеру.

  1. Прочитано 309 символов и пишите в файл. Не уверен, что если вы можете сделать это сразу, или если вы должны сделать это с помощью одного символа в то время
  2. После написания 309-й выводимого символа новой строки в файл
  3. Repeat

Например (используя this сайт):

FileInputStream fis = new FileInputStream(file); 
char current; 
int counter = 0 
    while (fis.available() > 0) { 
     current = (char) fis.read(); 
     counter++; 
     // output current to file 
     if ((counter%309) = 0) { 
     //output newline character 
     } 
    } 
0

обернуть FileReader в BufferedReader, а затем сохранить зацикливание, чтение 309 символов за раз.

Нечто подобное (не проверено):

BufferedReader r = new BufferedReader(new FileReader("yourfile.txt"), 1024); 
boolean done = false; 
char[] buffer = new char[309]; 
while(!done) 
{ 
    int read = r.read(buffer,0,309); 
    if(read > 0) 
    { 
    //write buffer to dfestination, appending newline 
    } 
    else 
    { 
     done = true; 
    } 
} 
1

Не используйте BufferedReader, который будет держать большую часть основного файла в памяти. Используйте FileReader непосредственно, а затем использовать метод read() получить ровно столько информации, сколько вам нужно:

FileReader reader = new FileReader(fileName); 
char[] buffer = new char[309]; 
int charsRead = 0; 

while ((charsRead = reader.read(buffer, 0, buffer.length)) == buffer.length) 
{ 
    System.out.println(new String(buffer)); 
} 
if (charsRead > 0) 
{ 
    // print any trailing chars 
    System.out.println(new String(buffer, 0, charsRead)); 
} 
+0

Вы можете установить размер BufferedReader, чтобы не считывать все сразу. – PaulJWilliams

+0

-1: вы не _guaranteed_, что reader.read() заполняет буфер. –

+1

'BufferedReader' не читает, сохраняя весь файл в памяти. Проблема в том, что если файл является одной строкой, то 'readLine()' будет, по определению, читать во всем файле. – cletus

2

Читайте в байтовый массив длиной 309, а затем записать байты следующим образом:

import java.io.*; 



    public class Test { 
     public static void main(String[] args) throws Exception { 
     InputStream in = null; 
     byte[] chars = new byte[309]; 
     try { 
      in = new FileInputStream(args[0]); 
      int read = 0; 

      while((read = in.read(chars)) != -1) { 
       System.out.write(chars, 0, read); 
       System.out.println(""); 
      } 
     }finally { 
      if(in != null) { 
       in.close(); 
      } 
     } 
     } 

    } 
+1

Байты могут разбивать данные в многобайтовых кодировках, таких как utf-8 или utf-16. Это не указано в исходном вопросе, но все же. Если 309-й байт является первым байтом многобайтового символа, buh-bye. – DaveE

0

Вы можете изменить свою программу на это:

BufferedReader r = null; 

r = new BufferedReader(new FileReader(fileName)); 
char[] data = new char[309]; 

while (r.read(data, 0, 309) > 0) { 
    System.out.println(new String(data) + "\n"); 
} 

Это от моей головы и не проверено.

Смежные вопросы