J'ai quelques fichiers qui devraient être triés selon l'id au début de chaque ligne . Les fichiers font environ 2-3 gb.
J'ai essayé de lire toutes les données dans une ArrayList
et de les trier. Mais la mémoire ne suffit pas pour tous les garder. Ça ne marche pas.
Les lignes ressemblent
0052304 0000004000000000000000000000000000000041 John Teddy 000023
0022024 0000004000000000000000000000000000000041 George Clan 00013
Comment puis-je trier les fichiers ??
Ce n'est pas exactement un problème de Java. Vous devez rechercher un algorithme efficace pour trier les données qui ne sont pas complètement lues en mémoire. Quelques adaptations à Merge-Sort peuvent y parvenir.
Jetez un coup d'œil à ceci: http://fr.wikipedia.org/wiki/Merge_sort
et: http://en.wikipedia.org/wiki/External_sorting
En gros, l’idée ici est de diviser le fichier en morceaux plus petits, de les trier (avec un tri par fusion ou une autre méthode), puis d’utiliser la méthode Fusion de trier pour créer le nouveau fichier trié.
Vous avez besoin d'un type de fusion externe pour le faire. Ici est une implémentation Java de celle-ci qui trie les très gros fichiers.
Étant donné que vos enregistrements sont déjà au format texte de fichier plat, vous pouvez les diriger vers UNIX sort(1)
p. Ex. sort -n -t' ' -k1,1 < input > output
. Il coupera automatiquement les données et effectuera un tri par fusion en utilisant la mémoire disponible et /tmp
. Si vous avez besoin de plus d'espace que de mémoire, ajoutez -T /tmpdir
à la commande.
Il est assez amusant de constater que tout le monde vous dit de télécharger d’énormes bibliothèques C # ou Java ou d’implémenter vous-même la fusion-tri lorsque vous pouvez utiliser un outil disponible sur toutes les plateformes et existant depuis des décennies.
Au lieu de charger toutes les données en mémoire en une fois, vous pouvez lire uniquement les clés et un index indiquant le début de la ligne (et éventuellement sa longueur), par exemple.
class Line {
int key, length;
long start;
}
Cela utiliserait environ 40 octets par ligne.
Une fois que vous avez trié ce tableau, vous pouvez utiliser RandomAccessFile pour lire les lignes dans leur ordre d'apparition.
Remarque: étant donné que vous frapperez le disque de manière aléatoire, au lieu d'utiliser de la mémoire, cela pourrait être très lent. Un disque typique prend 8 ms pour accéder aux données de manière aléatoire et si vous avez 10 millions de lignes, cela prendra environ une journée. (C'est le pire des cas absolu) En mémoire, cela prend environ 10 secondes.
Vous pouvez utiliser la base de données SQL Lite, charger les données dans la base de données, puis la laisser trier et renvoyer les résultats pour vous.
Avantages: Pas besoin de s'inquiéter d'écrire le meilleur algorithme de tri.
Inconvénient: vous aurez besoin d’espace disque et d’un traitement plus lent.
https://sites.google.com/site/arjunwebworld/Home/programming/sorting-large-data-files
Ce que vous devez faire est de regrouper les fichiers via un flux et de les traiter séparément. Ensuite, vous pouvez fusionner les fichiers car ils seront déjà triés. Cela ressemble à la façon dont le tri s'effectue.
La réponse à cette question SO sera utile: Diffuser des fichiers volumineux
Les systèmes d'exploitation sont fournis avec un puissant utilitaire de tri de fichiers. Une simple fonction qui appelle un script bash devrait aider.
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();
}
}
Vous devez effectuer un tri externe. C'est l'idée de base derrière Hadoop/MapReduce, juste qu'elle ne prend pas en compte les clusters distribués et fonctionne sur un seul nœud.
Pour de meilleures performances, utilisez Hadoop/Spark.
Modifiez ces lignes en fonction de votre système. fpath
est votre seul gros fichier d’entrée (testé avec 20 Go). shared
path est l'endroit où le journal d'exécution est stocké. fdir
est l'endroit où les fichiers intermédiaires seront stockés et fusionnés. Modifiez ces chemins en fonction de votre machine.
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";
Puis lancez le programme suivant. Votre fichier trié final sera créé avec le nom op401 dans le chemin fdir
. la dernière ligne Runtime.getRuntime().exec("valsort " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog);
vérifie si la sortie est triée ou non. Supprimez cette ligne si vous n'avez pas installé valsort ou si le fichier d'entrée n'est pas généré à l'aide de gensort ( http://www.ordinal.com/gensort.html ).
N'oubliez pas également de remplacer int totalLines = 200000000;
par le nombre total de lignes de votre fichier. et le nombre de threads (int threadCount = 16
) doit toujours être au pouvoir de 2 et suffisamment grand pour que (taille totale * 2/pas de thread) la quantité de données puisse résider en mémoire. Changer le nombre de threads changera le nom du fichier de sortie final. Comme pour 16, ce sera op401, pour 32, ce sera op501, pour 8, ce sera op301, etc.
Prendre plaisir.
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();
}
}