web-dev-qa-db-fra.com

Lire des fichiers volumineux en Java

J'ai besoin des conseils de quelqu'un qui connaît très bien Java et des problèmes de mémoire ... J'ai un fichier volumineux (environ 1,5 Go) et je dois le couper en plusieurs (100 petits fichiers par exemple).

Je sais généralement comment le faire (en utilisant une BufferedReader), mais j'aimerais savoir si vous avez des conseils concernant la mémoire ou des astuces pour le faire plus rapidement.

Mon fichier contient du texte, ce n'est pas binaire et j'ai environ 20 caractères par ligne.

56
CC.

Premièrement, si votre fichier contient des données binaires, alors utiliser BufferedReader serait une grave erreur (car vous convertiriez les données en String, ce qui est inutile et pourrait facilement corrompre les données); vous devriez plutôt utiliser BufferedInputStream. S'il s'agit de données textuelles et que vous devez les fractionner en sauts de ligne, vous pouvez utiliser BufferedReader (si le fichier contient des lignes d'une longueur raisonnable).

En ce qui concerne la mémoire, il ne devrait pas y avoir de problème si vous utilisez un tampon de taille décente (je voudrais utiliser au moins 1 Mo pour m'assurer que le HD effectue principalement la lecture et l'écriture séquentielles).

Si la vitesse pose problème, vous pouvez consulter les packages Java.nio - ceux-ci sont supposés être plus rapides que Java.io

27

Pour économiser de la mémoire, ne stockez/dupliquez pas inutilement les données en mémoire (c’est-à-dire ne les affectez pas à des variables extérieures à la boucle). Traitez simplement la sortie immédiatement dès que l'entrée entre en jeu.

Peu importe que vous utilisiez ou non BufferedReader. Cela ne coûtera pas beaucoup plus de mémoire, comme certains le suggèrent implicitement. Au mieux, il ne touchera que quelques% de la performance. La même chose s'applique lors de l'utilisation de NIO. Cela ne fera qu'améliorer l'évolutivité, pas l'utilisation de la mémoire. Cela ne deviendra intéressant que lorsque des centaines de threads s'exécutant sur le même fichier.

Parcourez simplement le fichier, écrivez chaque ligne immédiatement dans un autre fichier au fur et à mesure de votre lecture, comptez les lignes et si le nombre atteint 100, passez au fichier suivant, etc.

Exemple de coup d'envoi:

String encoding = "UTF-8";
int maxlines = 100;
BufferedReader reader = null;
BufferedWriter writer = null;

try {
    reader = new BufferedReader(new InputStreamReader(new FileInputStream("/bigfile.txt"), encoding));
    int count = 0;
    for (String line; (line = reader.readLine()) != null;) {
        if (count++ % maxlines == 0) {
            close(writer);
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/smallfile" + (count / maxlines) + ".txt"), encoding));
        }
        writer.write(line);
        writer.newLine();
    }
} finally {
    close(writer);
    close(reader);
}
30
BalusC

Vous pouvez envisager d'utiliser des fichiers mappés en mémoire, via FileChannel s.

Généralement beaucoup plus rapide pour les gros fichiers. Il y a des compromis en termes de performances qui pourraient ralentir la lecture, donc YMMV.

Réponse associée: Java NIO FileChannel ou FileOutputstream performance/utilité

12
Ryan Emerle

C'est un très bon article: http://Java.Sun.com/developer/technicalArticles/Programming/PerfTuning/

En résumé, pour de bonnes performances, vous devriez:

  1. Évitez d’accéder au disque.
  2. Évitez d’accéder au système d’exploitation sous-jacent.
  3. Évitez les appels de méthode.
  4. Évitez de traiter les octets et les caractères individuellement.

Par exemple, pour réduire l'accès au disque, vous pouvez utiliser un tampon important. L'article décrit différentes approches.

4
b.roth

Est-ce que cela doit être fait en Java? C'est à dire. A-t-il besoin d'être indépendant de la plate-forme? Sinon, je suggérerais d'utiliser la commande ' split ' dans * nix. Si vous le souhaitez vraiment, vous pouvez exécuter cette commande via votre programme Java. Bien que je n’aie pas testé, j’imagine que ses performances sont supérieures à celles de l’implémentation Java IO que vous pourriez imaginer.

3
Mike

Vous pouvez utiliser Java.nio, qui est plus rapide que le flux d'entrée/sortie classique:

http://Java.Sun.com/javase/6/docs/technotes/guides/io/index.html

1
Kartoch

Oui. Je pense aussi que l’utilisation de read () avec des arguments tels que read (Char [], int init, int end) est un meilleur moyen de lire un fichier aussi volumineux .longueur))

