1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.mvstore; |
7 | |
8 | import java.nio.ByteBuffer; |
9 | import java.util.HashMap; |
10 | |
11 | /** |
12 | * A chunk of data, containing one or multiple pages. |
13 | * <p> |
14 | * Chunks are page aligned (each page is usually 4096 bytes). |
15 | * There are at most 67 million (2^26) chunks, |
16 | * each chunk is at most 2 GB large. |
17 | */ |
18 | public class Chunk { |
19 | |
20 | /** |
21 | * The maximum chunk id. |
22 | */ |
23 | public static final int MAX_ID = (1 << 26) - 1; |
24 | |
25 | /** |
26 | * The maximum length of a chunk header, in bytes. |
27 | */ |
28 | static final int MAX_HEADER_LENGTH = 1024; |
29 | |
30 | /** |
31 | * The length of the chunk footer. The longest footer is: |
32 | * chunk:ffffffff,block:ffffffffffffffff, |
33 | * version:ffffffffffffffff,fletcher:ffffffff |
34 | */ |
35 | static final int FOOTER_LENGTH = 128; |
36 | |
37 | /** |
38 | * The chunk id. |
39 | */ |
40 | public final int id; |
41 | |
42 | /** |
43 | * The start block number within the file. |
44 | */ |
45 | public long block; |
46 | |
47 | /** |
48 | * The length in number of blocks. |
49 | */ |
50 | public int len; |
51 | |
52 | /** |
53 | * The total number of pages in this chunk. |
54 | */ |
55 | public int pageCount; |
56 | |
57 | /** |
58 | * The number of pages still alive. |
59 | */ |
60 | public int pageCountLive; |
61 | |
62 | /** |
63 | * The sum of the max length of all pages. |
64 | */ |
65 | public long maxLen; |
66 | |
67 | /** |
68 | * The sum of the max length of all pages that are in use. |
69 | */ |
70 | public long maxLenLive; |
71 | |
72 | /** |
73 | * The garbage collection priority. Priority 0 means it needs to be |
74 | * collected, a high value means low priority. |
75 | */ |
76 | public int collectPriority; |
77 | |
78 | /** |
79 | * The position of the meta root. |
80 | */ |
81 | public long metaRootPos; |
82 | |
83 | /** |
84 | * The version stored in this chunk. |
85 | */ |
86 | public long version; |
87 | |
88 | /** |
89 | * When this chunk was created, in milliseconds after the store was created. |
90 | */ |
91 | public long time; |
92 | |
93 | /** |
94 | * When this chunk was no longer needed, in milliseconds after the store was |
95 | * created. After this, the chunk is kept alive a bit longer (in case it is |
96 | * referenced in older versions). |
97 | */ |
98 | public long unused; |
99 | |
100 | /** |
101 | * The last used map id. |
102 | */ |
103 | public int mapId; |
104 | |
105 | /** |
106 | * The predicted position of the next chunk. |
107 | */ |
108 | public long next; |
109 | |
110 | Chunk(int id) { |
111 | this.id = id; |
112 | } |
113 | |
114 | /** |
115 | * Read the header from the byte buffer. |
116 | * |
117 | * @param buff the source buffer |
118 | * @param start the start of the chunk in the file |
119 | * @return the chunk |
120 | */ |
121 | static Chunk readChunkHeader(ByteBuffer buff, long start) { |
122 | int pos = buff.position(); |
123 | byte[] data = new byte[Math.min(buff.remaining(), MAX_HEADER_LENGTH)]; |
124 | buff.get(data); |
125 | try { |
126 | for (int i = 0; i < data.length; i++) { |
127 | if (data[i] == '\n') { |
128 | // set the position to the start of the first page |
129 | buff.position(pos + i + 1); |
130 | String s = new String(data, 0, i, DataUtils.LATIN).trim(); |
131 | return fromString(s); |
132 | } |
133 | } |
134 | } catch (Exception e) { |
135 | // there could be various reasons |
136 | throw DataUtils.newIllegalStateException( |
137 | DataUtils.ERROR_FILE_CORRUPT, |
138 | "File corrupt reading chunk at position {0}", start, e); |
139 | } |
140 | throw DataUtils.newIllegalStateException( |
141 | DataUtils.ERROR_FILE_CORRUPT, |
142 | "File corrupt reading chunk at position {0}", start); |
143 | } |
144 | |
145 | /** |
146 | * Write the chunk header. |
147 | * |
148 | * @param buff the target buffer |
149 | * @param minLength the minimum length |
150 | */ |
151 | void writeChunkHeader(WriteBuffer buff, int minLength) { |
152 | long pos = buff.position(); |
153 | buff.put(asString().getBytes(DataUtils.LATIN)); |
154 | while (buff.position() - pos < minLength - 1) { |
155 | buff.put((byte) ' '); |
156 | } |
157 | if (minLength != 0 && buff.position() > minLength) { |
158 | throw DataUtils.newIllegalStateException( |
159 | DataUtils.ERROR_INTERNAL, |
160 | "Chunk metadata too long"); |
161 | } |
162 | buff.put((byte) '\n'); |
163 | } |
164 | |
165 | /** |
166 | * Get the metadata key for the given chunk id. |
167 | * |
168 | * @param chunkId the chunk id |
169 | * @return the metadata key |
170 | */ |
171 | static String getMetaKey(int chunkId) { |
172 | return "chunk." + Integer.toHexString(chunkId); |
173 | } |
174 | |
175 | /** |
176 | * Build a block from the given string. |
177 | * |
178 | * @param s the string |
179 | * @return the block |
180 | */ |
181 | public static Chunk fromString(String s) { |
182 | HashMap<String, String> map = DataUtils.parseMap(s); |
183 | int id = DataUtils.readHexInt(map, "chunk", 0); |
184 | Chunk c = new Chunk(id); |
185 | c.block = DataUtils.readHexLong(map, "block", 0); |
186 | c.len = DataUtils.readHexInt(map, "len", 0); |
187 | c.pageCount = DataUtils.readHexInt(map, "pages", 0); |
188 | c.pageCountLive = DataUtils.readHexInt(map, "livePages", c.pageCount); |
189 | c.mapId = DataUtils.readHexInt(map, "map", 0); |
190 | c.maxLen = DataUtils.readHexLong(map, "max", 0); |
191 | c.maxLenLive = DataUtils.readHexLong(map, "liveMax", c.maxLen); |
192 | c.metaRootPos = DataUtils.readHexLong(map, "root", 0); |
193 | c.time = DataUtils.readHexLong(map, "time", 0); |
194 | c.unused = DataUtils.readHexLong(map, "unused", 0); |
195 | c.version = DataUtils.readHexLong(map, "version", id); |
196 | c.next = DataUtils.readHexLong(map, "next", 0); |
197 | return c; |
198 | } |
199 | |
200 | /** |
201 | * Calculate the fill rate in %. 0 means empty, 100 means full. |
202 | * |
203 | * @return the fill rate |
204 | */ |
205 | public int getFillRate() { |
206 | if (maxLenLive <= 0) { |
207 | return 0; |
208 | } else if (maxLenLive == maxLen) { |
209 | return 100; |
210 | } |
211 | return 1 + (int) (98 * maxLenLive / maxLen); |
212 | } |
213 | |
214 | @Override |
215 | public int hashCode() { |
216 | return id; |
217 | } |
218 | |
219 | @Override |
220 | public boolean equals(Object o) { |
221 | return o instanceof Chunk && ((Chunk) o).id == id; |
222 | } |
223 | |
224 | /** |
225 | * Get the chunk data as a string. |
226 | * |
227 | * @return the string |
228 | */ |
229 | public String asString() { |
230 | StringBuilder buff = new StringBuilder(); |
231 | DataUtils.appendMap(buff, "chunk", id); |
232 | DataUtils.appendMap(buff, "block", block); |
233 | DataUtils.appendMap(buff, "len", len); |
234 | if (maxLen != maxLenLive) { |
235 | DataUtils.appendMap(buff, "liveMax", maxLenLive); |
236 | } |
237 | if (pageCount != pageCountLive) { |
238 | DataUtils.appendMap(buff, "livePages", pageCountLive); |
239 | } |
240 | DataUtils.appendMap(buff, "map", mapId); |
241 | DataUtils.appendMap(buff, "max", maxLen); |
242 | if (next != 0) { |
243 | DataUtils.appendMap(buff, "next", next); |
244 | } |
245 | DataUtils.appendMap(buff, "pages", pageCount); |
246 | DataUtils.appendMap(buff, "root", metaRootPos); |
247 | DataUtils.appendMap(buff, "time", time); |
248 | if (unused != 0) { |
249 | DataUtils.appendMap(buff, "unused", unused); |
250 | } |
251 | DataUtils.appendMap(buff, "version", version); |
252 | return buff.toString(); |
253 | } |
254 | |
255 | byte[] getFooterBytes() { |
256 | StringBuilder buff = new StringBuilder(); |
257 | DataUtils.appendMap(buff, "chunk", id); |
258 | DataUtils.appendMap(buff, "block", block); |
259 | DataUtils.appendMap(buff, "version", version); |
260 | byte[] bytes = buff.toString().getBytes(DataUtils.LATIN); |
261 | int checksum = DataUtils.getFletcher32(bytes, bytes.length); |
262 | DataUtils.appendMap(buff, "fletcher", checksum); |
263 | while (buff.length() < Chunk.FOOTER_LENGTH - 1) { |
264 | buff.append(' '); |
265 | } |
266 | buff.append("\n"); |
267 | return buff.toString().getBytes(DataUtils.LATIN); |
268 | } |
269 | |
270 | @Override |
271 | public String toString() { |
272 | return asString(); |
273 | } |
274 | |
275 | } |
276 | |