Path: blob/master/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java
41159 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.nio.file;2526import sun.nio.cs.ISO_8859_1;27import sun.nio.cs.UTF_8;28import sun.nio.cs.US_ASCII;2930import java.io.BufferedReader;31import java.io.IOException;32import java.io.UncheckedIOException;33import java.nio.ByteBuffer;34import java.nio.channels.Channels;35import java.nio.channels.FileChannel;36import java.nio.channels.ReadableByteChannel;37import java.nio.charset.Charset;38import java.util.HashSet;39import java.util.Set;40import java.util.Spliterator;41import java.util.concurrent.atomic.AtomicInteger;42import java.util.function.Consumer;4344import jdk.internal.access.SharedSecrets;45import jdk.internal.access.JavaNioAccess;4647/**48* A file-based lines spliterator, leveraging a shared mapped byte buffer and49* associated file channel, covering lines of a file for character encodings50* where line feed characters can be easily identified from character encoded51* bytes.52*53* <p>54* When the root spliterator is first split a mapped byte buffer will be created55* over the file for it's size that was observed when the stream was created.56* Thus a mapped byte buffer is only required for parallel stream execution.57* Sub-spliterators will share that mapped byte buffer. Splitting will use the58* mapped byte buffer to find the closest line feed characters(s) to the left or59* right of the mid-point of covered range of bytes of the file. If a line feed60* is found then the spliterator is split with returned spliterator containing61* the identified line feed characters(s) at the end of it's covered range of62* bytes.63*64* <p>65* Traversing will create a buffered reader, derived from the file channel, for66* the range of bytes of the file. The lines are then read from that buffered67* reader. Once traversing commences no further splitting can be performed and68* the reference to the mapped byte buffer will be set to null.69*/70final class FileChannelLinesSpliterator implements Spliterator<String> {7172static final Set<String> SUPPORTED_CHARSET_NAMES;73static {74SUPPORTED_CHARSET_NAMES = new HashSet<>();75SUPPORTED_CHARSET_NAMES.add(UTF_8.INSTANCE.name());76SUPPORTED_CHARSET_NAMES.add(ISO_8859_1.INSTANCE.name());77SUPPORTED_CHARSET_NAMES.add(US_ASCII.INSTANCE.name());78}7980private final FileChannel fc;81private final Charset cs;82private int index;83private final int fence;8485// Null before first split, non-null when splitting, null when traversing86private ByteBuffer buffer;87// Non-null when traversing88private BufferedReader reader;8990// Number of references to the shared mapped buffer. Initialized to unity91// when the buffer is created by the root spliterator. Incremented in the92// sub-spliterator constructor. Decremented when 'buffer' transitions from93// non-null to null, either when traversing begins or if the spliterator is94// closed before traversal. If the count is zero after decrementing, then95// the buffer is unmapped.96private final AtomicInteger bufRefCount;9798FileChannelLinesSpliterator(FileChannel fc, Charset cs, int index, int fence) {99this.fc = fc;100this.cs = cs;101this.index = index;102this.fence = fence;103this.bufRefCount = new AtomicInteger();104}105106private FileChannelLinesSpliterator(FileChannel fc, Charset cs, int index,107int fence, ByteBuffer buffer, AtomicInteger bufRefCount) {108this.fc = fc;109this.cs = cs;110this.index = index;111this.fence = fence;112this.buffer = buffer;113this.bufRefCount = bufRefCount;114this.bufRefCount.incrementAndGet();115}116117@Override118public boolean tryAdvance(Consumer<? super String> action) {119String line = readLine();120if (line != null) {121action.accept(line);122return true;123} else {124return false;125}126}127128@Override129public void forEachRemaining(Consumer<? super String> action) {130String line;131while ((line = readLine()) != null) {132action.accept(line);133}134}135136private BufferedReader getBufferedReader() {137/**138* A readable byte channel that reads bytes from an underlying139* file channel over a specified range.140*/141ReadableByteChannel rrbc = new ReadableByteChannel() {142@Override143public int read(ByteBuffer dst) throws IOException {144int bytesToRead = fence - index;145if (bytesToRead == 0)146return -1;147148int bytesRead;149if (bytesToRead < dst.remaining()) {150// The number of bytes to read is less than remaining151// bytes in the buffer152// Snapshot the limit, reduce it, read, then restore153int oldLimit = dst.limit();154dst.limit(dst.position() + bytesToRead);155bytesRead = fc.read(dst, index);156dst.limit(oldLimit);157} else {158bytesRead = fc.read(dst, index);159}160if (bytesRead == -1) {161index = fence;162return bytesRead;163}164165index += bytesRead;166return bytesRead;167}168169@Override170public boolean isOpen() {171return fc.isOpen();172}173174@Override175public void close() throws IOException {176fc.close();177}178};179return new BufferedReader(Channels.newReader(rrbc, cs.newDecoder(), -1));180}181182private String readLine() {183if (reader == null) {184reader = getBufferedReader();185unmap();186}187188try {189return reader.readLine();190} catch (IOException e) {191throw new UncheckedIOException(e);192}193}194195private ByteBuffer getMappedByteBuffer() {196try {197return fc.map(FileChannel.MapMode.READ_ONLY, 0, fence);198} catch (IOException e) {199throw new UncheckedIOException(e);200}201}202203@Override204public Spliterator<String> trySplit() {205// Cannot split after partial traverse206if (reader != null)207return null;208209ByteBuffer b;210if ((b = buffer) == null) {211b = buffer = getMappedByteBuffer();212bufRefCount.set(1);213}214215final int hi = fence, lo = index;216217// Check if line separator hits the mid point218int mid = (lo + hi) >>> 1;219int c = b.get(mid);220if (c == '\n') {221mid++;222} else if (c == '\r') {223// Check if a line separator of "\r\n"224if (++mid < hi && b.get(mid) == '\n') {225mid++;226}227} else {228// TODO give up after a certain distance from the mid point?229// Scan to the left and right of the mid point230int midL = mid - 1;231int midR = mid + 1;232mid = 0;233while (midL > lo && midR < hi) {234// Sample to the left235c = b.get(midL--);236if (c == '\n' || c == '\r') {237// If c is "\r" then no need to check for "\r\n"238// since the subsequent value was previously checked239mid = midL + 2;240break;241}242243// Sample to the right244c = b.get(midR++);245if (c == '\n' || c == '\r') {246mid = midR;247// Check if line-separator is "\r\n"248if (c == '\r' && mid < hi && b.get(mid) == '\n') {249mid++;250}251break;252}253}254}255256// The left spliterator will have the line-separator at the end257return (mid > lo && mid < hi)258? new FileChannelLinesSpliterator(fc, cs, lo, index = mid,259b, bufRefCount)260: null;261}262263@Override264public long estimateSize() {265// Use the number of bytes as an estimate.266// We could divide by a constant that is the average number of267// characters per-line, but that constant will be factored out.268return fence - index;269}270271@Override272public long getExactSizeIfKnown() {273return -1;274}275276@Override277public int characteristics() {278return Spliterator.ORDERED | Spliterator.NONNULL;279}280281private void unmap() {282if (buffer != null) {283ByteBuffer b = buffer;284buffer = null;285if (bufRefCount.decrementAndGet() == 0) {286JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();287try {288nioAccess.unmapper(b).unmap();289} catch (UnsupportedOperationException ignored) {290}291}292}293}294295void close() {296unmap();297}298}299300301