Et j'ai également rencontré le problème des valeurs manquantes de l'utilisation de BufferedReader au lieu de BufferedInputStreamReader pour un flux d'entrée de données binaires. Donc, utiliser BufferedInputStreamReader est beaucoup mieux dans ce cas.

1
Namalak

À moins que vous lisiez accidentellement tout le fichier d'entrée au lieu de le lire ligne par ligne, votre limite principale sera la vitesse du disque. Vous voudrez peut-être essayer de commencer avec un fichier contenant 100 lignes et l'écrire dans 100 fichiers différents, une ligne dans chacune, et faire fonctionner le mécanisme de déclenchement sur le nombre de lignes écrites dans le fichier actuel. Ce programme sera facilement adaptable à votre situation.

package all.is.well;
import Java.io.IOException;
import Java.io.RandomAccessFile;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import junit.framework.TestCase;

/**
 * @author Naresh Bhabat
 * 
Following  implementation helps to deal with extra large files in Java.
This program is tested for dealing with 2GB input file.
There are some points where extra logic can be added in future.


Pleasenote: if we want to deal with binary input file, then instead of reading line,we need to read bytes from read file object.



It uses random access file,which is almost like streaming API.


 * ****************************************
Notes regarding executor framework and its readings.
Please note :ExecutorService executor = Executors.newFixedThreadPool(10);

 *  	   for 10 threads:Total time required for reading and writing the text in
 *         :seconds 349.317
 * 
 *         For 100:Total time required for reading the text and writing   : seconds 464.042
 * 
 *         For 1000 : Total time required for reading and writing text :466.538 
 *         For 10000  Total time required for reading and writing in seconds 479.701
 *
 * 
 */
public class DealWithHugeRecordsinFile extends TestCase {

	static final String FILEPATH = "C:\\springbatch\\bigfile1.txt.txt";
	static final String FILEPATH_WRITE = "C:\\springbatch\\writinghere.txt";
	static volatile RandomAccessFile fileToWrite;
	static volatile RandomAccessFile file;
	static volatile String fileContentsIter;
	static volatile int position = 0;

	public static void main(String[] args) throws IOException, InterruptedException {
		long currentTimeMillis = System.currentTimeMillis();

		try {
			fileToWrite = new RandomAccessFile(FILEPATH_WRITE, "rw");//for random write,independent of thread obstacles 
			file = new RandomAccessFile(FILEPATH, "r");//for random read,independent of thread obstacles 
			seriouslyReadProcessAndWriteAsynch();

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Thread currentThread = Thread.currentThread();
		System.out.println(currentThread.getName());
		long currentTimeMillis2 = System.currentTimeMillis();
		double time_seconds = (currentTimeMillis2 - currentTimeMillis) / 1000.0;
		System.out.println("Total time required for reading the text in seconds " + time_seconds);

	}

	/**
	 * @throws IOException
	 * Something  asynchronously serious
	 */
	public static void seriouslyReadProcessAndWriteAsynch() throws IOException {
		ExecutorService executor = Executors.newFixedThreadPool(10);//pls see for explanation in comments section of the class
		while (true) {
			String readLine = file.readLine();
			if (readLine == null) {
				break;
			}
			Runnable genuineWorker = new Runnable() {
				@Override
				public void run() {
					// do hard processing here in this thread,i have consumed
					// some time and ignore some exception in write method.
					writeToFile(FILEPATH_WRITE, readLine);
					// System.out.println(" :" +
					// Thread.currentThread().getName());

				}
			};
			executor.execute(genuineWorker);
		}
		executor.shutdown();
		while (!executor.isTerminated()) {
		}
		System.out.println("Finished all threads");
		file.close();
		fileToWrite.close();
	}

	/**
	 * @param filePath
	 * @param data
	 * @param position
	 */
	private static void writeToFile(String filePath, String data) {
		try {
			// fileToWrite.seek(position);
			data = "\n" + data;
			if (!data.contains("Randomization")) {
				return;
			}
			System.out.println("Let us do something time consuming to make this thread busy"+(position++) + "   :" + data);
			System.out.println("Lets consume through this loop");
			int i=1000;
			while(i>0){
			
				i--;
			}
			fileToWrite.write(data.getBytes());
			throw new Exception();
		} catch (Exception exception) {
			System.out.println("exception was thrown but still we are able to proceeed further"
					+ " \n This can be used for marking failure of the records");
			//exception.printStackTrace();

		}

	}
}

0
RAM

N'utilisez pas read sans arguments . C'est très lent . Mieux vaut le lire dans un tampon et le déplacer rapidement.

Utilisez bufferedInputStream car il prend en charge la lecture binaire.

Et c'est tout.

0
oneat