2011-12-16 7 views
0

큰 탭으로 구분 된 데이터 파일이 있습니다. 이러한 파일에는 열보다 몇 배 더 많은 행이 있습니다. 문제는이 파일을 피벗하고 싶지만,이 경우 "큰"은 너무 커서 메모리에서이를 수행 할 수 없다고 정의됩니다.대용량 데이터 파일 피하기

나는 이것을하는 가장 빠른 방법에 대한 몇 가지 제안을 찾고 싶었습니다. 저는 UNIX에서 Java로 일하고 있습니다. 더 빠른 언어 별 솔루션이 생기면 (또는 awk 등을 사용하는 경우) 열어두기는하지만 말입니다.

현재 우리는 이것을 메모리에서 수행하고 있지만, 시간이 지남에 따라 파일이 메모리 용량을 초과하고 있습니다. 분명히 "더 큰 기계 구입"은 해결책이지만 현재는 카드가 아닙니다.

+0

결과 파일이 같은 이름을 원한다고 가정합니까? – fge

+0

아니, 실제로는 파일의 복사본을 미리 만들 수는 있지만 그 반대는 이상적인 경우입니다. – geoffjentry

+0

피봇 팅 (pivoting)이란 무엇입니까? – Nayuki

답변

1

다음과 같은 내용이 도움이 될 수 있습니다. 이 코드는 먼저 소스 파일을 BufferedReader으로 열고 첫 번째 행을 읽고 \t에 대해이를 나눕니다.

결과 배열의 길이는 대상 파일의 줄 수입니다. FileHolder의 새로운 배열이 생성됩니다. FileHolder에는 기본적으로 파일 설명자가 있고 ByteBuffer은 캐시로 사용됩니다 (각 단어를 모두 쓰지 않도록). 모든 소유자가 생성되면 첫 번째 줄이 작성됩니다.

그런 다음 원본 파일을 다시 읽고 빈 줄에 추가 할 모든 파일 소유자가 추가 될 때까지 한 줄씩 다시 나누십시오.

완료되면 대상 파일이 만들어지고 (마지막) 모든 FileHolder 인스턴스가 배열 순서대로 순서대로 작성됩니다.

다음은 샘플 코드입니다 (LONG, here도 있음). 확실히 개선 될 수 있지만 (리소스는 실제로 올바른 장소 등에서 닫히지 않습니다) 작동합니다. 275MB 파일을 약 25 초 만에 옮겨 놓고 (쿼드 코어 Q6600, 4GB RAM, x86_64 Linux 3.1.2-rc5) 64MB의 Sun (64 비트) JDK를 "flimsy"기본값으로 실행합니다 :

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.5GB 파일에서 -Xmx4000과 함께 실행할 수 없었습니다. 우리가 가지고있는 파일은 가용 RAM보다 큰 편이어서 파일 크기보다 적은 RAM을 사용하는 솔루션이 필요합니다. – geoffjentry

+0

호기심에 의해 4.5GB 파일에서이 명령을 실행할 수 있습니까 :'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

여기에는 관련된 데이터만큼 큰 데이터에는 작동하지 않습니다. 어쨌든 내 코드에는 현재 수정중인 결함이 있습니다. 이제 64MB 힙 크기의 256MB 파일을 작성할 수 있습니다. 게다가 최종 탭을 제거하는 정규식이 필요하지 않습니다. – fge