Java: вставка новой строки после каждого 309-го символа - PullRequest
2 голосов
/ 03 августа 2010

Позвольте мне предварить это, сказав, что я довольно новичок в Java.

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

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

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

String line;

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

Ответы [ 7 ]

15 голосов
/ 03 августа 2010

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

  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, 64-разрядная ОС Windows 7, 8 ГБ ОЗУ, тестовый запуск на диске 7200 об / мин, 1,5 ТБ):

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 действительно дорогая, поэтому я запускал версии с и без. В основном после прогрева «эффективная» версия всего в 2 раза быстрее. Я предполагаю, что к этому времени файл фактически находится в памяти.

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

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)

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

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

2 голосов
/ 03 августа 2010

Считать в байтовый массив длиной 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();
            }
         }
      }

   }
2 голосов
/ 03 августа 2010

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

1 голос
/ 03 августа 2010

Не используйте 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));
}
1 голос
/ 03 августа 2010

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

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

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

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 голосов
/ 03 августа 2010

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

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

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

0 голосов
/ 03 августа 2010

Оберните ваш 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;
   }
}
...