上周,我讨论了类Pattern新的(@since 1.8)方法splitAsStream可以处理字符序列,仅从流中读取该字符序列,并且不进行模式匹配以创建所有可能的元素并返回它作为流。 这种行为是流的本质,它是支持高性能应用程序的必经之路。

正如我在上周承诺的那样,在本文中,我将展示splitAsStream的实际应用,在该应用中,处理流而不是仅将整个字符串拆分为数组并对其进行处理确实有意义。

您可能已经从文章标题中猜到了该应用程序正在将文件与一些标记分离。 只要文件长度不超过2GB,就可以将其表示为CharSequence 。 限制来自以下事实: CharSequence的长度是int值,在Java中为32位。 文件长度为long ,为64位。 由于从文件读取比从已在内存中的字符串读取要慢得多,因此使用流处理的惰性是有意义的。 我们需要的是一个由文件备份的字符序列实现。 如果可以的话,我们可以编写如下程序:

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类的实现可以是:

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) {
            }
        }
    }
}

该实现仅从文件中读取那么多字节,这是对charAtsubSequence的最后一个实际方法调用所需要的。

如果您有兴趣,可以改进此代码,以仅将真正需要的字节保留在内存中,并删除已经返回到流中的字节。 要知道不需要什么字节,上一篇文章提供了一个很好的提示,那就是splitAsStream绝不会接触索引比最后一次调用subSequence的第一个( start )参数小的subSequence 。 但是,如果您以丢掉字符的方式实现代码,并且如果有人要访问已经抛出的字符而失败,那么它将无法真正实现CharSequence接口,尽管只要使用splitAsStream ,它仍然可以很好地工作。只要实现不会改变,并且开始需要一些已经传递的字符。 (嗯,我不确定,但是如果我们使用一些复杂的正则表达式作为拆分模式,也可能会发生这种情况。)

编码愉快!

翻译自: https://www.javacodegeeks.com/2017/11/split-file-stream.html