Чтение большого количества данных из файла в Java - PullRequest
16 голосов
/ 22 апреля 2010

У меня есть текстовый файл, который содержит 1 000 002 чисел в следующем порядке:

123 456
1 2 3 4 5 6 .... 999999 100000

Теперь мне нужно прочитать эти данные и распределить их по int переменным (самые первые два числа) и всем остальным ( 1 000 000 чисел) в массиве int[].

Это не сложная задача, но - это ужасно медленно.

Моя первая попытка была java.util.Scanner:

 Scanner stdin = new Scanner(new File("./path"));
 int n = stdin.nextInt();
 int t = stdin.nextInt();
 int array[] = new array[n];

 for (int i = 0; i < n; i++) {
     array[i] = stdin.nextInt();
 }

Он работает как исключение, но для его выполнения требуется около 7500 мс . Мне нужно получить эти данные за несколько сотен миллисекунд.

Тогда я попробовал java.io.BufferedReader:

Используя BufferedReader.readLine() и String.split() Я получил те же результаты примерно за 1700 мс , но это все еще слишком много.

Как я могу прочитать этот объем данных менее чем за 1 секунду? Окончательный результат должен быть равен:

int n = 123;
int t = 456;
int array[] = { 1, 2, 3, 4, ..., 999999, 100000 };

Согласно ответу trashgod:

StreamTokenizer решение быстрое (занимает около 1400 мс), но все равно слишком медленное:

StreamTokenizer st = new StreamTokenizer(new FileReader("./test_grz"));
st.nextToken();
int n = (int) st.nval;

st.nextToken();
int t = (int) st.nval;

int array[] = new int[n];

for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
    array[i] = (int) st.nval;
}

PS. Там нет необходимости для проверки. Я на 100% уверен, что данные в ./test_grz файле верны.

Ответы [ 7 ]

12 голосов
/ 23 апреля 2010

Спасибо за каждый ответ, но я уже нашел метод, который соответствует моим критериям:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("./path"));
int n = readInt(bis);
int t = readInt(bis);
int array[] = new int[n];
for (int i = 0; i < n; i++) {
    array[i] = readInt(bis);
}

private static int readInt(InputStream in) throws IOException {
    int ret = 0;
    boolean dig = false;

    for (int c = 0; (c = in.read()) != -1; ) {
        if (c >= '0' && c <= '9') {
            dig = true;
            ret = ret * 10 + c - '0';
        } else if (dig) break;
    }

    return ret;
}

Требуется всего около 300 мс , чтобы прочитать 1 млн целых чисел!

2 голосов
/ 23 апреля 2010

Вы можете сократить время для результата StreamTokenizer, используя BufferedReader:

Reader r = null;
try {
    r = new BufferedReader(new FileReader(file));
    final StreamTokenizer st = new StreamTokenizer(r);
    ...
} finally {
    if (r != null)
        r.close();
}

Кроме того, не забудьте закрыть ваши файлы, как я показал здесь.

Вы также можете сэкономить время, используя специальный токенизатор только для своих целей:

public class CustomTokenizer {

    private final Reader r;

    public CustomTokenizer(final Reader r) {
        this.r = r;
    }

    public int nextInt() throws IOException {
        int i = r.read();
        if (i == -1)
            throw new EOFException();

        char c = (char) i;

        // Skip any whitespace
        while (c == ' ' || c == '\n' || c == '\r') {
            i = r.read();
            if (i == -1)
                throw new EOFException();
            c = (char) i;
        }

        int result = (c - '0');
        while ((i = r.read()) >= 0) {
            c = (char) i;
            if (c == ' ' || c == '\n' || c == '\r')
                break;
            result = result * 10 + (c - '0');
        }

        return result;
    }

}

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

Если вы много читаете эти результаты и эти результаты не сильно меняются, вам, вероятно, следует сохранить массивотслеживать время последнего изменения файла.Затем, если файл не изменился, просто используйте кэшированную копию массива, и это значительно ускорит результаты.Например:

public class ArrayRetriever {

    private File inputFile;
    private long lastModified;
    private int[] lastResult;

    public ArrayRetriever(File file) {
        this.inputFile = file;
    }

