Читать большие файлы на Java - PullRequest
57 голосов
/ 01 марта 2010

Мне нужен совет от человека, который хорошо знает Java и имеет проблемы с памятью У меня есть большой файл (примерно 1,5 ГБ), и мне нужно разрезать этот файл на множество (например, 100 небольших файлов) небольших файлов.

В целом я знаю, как это сделать (используя BufferedReader), но я хотел бы знать, есть ли у вас какие-либо советы относительно памяти или советы, как это сделать быстрее.

Мой файл содержит текст, он не двоичный, и у меня около 20 символов в строке.

Ответы [ 10 ]

32 голосов
/ 01 марта 2010

Для экономии памяти не храните / не дублируйте данные в памяти (т.е. не назначайте их переменным вне цикла). Просто обработайте вывод немедленно , как только вход поступит.

Это действительно не имеет значения, используете ли вы BufferedReader или нет. Это не будет стоить значительно больше памяти, как некоторые неявно предполагают. В лучшем случае это будет всего лишь несколько процентов от производительности. То же самое касается использования NIO. Это только улучшит масштабируемость, но не использование памяти. Это станет интересным только тогда, когда вы работаете с сотнями потоков в одном файле.

Просто циклически просматривайте файл, записывайте каждую строку сразу в другой файл по мере того, как вы читаете, подсчитывайте строки и, если он достигнет 100, затем переключитесь на следующий файл и так далее.

Пример запуска:

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);
}
27 голосов
/ 01 марта 2010

Во-первых, если ваш файл содержит двоичные данные, то использование BufferedReader было бы большой ошибкой (потому что вы конвертировали бы данные в String, что не нужно и могло бы легко повредить данные); Вы должны использовать BufferedInputStream вместо этого. Если это текстовые данные, и вам нужно разбить их на разрывы строк, тогда можно использовать BufferedReader (при условии, что файл содержит строки разумной длины).

Что касается памяти, проблем не должно быть, если вы используете буфер приличного размера (я бы использовал не менее 1 МБ, чтобы убедиться, что HD выполняет в основном последовательное чтение и запись).

Если скорость окажется проблемой, вы можете взглянуть на пакеты java.nio - они предположительно быстрее, чем java.io,

12 голосов
/ 01 марта 2010

Вы можете использовать файлы, отображаемые в память, через FileChannel s.

Обычно намного быстрее для больших файлов. Существуют компромиссы производительности, которые могут замедлять его, поэтому YMMV.

Соответствующий ответ: Производительность / полезность Java NIO FileChannel против FileOutputstream

4 голосов
/ 01 марта 2010

Должно ли это быть сделано в Java? То есть он должен быть независимым от платформы? Если нет, я бы предложил использовать команду split в * nix. Если вы действительно хотите, вы можете выполнить эту команду через Java-программу. Хотя я не тестировал, я думаю, что он работает быстрее, чем любая реализация Java IO, которую вы могли бы придумать.

4 голосов
/ 01 марта 2010

Это очень хорошая статья: http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/

Таким образом, для отличной производительности вы должны:

  1. Избегайте доступа к диску.
  2. Избегайте доступа к базовой операционной системе.
  3. Избегайте вызовов методов.
  4. Избегайте индивидуальной обработки байтов и символов.

Например, чтобы уменьшить доступ к диску, вы можете использовать большой буфер. В статье описаны различные подходы.

1 голос
/ 09 октября 2016

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();

		}

	}
}
1 голос
/ 27 октября 2010

Да. Я также думаю, что использование read () с аргументами вроде read (Char [], int init, int end) - лучший способ прочитать такой большой файл (Например: чтение (буфер, 0, длина буфера))

И я также столкнулся с проблемой пропущенных значений при использовании BufferedReader вместо BufferedInputStreamReader для потока ввода двоичных данных. Таким образом, использование BufferedInputStreamReader намного лучше в этом случае.

1 голос
/ 01 марта 2010

Вы можете использовать java.nio, который быстрее классического потока ввода / вывода:

http://java.sun.com/javase/6/docs/technotes/guides/io/index.html

0 голосов
/ 01 марта 2010

Если вы случайно не прочитали весь входной файл вместо того, чтобы читать его построчно, то вашим основным ограничением будет скорость диска. Вы можете попробовать начать с файла, содержащего 100 строк, и записать его в 100 различных файлов по одной строке в каждом и заставить механизм запуска работать с количеством строк, записанных в текущий файл. Эта программа будет легко масштабируема в вашей ситуации.

0 голосов
/ 01 марта 2010

Не используйте чтение без аргументов. Это очень медленно. Лучше прочитать его в буфер и быстро переместить в файл.

Используйте bufferedInputStream, потому что он поддерживает двоичное чтение.

И это все.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...