使用Java Streams从文本文件一次读取X行?

时间:2018-01-11 21:34:24

标签: java java-8 stream java-stream

我有一个简单的旧文本文件"行以新行字符结尾。出于任意原因,我需要一次读取和解析此文本文件4(X为通用)行。

我想将Java流用于此任务,我知道我可以将文件转换为如下所示的流:

try (Stream<String> stream = Files.lines(Paths.get("file.txt""))) {
    stream.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

但是我如何使用Java的Stream API来实现&#34;束缚&#34;该文件分为4组连续组?

5 个答案:

答案 0 :(得分:4)

这是java.util.Scanner的工作。在Java 9中,您只需使用

即可
try(Scanner s = new Scanner(PATH)) {
    s.findAll("(.*\\R){1,4}")
     .map(mr -> Arrays.asList(mr.group().split("\\R")))
     .forEach(System.out::println);
}

对于Java 8,您可以使用this answerfindAll的后端口。为该方法添加import static后,您可以像

一样使用它
try(Scanner s = new Scanner(PATH)) {
    findAll(s, Pattern.compile("(.*\\R){1,4}"))
        .map(mr -> Arrays.asList(mr.group().split("\\R")))
        .forEach(System.out::println);
}

请注意,匹配操作的结果是一个包含最多四行的字符串(最后一行较少)。如果这适合您的后续操作,您可以跳过将该字符串拆分为单独的行。

您甚至可以使用MatchResult的属性来更复杂地处理块,例如

try(Scanner s = new Scanner(PATH)) {
    findAll(s, Pattern.compile("(.*)\\R(?:(.*)\\R)?(?:(.*)\\R)?(?:(.*)\\R)?"))
        .flatMap(mr -> IntStream.rangeClosed(1, 4)
                           .mapToObj(ix -> mr.group(ix)==null? null: ix+": "+mr.group(ix)))
        .filter(Objects::nonNull)
        .forEach(System.out::println);
}

答案 1 :(得分:3)

这是使用Guava的Iterators.partition方法的简单方法:

try (Stream<String> stream = Files.lines(Paths.get("file.txt""))) {

    Iterator<List<String>> iterator = Iterators.partition(stream.iterator(), 4);

    // iterator.next() returns each chunk as a List<String>

} catch (IOException e) {
    // handle exception properly
}

这仅适用于顺序处理,但如果您从磁盘读取文件,我很难想象并行处理会带来什么好处......

编辑:如果你想,而不是使用迭代器,你可以将它再次转换为流:

Stream<List<String>> targetStream = StreamSupport.stream(
      Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED),
      false);

答案 2 :(得分:2)

如果你想坚持使用流,我看到的唯一解决方案是编写自己的自定义收集器。它并非用于此目的,但您可以使用它。

private static final class CustomCollector {

    private List<String> list = new ArrayList<>();

    private List<String> acumulateList = new ArrayList<>();

    public void accept(String str) {
        acumulateList.add(str);
        if (acumulateList.size() == 4) { // acumulate 4 strings
            String collect = String.join("", acumulateList);
            // I just joined them in on string, you can do whatever you want
            list.add(collect);
            acumulateList = new ArrayList<>();
        }
    }

    public CustomCollector combine(CustomCollector other) {
        throw new UnsupportedOperationException("Parallel Stream not supported");
    }

    public List<String> finish() {
        if(!acumulateList.isEmpty()) {
            list.add(String.join("", acumulateList));
        }
        return list;
    }

    public static Collector<String, ?, List<String>> collector() {
        return Collector.of(CustomCollector::new, CustomCollector::accept, CustomCollector::combine, CustomCollector::finish);
    }
}

并像这样使用它:

stream.collect(CustomCollector.collector());

答案 3 :(得分:2)

如果您已开放使用RxJava,则可以使用其buffer功能:

Stream<String> stream = Files.lines(Paths.get("file.txt"))

Observable.fromIterable(stream::iterator)
          .buffer(4)                      // Observable<List<String>>
          .map(x -> String.join(", ", x)) // Observable<String>
          .forEach(System.out::println);

buffer创建一个Observable,用于收集特定大小的列表中的元素。在上面的示例中,我通过map添加了另一个转换,以使列表更适合打印,但您可以根据需要转换Observable。例如,如果您有一个方法processChunkList<String>作为参数并返回String,则可以执行以下操作:

Observable<String> fileObs =
    Observable.fromIterable(stream::iterator)
              .buffer(4)
              .map(x -> processChunk(x));

答案 4 :(得分:2)

有一种方法可以使用标准Java 8 Stream API将文件内容分区并处理为import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class ReadFileWithStream { public static void main(String[] args) throws IOException { // Path to a file to read final Path path = Paths.get(ReadFileWithStream.class.getResource("/input.txt")‌​.toURI()); final AtomicInteger counter = new AtomicInteger(0); // Size of a chunk final int size = 4; final Collection<List<String>> partitioned = Files.lines(path) .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / size)) .values(); partitioned.forEach(System.out::println); } } - 大小的块。您可以使用Collectors.groupingBy()将文件内容分区为块 - 您可以将它们收集为[0, 0, 0, 2] [0, -3, 2, 0] [1, -3, -8, 0] [2, -12, -11, -11] [-8, -1, -8, 0] [2, -1, 2, -1] ... and so on ,也可以在收集所有行时应用一些处理(例如,您可以将它们连接到单个字符串)。

看一下下面的例子:

List<String>

我的输入文件contains some numbers (one number at a line),当我运行以下代码时,我会得到类似的内容:

Collection<List<String>>

Collectors.groupingBy()也允许我使用不同的下游收集器。默认情况下,Collectors.toList()正在使用,因此我的结果会累积到Collection<Integer>中,我得到final Collection<Integer> partitioned = Files.lines(path) .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / size, Collectors.summingInt(Integer::valueOf))) .values(); 作为最终结果。

假设我想要读取4个大小的块,我想将所有数字加在一个块中。在这种情况下,我将使用Collectors.summingInt()作为我的下游函数,返回的结果为2 -1 -10 -32 -17 2 -11 -49 ... and so on

Collectors.groupingBy()

输出:

width = 80
height = 80
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=( width, height, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))
#model.add(Dropout(0.5))
model.add(Dense(2))
model.add(Activation('softmax'))

model.compile(loss='sparse_categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

最后但并非最不重要。 img = image.load_img('Test2.jpg', target_size=(80, 80)) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) images = np.vstack([x]) classes = model.predict_proba(images, batch_size=1) print(classes) [[ 0. 1.]] 返回一个地图,其中值按特定键分组。这就是为什么最后我们调用Map.values()来获取此地图中包含的值的集合。

希望它有所帮助。