Mercurial > hg4j
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 { |
