[英]How do I sort very large files
我有一些文件應該根據每行開頭的 id 進行排序。 這些文件大約為 2-3 GB。
我試圖將所有數據讀入一個ArrayList
並對它們進行排序。 但是內存不足以保存所有這些。 這是行不通的。
線條看起來像
0052304 0000004000000000000000000000000000000041 John Teddy 000023
0022024 0000004000000000000000000000000000000041 George Clan 00013
我怎樣才能對文件進行排序??
這不完全是 Java 問題。 您需要研究一種有效的算法來對未完全讀入內存的數據進行排序。 對 Merge-Sort 的一些改編可以實現這一點。
看看這個: http : //en.wikipedia.org/wiki/Merge_sort
和: http : //en.wikipedia.org/wiki/External_sorting
基本上這里的想法是將文件分成更小的部分,對它們進行排序(使用合並排序或其他方法),然后使用合並排序中的合並來創建新的排序文件。
由於您的記錄已經是平面文件文本格式,您可以將它們通過管道傳輸到 UNIX sort(1)
例如sort -n -t' ' -k1,1 < input > output
。 它將自動分塊數據並使用可用內存和/tmp
執行合並排序。 如果您需要的空間超過可用內存,請將-T /tmpdir
添加到命令中。
很有趣的是,每個人都告訴您下載巨大的 C# 或 Java 庫,或者當您可以使用在每個平台上都可用並且已經存在了幾十年的工具時自己實現合並排序。
您需要一個外部歸並排序來做到這一點。 這是對非常大的文件進行排序的 Java 實現。
您可以只讀取鍵和行開始位置的索引(也可能是長度),而不是一次將所有數據加載到內存中,例如
class Line {
int key, length;
long start;
}
這將使用每行大約 40 個字節。
對這個數組進行排序后,您可以使用 RandomAccessFile 按照它們出現的順序讀取這些行。
注意:由於您將隨機訪問磁盤,而不是使用內存,這可能會非常慢。 一個典型的磁盤需要 8 毫秒來隨機訪問數據,如果您有 1000 萬行,這大約需要一天時間。 (這絕對是最壞的情況)在內存中大約需要 10 秒。
您需要執行外部排序。 這是 Hadoop/MapReduce 背后的驅動思想,只是它沒有考慮分布式集群並在單個節點上工作。
為了獲得更好的性能,您應該使用 Hadoop/Spark。
根據您的系統更改此行。 fpath
是你的一個大輸入文件(用 20GB 測試)。 shared
路徑是存儲執行日志的地方。 fdir
是存儲和合並中間文件的地方。 根據您的機器更改這些路徑。
public static final String fdir = "/tmp/";
public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
public static final String fPath = "/input/data-20GB.in";
public static final String opLog = shared+"Mysort20GB.log";
然后運行以下程序。 您的最終排序文件將在fdir
路徑中創建,名稱為fdir
。 最后一行Runtime.getRuntime().exec("valsort " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog);
檢查輸出是否已排序。 如果您沒有安裝 valsort 或者沒有使用 gensort( http://www.ordinal.com/gensort.html ) 生成輸入文件,請刪除此行。
也不要忘記改變int totalLines = 200000000;
到文件中的總行數。 和線程計數( int threadCount = 16
)應該始終是 2 的冪並且足夠大,以便(總大小 * 2 / 線程數)數據量可以駐留在內存中。 更改線程計數將更改最終輸出文件的名稱。 就像 16 號一樣,它將是 op401,對於 32 號來說,它將是 op501,對於 8 號它將是 op301,等等。
享受。
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.stream.Stream;
class SplitFile extends Thread {
String fileName;
int startLine, endLine;
SplitFile(String fileName, int startLine, int endLine) {
this.fileName = fileName;
this.startLine = startLine;
this.endLine = endLine;
}
public static void writeToFile(BufferedWriter writer, String line) {
try {
writer.write(line + "\r\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void run() {
try {
BufferedWriter writer = Files.newBufferedWriter(Paths.get(fileName));
int totalLines = endLine + 1 - startLine;
Stream<String> chunks =
Files.lines(Paths.get(Mysort20GB.fPath))
.skip(startLine - 1)
.limit(totalLines)
.sorted(Comparator.naturalOrder());
chunks.forEach(line -> {
writeToFile(writer, line);
});
System.out.println(" Done Writing " + Thread.currentThread().getName());
writer.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
class MergeFiles extends Thread {
String file1, file2, file3;
MergeFiles(String file1, String file2, String file3) {
this.file1 = file1;
this.file2 = file2;
this.file3 = file3;
}
public void run() {
try {
System.out.println(file1 + " Started Merging " + file2 );
FileReader fileReader1 = new FileReader(file1);
FileReader fileReader2 = new FileReader(file2);
FileWriter writer = new FileWriter(file3);
BufferedReader bufferedReader1 = new BufferedReader(fileReader1);
BufferedReader bufferedReader2 = new BufferedReader(fileReader2);
String line1 = bufferedReader1.readLine();
String line2 = bufferedReader2.readLine();
//Merge 2 files based on which string is greater.
while (line1 != null || line2 != null) {
if (line1 == null || (line2 != null && line1.compareTo(line2) > 0)) {
writer.write(line2 + "\r\n");
line2 = bufferedReader2.readLine();
} else {
writer.write(line1 + "\r\n");
line1 = bufferedReader1.readLine();
}
}
System.out.println(file1 + " Done Merging " + file2 );
new File(file1).delete();
new File(file2).delete();
writer.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
public class Mysort20GB {
//public static final String fdir = "/Users/diesel/Desktop/";
public static final String fdir = "/tmp/";
public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
public static final String fPath = "/input/data-20GB.in";
public static final String opLog = shared+"Mysort20GB.log";
public static void main(String[] args) throws Exception{
long startTime = System.nanoTime();
int threadCount = 16; // Number of threads
int totalLines = 200000000;
int linesPerFile = totalLines / threadCount;
ArrayList<Thread> activeThreads = new ArrayList<Thread>();
for (int i = 1; i <= threadCount; i++) {
int startLine = i == 1 ? i : (i - 1) * linesPerFile + 1;
int endLine = i * linesPerFile;
SplitFile mapThreads = new SplitFile(fdir + "op" + i, startLine, endLine);
activeThreads.add(mapThreads);
mapThreads.start();
}
activeThreads.stream().forEach(t -> {
try {
t.join();
} catch (Exception e) {
}
});
int treeHeight = (int) (Math.log(threadCount) / Math.log(2));
for (int i = 0; i < treeHeight; i++) {
ArrayList<Thread> actvThreads = new ArrayList<Thread>();
for (int j = 1, itr = 1; j <= threadCount / (i + 1); j += 2, itr++) {
int offset = i * 100;
String tempFile1 = fdir + "op" + (j + offset);
String tempFile2 = fdir + "op" + ((j + 1) + offset);
String opFile = fdir + "op" + (itr + ((i + 1) * 100));
MergeFiles reduceThreads =
new MergeFiles(tempFile1,tempFile2,opFile);
actvThreads.add(reduceThreads);
reduceThreads.start();
}
actvThreads.stream().forEach(t -> {
try {
t.join();
} catch (Exception e) {
}
});
}
long endTime = System.nanoTime();
double timeTaken = (endTime - startTime)/1e9;
System.out.println(timeTaken);
BufferedWriter logFile = new BufferedWriter(new FileWriter(opLog, true));
logFile.write("Time Taken in seconds:" + timeTaken);
Runtime.getRuntime().exec("valsort " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog);
logFile.close();
}
}
使用 java 庫big-sorter可用於對非常大的文本或二進制文件進行排序。
以下是您的確切問題將如何實施:
// write the input to a file
String s = "0052304 0000004000000000000000000000000000000041 John Teddy 000023\n"
+ "0022024 0000004000000000000000000000000000000041 George Clan 00013";
File input = new File("target/input");
Files.write(input.toPath(),s.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
File output = new File("target/output");
//sort the input
Sorter
.serializerLinesUtf8()
.comparator((a,b) -> {
String ida = a.substring(0, a.indexOf(' '));
String idb = b.substring(0, b.indexOf(' '));
return ida.compareTo(idb);
})
.input(input)
.output(output)
.sort();
// display the output
Files.readAllLines(output.toPath()).forEach(System.out::println);
輸出:
0022024 0000004000000000000000000000000000000041 George Clan 00013
0052304 0000004000000000000000000000000000000041 John Teddy 000023
您可以使用 SQL Lite 文件 db,將數據加載到 db,然后讓它排序並為您返回結果。
優點:無需擔心編寫最佳排序算法。
缺點:您將需要磁盤空間,處理速度較慢。
https://sites.google.com/site/arjunwebworld/Home/programming/sorting-large-data-files
操作系統帶有強大的文件排序實用程序。 調用 bash 腳本的簡單函數應該會有所幫助。
public static void runScript(final Logger log, final String scriptFile) throws IOException, InterruptedException {
final String command = scriptFile;
if (!new File (command).exists() || !new File(command).canRead() || !new File(command).canExecute()) {
log.log(Level.SEVERE, "Cannot find or read " + command);
log.log(Level.WARNING, "Make sure the file is executable and you have permissions to execute it. Hint: use \"chmod +x filename\" to make it executable");
throw new IOException("Cannot find or read " + command);
}
final int returncode = Runtime.getRuntime().exec(new String[] {"bash", "-c", command}).waitFor();
if (returncode!=0) {
log.log(Level.SEVERE, "The script returned an Error with exit code: " + returncode);
throw new IOException();
}
}
您需要做的是通過流將文件分塊並單獨處理它們。 然后您可以將文件合並在一起,因為它們已經被排序,這類似於合並排序的工作方式。
這個SO問題的答案將是有價值的: Stream large files
我使用了我自己的邏輯並按格式對一個 BIG JSON 文件進行了排序
{"name":"hoge.finance","address":"0xfAd45E47083e4607302aa43c65fB3106F1cd7607"}
完整的源代碼和測試用例可以在https://github.com/sitetester/token-sorter上找到。 代碼有據可查,很容易理解。
它將輸入文件拆分為多個較小的 SORTED 文件(可配置),然后比較數據。
在這里粘貼一些評論...
// at this point, we have sorted data sets in respective files
// next, we will take first token from first file and compare it with tokens of all other files
// during comparison, if some token from other file is in sorted order, then we make it default/initial sorted token
// & jump to next file, since all remaining tokens in THAT file are already in sorted form
// at end of comparisons with all files, we remove it from specific file (so it's not compared next time) and put/append in final sorted file
// this process continues, until all entries are matched
// if some file has no entries, then we simply delete it (so it's not compared next time)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.