【问题标题】:Sorting a huge file in Java在Java中对一个大文件进行排序
【发布时间】:2010-03-04 21:23:11
【问题描述】:

我有一个文件,它由一行组成:

 1 , 1 2 , 1 3 6 , 4 ,...

在此表示中,空格分隔整数和逗号。 这个字符串太大了,我无法用 RandomAccessFile.readLine() 读取它(几乎需要 4 Gb)。所以我创建了一个缓冲区,它可以包含 10 个整数。我的任务是对字符串中的所有整数进行排序。

你能帮忙吗?

编辑

@奥斯卡雷耶斯

我需要将一些整数序列写入文件,然后从中读取。其实我不知道,该怎么做。我是新手。所以我决定用chars来写整数,整数之间的分隔符是“,”,序列之间的分隔符是“\n\r”。所以我创造了一个能读它的怪物:

public BinaryRow getFilledBuffer(String filePath, long offset) throws IOException{
    mainFile = new RandomAccessFile(filePath, "r");

    if (mainFile.length() == 0){
        return new BinaryRow();
    }

    StringBuilder str = new StringBuilder();

    mainFile.seek(mainFile.length()-4); //that is "\n" symbol
    char chN = mainFile.readChar();

    mainFile.seek(offset);
    int i = 0;
    char nextChar = mainFile.readChar();
    while (i < 11 && nextChar != chN){
        str.append(nextChar);
        if (nextChar == ','){
            i++;
            if (i == 10){
                break;
            }
        }
        nextChar = mainFile.readChar();
    }

    if (nextChar == chN){
        position = -1;
    }else{
        position = mainFile.getFilePointer();
    }

    BinaryRow br = new BinaryRow();

    StringBuilder temp = new StringBuilder();

    for (int j = 0; j < str.length(); j++){
        if ((str.charAt(j) != ',')){
            temp.append(str.charAt(j));
            if (j == str.length() - 1){
                br.add(Integer.parseInt(temp.toString()));
            }   
        }else{
            br.add(Integer.parseInt(temp.toString()));
            temp.delete(0, temp.length());
        }
    }


    mainFile.close();
    return br;

}

如果你能建议怎么做,请做 =)

【问题讨论】:

  • 你的代码哪里出了问题?您尝试了哪些方法?
  • 是的,要将这些整数写入我使用 RandomAccessFile.writeChars() 的文件。我尝试使用 writeInt() 但整数粘在一起...所以 writeChars() 以这种方式写入整数,我只添加了逗号...
  • @Dmitry: 将号码136 放在一起有什么问题,你为什么需要它作为1 3 6
  • 我现在迷路了,你的输入和期望的输出是什么?
  • 期望的输入是:1,2,4,5,6,7 3,8,9 那是一个文件的表示(有两个序列)。我需要添加它们(在结果序列中应该是两个序列中的整数而不重复,并且结果序列必须排序)。期望的输出:1,2,3,4,5,6,7,8,9 那是另一个文件 - resultFile.

标签: java sorting external-sorting


【解决方案1】:

这正是 QuickSort 的起源,当时没有足够的 RAM 在内存中排序,所以他们的程序是将部分结果存储在磁盘中。

所以你可以做的是:

  1. 选择一个支点。
  2. 按顺序读取文件并将低于基准值的数据存储在 temp_file_1 中,将大于或等于基准值的数据存储在 temp_file_2 中
  3. 在 temp_file_1 中重复该过程并将结果附加到 result_file
  4. 对 temp_file_2 重复该过程并将结果附加到 result_file

当零件足够小时(像2一样直接交换它们足够在内存中排序)

通过这种方式,您可以分块排序并将部分结果存储在临时文件中,您将获得一个对结果进行排序的最终文件。

编辑我告诉过你可以快速排序。

看来您毕竟需要一些额外的空间来存放临时文件。

这就是我所做的。

我创建了一个 40 mb 的文件,其中的数字用逗号分隔。

我把它命名为input

input http://img200.imageshack.us/img200/5129/capturadepantalla201003t.png

输入为 40mb

在排序过程中,创建带有“大于”、“小于”值的桶的 tmp 文件,当排序完成时,这些值被发送到一个名为(猜猜看)output 的文件中

p>

processing http://img200.imageshack.us/img200/1672/capturadepantalla201003y.png

使用部分结果创建临时文件

最后,所有的 tmp 文件都被删除,结果以正确的数字顺序保存在“输出”文件中:

output http://img203.imageshack.us/img203/5950/capturadepantalla201003w.png

最后创建了“输出”文件,注意它也是 40 mb

这是完整的程序。

import java.io.*;
import java.util.*;

public class FileQuickSort {

    static final int MAX_SIZE = 1024*1024*16; // 16 megabytes in this sample, the more memory your program has, less disk writing will be used. 
    public static void main( String [] args ) throws IOException {
        fileQuickSort( new File("input"), new File("output"));
        System.out.println();
    }

