1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.io.input;
18
19 import static org.apache.commons.io.IOUtils.EOF;
20
21 import java.io.BufferedInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.FileChannel;
26 import java.nio.channels.FileChannel.MapMode;
27 import java.nio.file.Path;
28 import java.nio.file.StandardOpenOption;
29
30 import org.apache.commons.io.build.AbstractStreamBuilder;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public final class MemoryMappedFileInputStream extends AbstractInputStream {
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public static class Builder extends AbstractStreamBuilder<MemoryMappedFileInputStream, Builder> {
93
94
95
96
97 public Builder() {
98 setBufferSizeDefault(DEFAULT_BUFFER_SIZE);
99 setBufferSize(DEFAULT_BUFFER_SIZE);
100 }
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 @Override
124 public MemoryMappedFileInputStream get() throws IOException {
125 return new MemoryMappedFileInputStream(this);
126 }
127 }
128
129
130
131
132
133 private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
134
135 private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]).asReadOnlyBuffer();
136
137
138
139
140
141
142
143 public static Builder builder() {
144 return new Builder();
145 }
146
147 private final int bufferSize;
148 private final FileChannel channel;
149 private ByteBuffer buffer = EMPTY_BUFFER;
150
151
152
153
154 private long nextBufferPosition;
155
156
157
158
159
160
161
162 private MemoryMappedFileInputStream(final Builder builder) throws IOException {
163 this.bufferSize = builder.getBufferSize();
164 this.channel = FileChannel.open(builder.getPath(), StandardOpenOption.READ);
165 }
166
167 @Override
168 public int available() throws IOException {
169
170 return buffer.remaining();
171 }
172
173 private void cleanBuffer() {
174 if (ByteBufferCleaner.isSupported() && buffer.isDirect()) {
175 ByteBufferCleaner.clean(buffer);
176 }
177 }
178
179 @Override
180 public void close() throws IOException {
181 if (!isClosed()) {
182 cleanBuffer();
183 buffer = EMPTY_BUFFER;
184 channel.close();
185 super.close();
186 }
187 }
188
189 int getBufferSize() {
190 return bufferSize;
191 }
192
193 private void nextBuffer() throws IOException {
194 final long remainingInFile = channel.size() - nextBufferPosition;
195 if (remainingInFile > 0) {
196 final long amountToMap = Math.min(remainingInFile, bufferSize);
197 cleanBuffer();
198 buffer = channel.map(MapMode.READ_ONLY, nextBufferPosition, amountToMap);
199 nextBufferPosition += amountToMap;
200 } else {
201 buffer = EMPTY_BUFFER;
202 }
203 }
204
205 @Override
206 public int read() throws IOException {
207 checkOpen();
208 if (!buffer.hasRemaining()) {
209 nextBuffer();
210 if (!buffer.hasRemaining()) {
211 return EOF;
212 }
213 }
214 return Short.toUnsignedInt(buffer.get());
215 }
216
217 @Override
218 public int read(final byte[] b, final int off, final int len) throws IOException {
219 checkOpen();
220 if (!buffer.hasRemaining()) {
221 nextBuffer();
222 if (!buffer.hasRemaining()) {
223 return EOF;
224 }
225 }
226 final int numBytes = Math.min(buffer.remaining(), len);
227 buffer.get(b, off, numBytes);
228 return numBytes;
229 }
230
231 @Override
232 public long skip(final long n) throws IOException {
233 checkOpen();
234 if (n <= 0) {
235 return 0;
236 }
237 if (n <= buffer.remaining()) {
238 buffer.position((int) (buffer.position() + n));
239 return n;
240 }
241 final long remainingInFile = channel.size() - nextBufferPosition;
242 final long skipped = buffer.remaining() + Math.min(remainingInFile, n - buffer.remaining());
243 nextBufferPosition += skipped - buffer.remaining();
244 nextBuffer();
245 return skipped;
246 }
247
248 }