|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package java.nio.file; |
|
|
|
import sun.nio.cs.ISO_8859_1; |
|
import sun.nio.cs.UTF_8; |
|
import sun.nio.cs.US_ASCII; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.IOException; |
|
import java.io.UncheckedIOException; |
|
import java.nio.ByteBuffer; |
|
import java.nio.channels.Channels; |
|
import java.nio.channels.FileChannel; |
|
import java.nio.channels.ReadableByteChannel; |
|
import java.nio.charset.Charset; |
|
import java.util.HashSet; |
|
import java.util.Set; |
|
import java.util.Spliterator; |
|
import java.util.concurrent.atomic.AtomicInteger; |
|
import java.util.function.Consumer; |
|
|
|
import jdk.internal.access.SharedSecrets; |
|
import jdk.internal.access.JavaNioAccess; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class FileChannelLinesSpliterator implements Spliterator<String> { |
|
|
|
static final Set<String> SUPPORTED_CHARSET_NAMES; |
|
static { |
|
SUPPORTED_CHARSET_NAMES = new HashSet<>(); |
|
SUPPORTED_CHARSET_NAMES.add(UTF_8.INSTANCE.name()); |
|
SUPPORTED_CHARSET_NAMES.add(ISO_8859_1.INSTANCE.name()); |
|
SUPPORTED_CHARSET_NAMES.add(US_ASCII.INSTANCE.name()); |
|
} |
|
|
|
private final FileChannel fc; |
|
private final Charset cs; |
|
private int index; |
|
private final int fence; |
|
|
|
|
|
private ByteBuffer buffer; |
|
|
|
private BufferedReader reader; |
|
|
|
// Number of references to the shared mapped buffer. Initialized to unity |
|
// when the buffer is created by the root spliterator. Incremented in the |
|
// sub-spliterator constructor. Decremented when 'buffer' transitions from |
|
// non-null to null, either when traversing begins or if the spliterator is |
|
// closed before traversal. If the count is zero after decrementing, then |
|
|
|
private final AtomicInteger bufRefCount; |
|
|
|
FileChannelLinesSpliterator(FileChannel fc, Charset cs, int index, int fence) { |
|
this.fc = fc; |
|
this.cs = cs; |
|
this.index = index; |
|
this.fence = fence; |
|
this.bufRefCount = new AtomicInteger(); |
|
} |
|
|
|
private FileChannelLinesSpliterator(FileChannel fc, Charset cs, int index, |
|
int fence, ByteBuffer buffer, AtomicInteger bufRefCount) { |
|
this.fc = fc; |
|
this.cs = cs; |
|
this.index = index; |
|
this.fence = fence; |
|
this.buffer = buffer; |
|
this.bufRefCount = bufRefCount; |
|
this.bufRefCount.incrementAndGet(); |
|
} |
|
|
|
@Override |
|
public boolean tryAdvance(Consumer<? super String> action) { |
|
String line = readLine(); |
|
if (line != null) { |
|
action.accept(line); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
@Override |
|
public void forEachRemaining(Consumer<? super String> action) { |
|
String line; |
|
while ((line = readLine()) != null) { |
|
action.accept(line); |
|
} |
|
} |
|
|
|
private BufferedReader getBufferedReader() { |
|
|
|
|
|
|
|
*/ |
|
ReadableByteChannel rrbc = new ReadableByteChannel() { |
|
@Override |
|
public int read(ByteBuffer dst) throws IOException { |
|
int bytesToRead = fence - index; |
|
if (bytesToRead == 0) |
|
return -1; |
|
|
|
int bytesRead; |
|
if (bytesToRead < dst.remaining()) { |
|
// The number of bytes to read is less than remaining |
|
// bytes in the buffer |
|
|
|
int oldLimit = dst.limit(); |
|
dst.limit(dst.position() + bytesToRead); |
|
bytesRead = fc.read(dst, index); |
|
dst.limit(oldLimit); |
|
} else { |
|
bytesRead = fc.read(dst, index); |
|
} |
|
if (bytesRead == -1) { |
|
index = fence; |
|
return bytesRead; |
|
} |
|
|
|
index += bytesRead; |
|
return bytesRead; |
|
} |
|
|
|
@Override |
|
public boolean isOpen() { |
|
return fc.isOpen(); |
|
} |
|
|
|
@Override |
|
public void close() throws IOException { |
|
fc.close(); |
|
} |
|
}; |
|
return new BufferedReader(Channels.newReader(rrbc, cs.newDecoder(), -1)); |
|
} |
|
|
|
private String readLine() { |
|
if (reader == null) { |
|
reader = getBufferedReader(); |
|
unmap(); |
|
} |
|
|
|
try { |
|
return reader.readLine(); |
|
} catch (IOException e) { |
|
throw new UncheckedIOException(e); |
|
} |
|
} |
|
|
|
private ByteBuffer getMappedByteBuffer() { |
|
try { |
|
return fc.map(FileChannel.MapMode.READ_ONLY, 0, fence); |
|
} catch (IOException e) { |
|
throw new UncheckedIOException(e); |
|
} |
|
} |
|
|
|
@Override |
|
public Spliterator<String> trySplit() { |
|
|
|
if (reader != null) |
|
return null; |
|
|
|
ByteBuffer b; |
|
if ((b = buffer) == null) { |
|
b = buffer = getMappedByteBuffer(); |
|
bufRefCount.set(1); |
|
} |
|
|
|
final int hi = fence, lo = index; |
|
|
|
|
|
int mid = (lo + hi) >>> 1; |
|
int c = b.get(mid); |
|
if (c == '\n') { |
|
mid++; |
|
} else if (c == '\r') { |
|
|
|
if (++mid < hi && b.get(mid) == '\n') { |
|
mid++; |
|
} |
|
} else { |
|
// TODO give up after a certain distance from the mid point? |
|
|
|
int midL = mid - 1; |
|
int midR = mid + 1; |
|
mid = 0; |
|
while (midL > lo && midR < hi) { |
|
|
|
c = b.get(midL--); |
|
if (c == '\n' || c == '\r') { |
|
// If c is "\r" then no need to check for "\r\n" |
|
|
|
mid = midL + 2; |
|
break; |
|
} |
|
|
|
|
|
c = b.get(midR++); |
|
if (c == '\n' || c == '\r') { |
|
mid = midR; |
|
|
|
if (c == '\r' && mid < hi && b.get(mid) == '\n') { |
|
mid++; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
return (mid > lo && mid < hi) |
|
? new FileChannelLinesSpliterator(fc, cs, lo, index = mid, |
|
b, bufRefCount) |
|
: null; |
|
} |
|
|
|
@Override |
|
public long estimateSize() { |
|
// Use the number of bytes as an estimate. |
|
// We could divide by a constant that is the average number of |
|
|
|
return fence - index; |
|
} |
|
|
|
@Override |
|
public long getExactSizeIfKnown() { |
|
return -1; |
|
} |
|
|
|
@Override |
|
public int characteristics() { |
|
return Spliterator.ORDERED | Spliterator.NONNULL; |
|
} |
|
|
|
private void unmap() { |
|
if (buffer != null) { |
|
ByteBuffer b = buffer; |
|
buffer = null; |
|
if (bufRefCount.decrementAndGet() == 0) { |
|
JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess(); |
|
try { |
|
nioAccess.unmapper(b).unmap(); |
|
} catch (UnsupportedOperationException ignored) { |
|
} |
|
} |
|
} |
|
} |
|
|
|
void close() { |
|
unmap(); |
|
} |
|
} |