Статьи

Разделить файл как поток

На прошлой неделе я обсуждал, что новый (@since 1.8) метод splitAsStream в классе Pattern работает с последовательностью символов, считывающей из него только столько, сколько необходимо потоку, и не работает с сопоставлением с образцом, создавая все возможные элементы и возвращая его как поток. Такое поведение является истинной природой потоков и должно поддерживаться высокопроизводительными приложениями.

В этой статье, как я и обещал на прошлой неделе, я покажу практическое применение splitAsStream где действительно имеет смысл обрабатывать поток, а не просто разбивать всю строку на массив и работать над этим.

Приложение, как вы уже догадались из заголовка статьи, разбивает файл на несколько токенов. Файл может быть представлен как последовательность CharSequence настолько длинной (или такой короткой), если она не длиннее 2 ГБ. Ограничение исходит из того факта, что длина CharSequence является значением типа int а в Java она является 32-битной. Длина файла long , которая составляет 64 бита. Поскольку чтение из файла намного медленнее, чем чтение из строки, которая уже находится в памяти, имеет смысл использовать лень обработки потока. Все, что нам нужно, это реализация последовательности символов, которая поддерживается файлом. Если у нас это есть, мы можем написать программу, подобную следующей:

1
2
3
4
5
6
7
public static void main(String[] args) throws FileNotFoundException {
        Pattern p = Pattern.compile("[,\\.\\-;]");
        final CharSequence splitIt =
            new FileAsCharSequence(
                   new File("path_to_source\\SplitFileAsStream.java"));
        p.splitAsStream(splitIt).forEach(System.out::println);
    }

Этот код не читает ни одну часть файла, которая пока не нужна, предполагает, что реализация FileAsCharSequence не считывает файл жадным. Реализация класса FileAsCharSequence может быть:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.epam.training.regex;
 
import java.io.*;
 
public class FileAsCharSequence implements CharSequence {
    private final int length;
    private final StringBuilder buffer = new StringBuilder();
    private final InputStream input;
 
    public FileAsCharSequence(File file) throws FileNotFoundException {
        if (file.length() > (long) Integer.MAX_VALUE) {
            throw new IllegalArgumentException("File is too long to handle as character sequence");
        }
        this.length = (int) file.length();
        this.input = new FileInputStream(file);
    }
 
    @Override
    public int length() {
        return length;
    }
 
    @Override
    public char charAt(int index) {
        ensureFilled(index + 1);
        return buffer.charAt(index);
    }
 
 
    @Override
    public CharSequence subSequence(int start, int end) {
        ensureFilled(end + 1);
        return buffer.subSequence(start, end);
    }
 
    private void ensureFilled(int index) {
        if (buffer.length() < index) {
            buffer.ensureCapacity(index);
            final byte[] bytes = new byte[index - buffer.length()];
            try {
                int length = input.read(bytes);
                if (length < bytes.length) {
                    throw new IllegalArgumentException("File ended unexpected");
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                buffer.append(new String(bytes, "utf-8"));
            } catch (UnsupportedEncodingException ignored) {
            }
        }
    }
}

Эта реализация читает только столько байтов из файла, сколько необходимо для последнего, фактического вызова метода charAt или subSequence .

Если вам интересно, вы можете улучшить этот код, чтобы сохранить в памяти только те байты, которые действительно необходимы, и удалить байты, которые уже были возвращены в поток. Чтобы узнать, какие байты не нужны, хороший splitAsStream из предыдущей статьи состоит в том, что splitAsStream никогда не касается символа, индекс которого меньше, чем у первого ( start ) аргумента последнего вызова subSequence . Однако, если вы реализуете код так, что он выбрасывает символы и завершается неудачей, если кто-то хочет получить доступ к уже выброшенному символу, он не будет по-настоящему реализовывать интерфейс CharSequence , хотя он все еще может хорошо работать с splitAsStream пока долго реализация не меняется и для запуска нужны уже некоторые переданные символы. (Ну, я не уверен, но это также может произойти, если мы используем какое-то сложное регулярное выражение в качестве шаблона расщепления.)

Удачного кодирования!

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Разделить файл как поток

Мнения, высказанные участниками Java Code Geeks, являются их собственными.