    //
    static void fileQuickSort( File inputFile, File outputFile ) throws IOException {
        Scanner scanner = new Scanner( new BufferedInputStream( new FileInputStream( inputFile ), MAX_SIZE));
        scanner.useDelimiter(",");

        if( inputFile.length() > MAX_SIZE && scanner.hasNextInt()) {
            System.out.print("-");

            // put them in two buckets... 
            File lowerFile = File.createTempFile("quicksort-","-lower.tmp",new File("."));
            File greaterFile = File.createTempFile("quicksort-","-greater.tmp", new File("."));
            PrintStream  lower   = createPrintStream(lowerFile);
            PrintStream greater  = createPrintStream(greaterFile);
            PrintStream target = null;
            int pivot = scanner.nextInt();

            // Read the file and put the values greater than in a file 
            // and the values lower than in other 
            while( scanner.hasNextInt() ){
                int current = scanner.nextInt();

                if( current < pivot ){
                    target = lower;
                } else {
                    target = greater;
                }
                target.printf("%d,",current);
            }
            // avoid dropping the pivot
            greater.printf("%d,",pivot);
            // close the stream before reading them again
            scanner.close();
            lower.close();
            greater.close();
            // sort each part
            fileQuickSort( lowerFile , outputFile );
            lowerFile.delete();
            fileQuickSort( greaterFile   , outputFile);
            greaterFile.delete();

            // And you're done.
        } else {

            // Else , if you have enough RAM to process it
            // 
            System.out.print(".");
            List<Integer> smallFileIntegers = new ArrayList<Integer>();
            // Read it
            while( scanner.hasNextInt() ){
                smallFileIntegers.add( scanner.nextInt() );
            }
            scanner.close();

            // Sort them in memory 
            Collections.sort( smallFileIntegers );

            PrintStream out = createPrintStream( outputFile);
            for( int i : smallFileIntegers ) {
                out.printf("%d,",i);
            }
            out.close();
            // And your're done
        }
    }
    private static PrintStream createPrintStream( File file ) throws IOException {
        boolean append = true;
        return new PrintStream(  new BufferedOutputStream( new FileOutputStream( file, append )));
    }
}

文件格式为number,number,number,number

你当前的格式是:n u m b e r , n u m b , b e r

要解决这个问题,您只需阅读所有内容并跳过空白即可。

为此添加另一个问题。

【讨论】:

  • 是的,这就像创建一棵树。我知道,这可能是唯一的方法,但会有很多文件......
  • 不是真的...我的意思是您不一定需要创建 1 GB 的文件。您只需执行此操作,直到您可以在内存中执行排序。
  • +1 如果没有其他原因,除了我曾经看到的半透明窗口的第一次有效使用。荣誉。您还为这个好的答案付出了很多努力。
  • 到底为什么要使用磁盘快速排序?我在这里错过了什么吗?我的做法是读取内存大小的块,在 RAM 中排序,写入临时文件。处理完文件并按块进行排序后,将块合并排序到输出文件中。
  • 等等,连续: if( inputFile.length() > MAX_SIZE &&scanner.hasNextInt()) 你说“inputFile.length() > MAX_SIZE”,但之后你什么都不做部分,不是吗?
【解决方案2】:

以块(每个 100 MB?)将其读取到内存中,一次一个块,对其进行排序并保存到磁盘。

然后打开所有有序块,读取每个块的第一个元素,并将最低的附加到输出。然后读取刚刚读取的块的下一个元素并重复。

合并时,您可以保留从每个块中读取的最后一个 int 数组,并对其进行迭代以获得最低值。然后,您将刚刚使用的值替换为从中提取的块中的下一个元素。

example with chunks [1, 5, 16] [2, 9, 14] [3, 8, 10]
array [(1), 2, 3], lowest 1 --> to output
      [5, (2), 3], lowest 2 --> to output
      [5, 9, (3)], lowest 3 -->
      [(5), 9, 8],        5
      [16, 9, (8)],       8
      [16, (9), 10],      9 
...

【讨论】:

  • 如果我没记错的话,我将不得不创建某种索引数组。另一方面,一个块可能包含整数 1, 200, 500,另一个 2, 100, 300 ...
  • @Dmitry:确实,如果你实现 QuickSort 会更好,它使用枢轴来克服这个细节。
  • 我添加了一个合并过程的例子
  • @Oscar:如果我没记错的话,您建议对数组进行快速排序以进行合并:我认为您可以遍历 40 个?元素(4 GB / 100 MB)它不应该降低性能,你会得到一个更简单的方法来替换你刚刚在同一个块上使用的值
  • 合并过程中内存的使用非常有限:实际上你可以只在内存中保存数组(它的大小对应于N个整数,N =块数,转换为略超过 1 KB - 因为我不知道它们是如何存储在 Java 中的,我假设是 32 位整数)您当然不必将整个块加载到内存中,只需打开所有块然后在数组中一次读取一个整数。
猜你喜欢
  • 1970-01-01
  • 2013-05-15
  • 2013-03-16
  • 2017-10-22
  • 2017-04-04
  • 1970-01-01
  • 2020-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多