    public int[] getResult() {
        if (lastResult != null && inputFile.lastModified() == lastModified)
            return lastResult;

        lastModified = inputFile.lastModified();

        // do logic to actually read the file here

        lastResult = array; // the array variable from your examples
        return lastResult;
    }

}
2 голосов
/ 22 апреля 2010

StreamTokenizer может быть быстрее, как предлагается здесь .

1 голос
/ 22 апреля 2010

Можно переформатировать входные данные, чтобы каждое целое число находилось на отдельной строке (вместо одной длинной строки с одним миллионом целых чисел), при использовании Integer.parseInt(BufferedReader.readLine()) вы должны увидеть гораздо более высокую производительность благодаря более умной буферизации по строкам, а не необходимость разбить длинную строку на отдельный массив строк.

Редактировать: Я проверил это и сумел прочитать вывод, полученный с помощью seq 1 1000000, в массив int намного меньше чем за полсекунды, но, конечно, это зависит от машины.

1 голос
/ 22 апреля 2010

Сколько памяти у вас в компьютере? Вы можете столкнуться с проблемами GC.

Лучше всего обрабатывать данные по одной строке, если это возможно. Не загружайте его в массив. Загрузите то, что вам нужно, обработайте, запишите и продолжайте.

Это уменьшит ваш объем памяти и все равно будет использовать тот же объем File IO

0 голосов
/ 27 ноября 2016

Использование StreamTokenizer в BufferedReader даст вам довольно хорошую производительность. Вам не нужно писать свою собственную функцию readInt ().

Вот код, который я использовал для локального тестирования производительности:

/**
 * Created by zhenhua.xu on 11/27/16.
 */
public class MyReader {

private static final String FILE_NAME = "./1m_numbers.txt";
private static final int n = 1000000;

public static void main(String[] args) {
    try {
        readByScanner();
        readByStreamTokenizer();
        readByStreamTokenizerOnBufferedReader();
        readByBufferedInputStream();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void readByScanner() throws Exception {
    long startTime = System.currentTimeMillis();

    Scanner stdin = new Scanner(new File(FILE_NAME));
    int array[] = new int[n];
    for (int i = 0; i < n; i++) {
        array[i] = stdin.nextInt();
    }

    long endTime = System.currentTimeMillis();
    System.out.println(String.format("Total time by Scanner: %d ms", endTime - startTime));
}

public static void readByStreamTokenizer() throws Exception {
    long startTime = System.currentTimeMillis();

    StreamTokenizer st = new StreamTokenizer(new FileReader(FILE_NAME));
    int array[] = new int[n];

    for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
        array[i] = (int) st.nval;
    }

    long endTime = System.currentTimeMillis();
    System.out.println(String.format("Total time by StreamTokenizer: %d ms", endTime - startTime));
}

public static void readByStreamTokenizerOnBufferedReader() throws Exception {
    long startTime = System.currentTimeMillis();

    StreamTokenizer st = new StreamTokenizer(new BufferedReader(new FileReader(FILE_NAME)));
    int array[] = new int[n];

    for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
        array[i] = (int) st.nval;
    }

    long endTime = System.currentTimeMillis();
    System.out.println(String.format("Total time by StreamTokenizer with BufferedReader: %d ms", endTime - startTime));
}

public static void readByBufferedInputStream() throws Exception {
    long startTime = System.currentTimeMillis();

    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(FILE_NAME));
    int array[] = new int[n];
    for (int i = 0; i < n; i++) {
        array[i] = readInt(bis);
    }

    long endTime = System.currentTimeMillis();
    System.out.println(String.format("Total time with BufferedInputStream: %d ms", endTime - startTime));
}

private static int readInt(InputStream in) throws IOException {
    int ret = 0;
    boolean dig = false;

    for (int c = 0; (c = in.read()) != -1; ) {
        if (c >= '0' && c <= '9') {
            dig = true;
            ret = ret * 10 + c - '0';
        } else if (dig) break;
    }

    return ret;
}

Результаты, которые я получил:

  • Общее время по сканеру: 789 мс
  • Общее время по StreamTokenizer: 226 мс
  • Общее время StreamTokenizer с BufferedReader: 80 мс
  • Общее время по BufferedInputStream: 95 мс
0 голосов
/ 22 апреля 2010

Я бы расширил FilterReader и проанализировал строку так, как она читается в методе read ().Пусть метод getNextNumber вернет числа.Код оставлен как упражнение для читателя.

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