comparison src/com/tmate/hgkit/fs/DataAccessProvider.java @ 26:71a9ba42cee8

Memory-mapped files for bigger files. Defect reading number of bytes greater than size of the buffer fixed
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sun, 09 Jan 2011 15:59:54 +0100
parents 6f9aca1a97be
children b0a15cefdfd6
comparison
equal deleted inserted replaced
25:da8ccbfae64d 26:71a9ba42cee8
29 return new DataAccess(); 29 return new DataAccess();
30 } 30 }
31 try { 31 try {
32 FileChannel fc = new FileInputStream(f).getChannel(); 32 FileChannel fc = new FileInputStream(f).getChannel();
33 if (fc.size() > mapioMagicBoundary) { 33 if (fc.size() > mapioMagicBoundary) {
34 return new MemoryMapFileAccess(fc, fc.size()); 34 // TESTS: bufLen of 1024 was used to test MemMapFileAccess
35 return new MemoryMapFileAccess(fc, fc.size(), mapioMagicBoundary);
35 } else { 36 } else {
36 // XXX once implementation is more or less stable, 37 // XXX once implementation is more or less stable,
37 // may want to try ByteBuffer.allocateDirect() to see 38 // may want to try ByteBuffer.allocateDirect() to see
38 // if there's any performance gain. 39 // if there's any performance gain.
39 boolean useDirectBuffer = false; 40 boolean useDirectBuffer = false;
41 // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize
40 return new FileAccess(fc, fc.size(), bufferSize, useDirectBuffer); 42 return new FileAccess(fc, fc.size(), bufferSize, useDirectBuffer);
41 } 43 }
42 } catch (IOException ex) { 44 } catch (IOException ex) {
43 // unlikely to happen, we've made sure file exists. 45 // unlikely to happen, we've made sure file exists.
44 ex.printStackTrace(); // FIXME log error 46 ex.printStackTrace(); // FIXME log error
48 50
49 // DOESN'T WORK YET 51 // DOESN'T WORK YET
50 private static class MemoryMapFileAccess extends DataAccess { 52 private static class MemoryMapFileAccess extends DataAccess {
51 private FileChannel fileChannel; 53 private FileChannel fileChannel;
52 private final long size; 54 private final long size;
53 private long position = 0; 55 private long position = 0; // always points to buffer's absolute position in the file
54 56 private final int memBufferSize;
55 public MemoryMapFileAccess(FileChannel fc, long channelSize) { 57 private MappedByteBuffer buffer;
58
59 public MemoryMapFileAccess(FileChannel fc, long channelSize, int /*long?*/ bufferSize) {
56 fileChannel = fc; 60 fileChannel = fc;
57 size = channelSize; 61 size = channelSize;
58 } 62 memBufferSize = bufferSize;
59 63 }
64
65 @Override
66 public boolean isEmpty() {
67 return position + (buffer == null ? 0 : buffer.position()) >= size;
68 }
69
60 @Override 70 @Override
61 public void seek(long offset) { 71 public void seek(long offset) {
62 position = offset; 72 assert offset >= 0;
73 // offset may not necessarily be further than current position in the file (e.g. rewind)
74 if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) {
75 buffer.position((int) (offset - position));
76 } else {
77 position = offset;
78 buffer = null;
79 }
63 } 80 }
64 81
65 @Override 82 @Override
66 public void skip(int bytes) throws IOException { 83 public void skip(int bytes) throws IOException {
67 position += bytes; 84 assert bytes >= 0;
68 } 85 if (buffer == null) {
69 86 position += bytes;
70 private boolean fill() throws IOException { 87 return;
71 final int BUFFER_SIZE = 8 * 1024; 88 }
89 if (buffer.remaining() > bytes) {
90 buffer.position(buffer.position() + bytes);
91 } else {
92 position += buffer.position() + bytes;
93 buffer = null;
94 }
95 }
96
97 private void fill() throws IOException {
98 if (buffer != null) {
99 position += buffer.position();
100 }
72 long left = size - position; 101 long left = size - position;
73 MappedByteBuffer rv = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < BUFFER_SIZE ? left : BUFFER_SIZE); 102 buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < memBufferSize ? left : memBufferSize);
74 position += rv.capacity(); 103 }
75 return rv.hasRemaining(); 104
105 @Override
106 public void readBytes(byte[] buf, int offset, int length) throws IOException {
107 if (buffer == null || !buffer.hasRemaining()) {
108 fill();
109 }
110 // XXX in fact, we may try to create a MappedByteBuffer of exactly length size here, and read right away
111 while (length > 0) {
112 int tail = buffer.remaining();
113 if (tail == 0) {
114 throw new IOException();
115 }
116 if (tail >= length) {
117 buffer.get(buf, offset, length);
118 } else {
119 buffer.get(buf, offset, tail);
120 fill();
121 }
122 offset += tail;
123 length -= tail;
124 }
125 }
126
127 @Override
128 public byte readByte() throws IOException {
129 if (buffer == null || !buffer.hasRemaining()) {
130 fill();
131 }
132 if (buffer.hasRemaining()) {
133 return buffer.get();
134 }
135 throw new IOException();
76 } 136 }
77 137
78 @Override 138 @Override
79 public void done() { 139 public void done() {
140 buffer = null;
80 if (fileChannel != null) { 141 if (fileChannel != null) {
81 try { 142 try {
82 fileChannel.close(); 143 fileChannel.close();
83 } catch (IOException ex) { 144 } catch (IOException ex) {
84 ex.printStackTrace(); // log debug 145 ex.printStackTrace(); // log debug
150 return buffer.hasRemaining(); 211 return buffer.hasRemaining();
151 } 212 }
152 213
153 @Override 214 @Override
154 public void readBytes(byte[] buf, int offset, int length) throws IOException { 215 public void readBytes(byte[] buf, int offset, int length) throws IOException {
155 final int tail = buffer.remaining(); 216 if (!buffer.hasRemaining()) {
156 if (tail >= length) { 217 fill();
157 buffer.get(buf, offset, length); 218 }
158 } else { 219 while (length > 0) {
159 buffer.get(buf, offset, tail); 220 int tail = buffer.remaining();
160 if (fill()) { 221 if (tail == 0) {
161 buffer.get(buf, offset + tail, length - tail); 222 throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past isEmpty() == true are made.
223 }
224 if (tail >= length) {
225 buffer.get(buf, offset, length);
162 } else { 226 } else {
163 throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past nonEmpty() == false are made. 227 buffer.get(buf, offset, tail);
164 } 228 fill();
229 }
230 offset += tail;
231 length -= tail;
165 } 232 }
166 } 233 }
167 234
168 @Override 235 @Override
169 public byte readByte() throws IOException { 236 public byte readByte() throws IOException {