2011-12-16 2 views
0

У меня есть большие файлы с разделителями табуляции. Эти файлы будут на несколько порядков больше строк, чем столбцы. Проблема в том, что я хотел бы повернуть эти файлы, но в этом случае «большой» определяется как слишком большой, чтобы сделать это в памяти.Поворот большого файла данных

Я надеялся найти некоторые предложения по самому быстрому способу этого. Я в первую очередь работаю в Java на UNIX, хотя, если бы возникло более быстрое решение для конкретного языка (или что-то с помощью awk и т. Д.), Я был бы открыт для этого.

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

+0

Предполагаете, вы хотите, чтобы файлы результатов были одинаковыми? – fge

+0

Нет, на самом деле обратное - идеальный случай, хотя это не имеет большого значения, поскольку мы могли бы сделать копию файла заранее. – geoffjentry

+0

Что вы подразумеваете под поворотом? – Nayuki

ответ

1

Что-то вроде ниже может работать для вас. Этот код сначала открывает исходный файл как BufferedReader, затем читает первую строку и разбивает ее на \t.

Длина результирующего массива - это количество строк целевого файла. Создается новый массив объектов FileHolder, где FileHolder содержит в основном дескриптор файла и ByteBuffer для использования в качестве кеша (чтобы не писать каждое слово). Когда создаются все держатели, записывается первая строка.

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

После этого создается целевой файл (наконец), и все экземпляры FileHolder записываются в него в порядке массива, поэтому в порядке строки.

Вот пример кода (LONG, также доступен here). Это, безусловно, может быть улучшено (ресурсы на самом деле не закрыты в правильном месте и т. Д.), Но это работает. Он переносит 275 МБ-файл здесь примерно за 25 секунд (четырехъядерный Q6600, 4 ГБ оперативной памяти, x86_64 Linux 3.1.2-rc5) и работает с «фальшивым» значением по умолчанию 64 МБ Sun (64 бит) JDK:

package net.sf.jpam; 

import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileReader; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.io.Reader; 
import java.nio.ByteBuffer; 
import java.nio.channels.FileChannel; 
import java.util.regex.Pattern; 

public final class Test 
{ 
    private static final Pattern TAB = Pattern.compile("\t"); 

    private static class FileHolder 
    { 
     private static final byte TABCHAR[] = "\t".getBytes(); 
     // Size of the buffer size 
     private static final int BUFSZ = 32768; 

     // Format string for a file 
     private static final String FORMAT = "/home/fge/t2.txt.%d"; 

     // The ByteBuffer 
     private final ByteBuffer buf = ByteBuffer.allocate(BUFSZ); 

     // The File object 
     private final File fd; 

     // RandomAccessFile 
     private final RandomAccessFile file; 

     FileHolder(final int index) 
      throws FileNotFoundException 
     { 
      final String name = String.format(FORMAT, index); 
      fd = new File(name); 
      file = new RandomAccessFile(fd, "rw"); 
     } 

     public void write(final String s) 
      throws IOException 
     { 
      final byte[] b = s.getBytes(); 
      if (buf.remaining() < b.length + TABCHAR.length) 
       flush(); 
      buf.put(b).put(TABCHAR); 
     } 

     private void flush() 
      throws IOException 
     { 
      file.write(buf.array(), 0, buf.position()); 
      buf.position(0); 
     } 

     public void copyTo(final RandomAccessFile dst) 
      throws IOException 
     { 
      flush(); 
      final FileChannel source = file.getChannel(); 
      final FileChannel destination = dst.getChannel(); 
      source.force(false); 
      final long len = source.size() - TABCHAR.length; 

      source.transferTo(0, len, destination); 
      dst.writeBytes("\n"); 

     } 

     public void tearDown() 
      throws IOException 
     { 
      file.close(); 
      if (!fd.delete()) 
       System.err.println("Failed to remove file " + fd); 
     } 

     @Override 
     public String toString() 
     { 
      return fd.toString(); 
     } 
    } 

    public static void main(final String... args) 
     throws IOException 
    { 
     long before, after; 

     before = System.currentTimeMillis(); 
     final Reader r = new FileReader("/home/fge/t.txt"); 
     final BufferedReader reader = new BufferedReader(r); 

     /* 
     * Read first line, count the number of elements. All elements are 
     * separated by a single tab. 
     */ 
     String line = reader.readLine(); 
     String[] elements = TAB.split(line); 

     final int nrLines = elements.length; 
     final FileHolder[] files = new FileHolder[nrLines]; 

     /* 
     * Initialize file descriptors 
     */ 
     for (int i = 0; i < nrLines; i++) 
      files[i] = new FileHolder(i); 


     /* 
     * Write first lines, then all others 
     */ 
     writeOneLine(elements, files); 

     while ((line = reader.readLine()) != null) { 
      elements = TAB.split(line); 
      writeOneLine(elements, files); 
     } 

     reader.close(); 
     r.close(); 
     after = System.currentTimeMillis(); 

     System.out.println("Read time: " + (after - before)); 

     before = System.currentTimeMillis(); 
     final RandomAccessFile out = new RandomAccessFile("/home/fge/t2.txt", 
      "rw"); 

     for (final FileHolder file: files) { 
      file.copyTo(out); 
      file.tearDown(); 
     } 

     out.getChannel().force(false); 
     out.close(); 

     after = System.currentTimeMillis(); 

     System.out.println("Write time: " + (after - before)); 
     System.exit(0); 
    } 

    private static void writeOneLine(final String[] elements, 
     final FileHolder[] fdArray) 
     throws IOException 
    { 
     final int len = elements.length; 
     String element; 
     FileHolder file; 

     for (int index = 0; index < len; index++) { 
      element = elements[index]; 
      file = fdArray[index]; 
      file.write(element); 
     } 
    } 
} 
+0

Он работает, но, похоже, он не решает проблему с памятью. Например, в файле объемом 4,5 Гбайт я не смог запустить это с помощью -Xmx4000. Файлы, которые мы имеем, часто больше, чем доступная оперативная память, поэтому требуется меньшее количество оперативной памяти, чем размер файла. – geoffjentry

+0

К любопытству, можете ли вы запустить эту команду в одном таком файле размером 4,5 ГБ: 'head -1 thefile | wc -c'? – fge

+0

16968 ... Может ли проблема быть вызвана превышением размера буфера? (Я не в состоянии настроить это на данный момент) – geoffjentry

0

@fge: 1) Лучше использовать CharBuffer вместо создания экземпляра строк.

2) Лучше использовать шаблон, как это:

initially.. 

private Matcher matcher; 
Pattern regexPattern = Pattern.compile(pattern); 
matcher = regexPattern.matcher(""); 

and then for matching pattern.. you do this.. 

matcher.reset(charBuffer).find() 

, потому что, когда вы смотрите в

Pattern.matcher(CharSequence input) { 
Matcher m = new Matcher(this, input); 
} 

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

+0

Это не будет работать для данных, больших, чем здесь, и в любом случае у моего кода есть недостатки, которые я исправляю: теперь я могу писать файлы размером 256 МБ с размером кучи размером 64 МБ. Более того, для удаления последней вкладки не требуется регулярное выражение – fge