├── CSharp ├── FileDB.cs ├── FileDB.cs.meta ├── FileDictionary.cs ├── FileDictionary.cs.meta ├── FileDictionaryDelegate.cs ├── FileDictionaryDelegate.cs.meta ├── SubFileStream.cs └── SubFileStream.cs.meta ├── LICENSE ├── Lua └── FileDictionary.lua └── README.md /CSharp/FileDB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using UnityEngine; 8 | 9 | namespace ATRI.Collection 10 | { 11 | public class FileDB : IDisposable 12 | { 13 | #region member 14 | // fileOffset, subFileLen, subFile isCopyTo File 15 | private Dictionary> tableInfo = new Dictionary(); 16 | private Dictionary tableDict = new Dictionary(); 17 | private FileStream _fs; 18 | private string _path; 19 | private bool _inited = false; 20 | #endregion 21 | 22 | public FileDB(string filePath) 23 | { 24 | _path = filePath; 25 | } 26 | 27 | #region public 28 | 29 | public bool TryGetValueByTableName(string tableName, TKey key, out TValue value) where TKey : IEquatable 30 | { 31 | Init(); 32 | object dict; 33 | if (!tableDict.TryGetValue(tableName, out dict)) 34 | { 35 | ValueTuple info; 36 | if (tableInfo.TryGetValue(tableName, out info)) 37 | { 38 | // copy value to mergefile 39 | if (!info.Item3) 40 | { 41 | var tempBuff = FileDictionaryUtils.moveTempBuff; 42 | FileStream subFile = new FileStream(info.Item4, FileMode.Open, FileAccess.Read); 43 | int bytesRead; 44 | _fs.Seek(info.Item1, SeekOrigin.Begin); 45 | while ((bytesRead = subFile.Read(tempBuff, 0, tempBuff.Length)) > 0) 46 | { 47 | _fs.Write(tempBuff, 0, bytesRead); 48 | } 49 | } 50 | SubFileStream subFileStream = new SubFileStream(_fs, info.Item1, info.Item2); 51 | dict = new FileDictionary(subFileStream); 52 | tableInfo.Remove(tableName); 53 | tableDict.Add(tableName, dict); 54 | } 55 | else 56 | { 57 | Debug.LogError("tableInfo not init"); 58 | value = default(TValue); 59 | return false; 60 | } 61 | } 62 | 63 | FileDictionary fd = (FileDictionary)dict; 64 | return fd.TryGetValue(key, out value); 65 | } 66 | 67 | public void Dispose() 68 | { 69 | if (_fs != null) 70 | { 71 | _fs.Dispose(); 72 | } 73 | } 74 | 75 | public void MergeRuntime(params string[] paths) 76 | { 77 | if (File.Exists(_path)) 78 | { 79 | File.Delete(_path); 80 | } 81 | 82 | tableInfo.Clear(); 83 | _inited = false; 84 | 85 | int len = 0; 86 | foreach (var filePath in paths) 87 | { 88 | FileInfo fi = new FileInfo(filePath); 89 | if (!fi.Exists) 90 | { 91 | throw new Exception(filePath + " not exit! please check"); 92 | } 93 | string fileName = Path.GetFileNameWithoutExtension(filePath); 94 | if (tableInfo.ContainsKey(fileName)) 95 | { 96 | Debug.LogError(fileName + " already exit!plz check in:[\n"+ string.Concat(paths, "\n") +"]"); 97 | return; 98 | } 99 | 100 | tableInfo.Add(fileName, (len, (int)fi.Length, false, filePath)); 101 | len += (int)fi.Length; 102 | } 103 | } 104 | 105 | #if UNITY_EDITOR 106 | public void MergeEditor(params string[] paths) 107 | { 108 | if (File.Exists(_path)) 109 | { 110 | File.Delete(_path); 111 | } 112 | 113 | tableInfo.Clear(); 114 | _inited = false; 115 | 116 | FileStream fs = new FileStream(_path, FileMode.CreateNew, FileAccess.Write); 117 | BinaryWriter bfw = new BinaryWriter(fs); 118 | 119 | MemoryStream headMs = new MemoryStream(); 120 | BinaryWriter bw = new BinaryWriter(headMs); 121 | 122 | int len = 0; 123 | bw.Write(paths.Length); 124 | Dictionary closeSet = new Dictionary(); 125 | foreach (var filePath in paths) 126 | { 127 | FileInfo fi = new FileInfo(filePath); 128 | if (!fi.Exists) 129 | { 130 | throw new Exception(filePath + "not eixt! please check"); 131 | } 132 | 133 | string fileName = Path.GetFileNameWithoutExtension(filePath); 134 | if (closeSet.ContainsKey(fileName)) 135 | { 136 | throw new Exception(filePath + "file name the same with path:" + closeSet[fileName]); 137 | } 138 | 139 | byte[] strData = UTF8Encoding.UTF8.GetBytes(fileName); 140 | bw.Write(strData.Length); 141 | bw.Write(strData); 142 | bw.Write(len); 143 | bw.Write((int)fi.Length); 144 | 145 | len += (int)fi.Length; 146 | } 147 | 148 | int totalHeadLen = (int)headMs.Length; 149 | bw.Flush(); 150 | bw.Close(); 151 | 152 | bfw.Write(totalHeadLen); 153 | bfw.Write(headMs.ToArray()); 154 | 155 | var tempBuff = FileDictionaryUtils.moveTempBuff; 156 | 157 | foreach (var filePath in paths) 158 | { 159 | int bytesRead; 160 | FileStream subFile = new FileStream(filePath, FileMode.Open, FileAccess.Read); 161 | while ((bytesRead = subFile.Read(tempBuff, 0, tempBuff.Length)) > 0) 162 | { 163 | fs.Write(tempBuff, 0, bytesRead); 164 | } 165 | } 166 | 167 | bfw.Flush(); 168 | bfw.Close(); 169 | 170 | } 171 | #endif 172 | 173 | #endregion 174 | 175 | private void Init() 176 | { 177 | if (_inited) return; 178 | _inited = true; 179 | 180 | // merge in runtime 181 | if (tableInfo.Count <= 0) 182 | { 183 | _fs = new FileStream(_path, FileMode.OpenOrCreate, FileAccess.Read); 184 | BinaryReader br = new BinaryReader(_fs); 185 | int totalHeadLen = br.ReadInt32(); 186 | int fileLen = br.ReadInt32(); 187 | 188 | for (int i = 0; i < fileLen; i++) 189 | { 190 | int strLen = br.ReadInt32(); 191 | byte[] datas = br.ReadBytes(strLen); 192 | string fileName = UTF8Encoding.UTF8.GetString(datas); 193 | int offset = br.ReadInt32(); 194 | int onefileLen = br.ReadInt32(); 195 | 196 | tableInfo.Add(fileName, (offset + totalHeadLen + 4, onefileLen, true, "")); 197 | } 198 | } 199 | else 200 | { 201 | _fs = new FileStream(_path, FileMode.OpenOrCreate, FileAccess.ReadWrite); 202 | } 203 | } 204 | 205 | } 206 | } -------------------------------------------------------------------------------- /CSharp/FileDB.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 542bb2be1bc6405a83daa4ce046a79ad 3 | timeCreated: 1716804391 -------------------------------------------------------------------------------- /CSharp/FileDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using UnityEngine; 7 | 8 | namespace ATRI.Collection 9 | { 10 | public unsafe class FileDictionary : IDictionary,IDisposable 11 | where TKey : IEquatable 12 | { 13 | #region member 14 | 15 | private IFileDictionaryDelegate funs; 16 | 17 | const int SIZE_COUNT = 2; 18 | const int MAX_CONFLIGCT_TIME = 8; 19 | private Stream _stream; 20 | private string _fileName; 21 | private int _capacity = 0; 22 | private int _dataOffset = 0; 23 | private int _size = 0; 24 | 25 | #endregion 26 | 27 | ~FileDictionary() 28 | { 29 | Dispose(false); 30 | } 31 | 32 | #region cache 33 | 34 | private const int CACHE_COUNT = 256; 35 | private Dictionary>> _cache = new Dictionary>>(CACHE_COUNT); 36 | private LinkedList> _lruQueue = new LinkedList>(); 37 | private ICollection _keys; 38 | private ICollection _values; 39 | 40 | private bool TryGetCache(TKey key, out TValue value) 41 | { 42 | LinkedListNode> ln; 43 | bool ret = _cache.TryGetValue(key, out ln); 44 | 45 | if (ret) 46 | { 47 | value = ln.Value.Value; 48 | _lruQueue.Remove(ln); 49 | _lruQueue.AddLast(ln); 50 | } 51 | else 52 | { 53 | value = default(TValue); 54 | } 55 | 56 | return ret; 57 | } 58 | 59 | private void AddToCache(TKey key, TValue value) 60 | { 61 | if (_lruQueue.Count >= CACHE_COUNT) 62 | { 63 | var first = _lruQueue.First; 64 | _lruQueue.RemoveFirst(); 65 | 66 | first.Value = new KeyValuePair(key, value); 67 | _cache.Add(key, first); 68 | _lruQueue.AddLast(first); 69 | } 70 | else 71 | { 72 | LinkedListNode> lruNode = new LinkedListNode>(new KeyValuePair(key, value)); 73 | _cache.Add(key, lruNode); 74 | _lruQueue.AddLast(lruNode); 75 | } 76 | } 77 | 78 | #endregion 79 | 80 | #region private 81 | 82 | public static int FindNextPowerOfTwo(int number) 83 | { 84 | if (number <= 0) 85 | { 86 | return 1; // 最小的2幂是2^0 = 1 87 | } 88 | 89 | int result = 1; 90 | while (result < number) 91 | { 92 | result <<= 1; // 左移一位,相当于乘以2 93 | } 94 | 95 | return result; 96 | } 97 | 98 | private void Resize(int capacity) 99 | { 100 | _stream.Seek(0, SeekOrigin.Begin); 101 | // 写入capacity 102 | WriteInt(capacity); 103 | WriteInt(_size); 104 | 105 | int* p; 106 | fixed (byte* bp = FileDictionaryUtils.intBuff) 107 | { 108 | p = (int*)bp; 109 | } 110 | 111 | *p = -1; 112 | 113 | if (_dataOffset != 0) // 不是第一次resize 114 | { 115 | // 移动数据区的数据 116 | int endIndex = (int)_stream.Length; 117 | int delta = (capacity - _capacity) * 4; 118 | int index = endIndex; 119 | 120 | while (index > _dataOffset) 121 | { 122 | int newIndex = index - FileDictionaryUtils.MOVE_BUFF_SIZE; 123 | if (_dataOffset > newIndex) 124 | { 125 | newIndex = _dataOffset; 126 | } 127 | 128 | int size = index - newIndex; 129 | 130 | _stream.Seek(newIndex, SeekOrigin.Begin); 131 | _stream.Read(FileDictionaryUtils.moveTempBuff, 0, size); 132 | 133 | _stream.Seek(newIndex + delta, SeekOrigin.Begin); 134 | _stream.Write(FileDictionaryUtils.moveTempBuff, 0, size); 135 | 136 | index = newIndex; 137 | } 138 | 139 | // 把索引区的数据都读取到内存中 140 | byte[] oldData = new byte[4 * (_capacity + MAX_CONFLIGCT_TIME)]; 141 | int* oldDataPoint = null; 142 | 143 | _stream.Seek(4 * SIZE_COUNT, SeekOrigin.Begin); 144 | _stream.Read(oldData, 0, 4 * (_capacity + MAX_CONFLIGCT_TIME)); 145 | 146 | // 开辟空间,并把索引区数据全部改成-1 147 | _stream.Seek(4 * SIZE_COUNT, SeekOrigin.Begin); 148 | for (int i = 0; i < capacity + MAX_CONFLIGCT_TIME; i++) 149 | { 150 | _stream.Write(FileDictionaryUtils.intBuff, 0, 4); 151 | } 152 | 153 | _dataOffset = (capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4; 154 | int oldCapacity = _capacity; 155 | _capacity = capacity; 156 | // 重新插入 索引区数据 157 | fixed (byte* b = oldData) 158 | { 159 | oldDataPoint = (int*)b; 160 | } 161 | 162 | for (int i = 0; i < oldCapacity + MAX_CONFLIGCT_TIME; i++) 163 | { 164 | int offset = *(oldDataPoint + i); 165 | ResetVal(offset); 166 | } 167 | } 168 | else 169 | { 170 | // 开辟空间默认值为-1 171 | for (int i = 0; i < capacity + MAX_CONFLIGCT_TIME; i++) 172 | { 173 | _stream.Write(FileDictionaryUtils.intBuff, 0, 4); 174 | } 175 | 176 | _dataOffset = (capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4; 177 | _capacity = capacity; 178 | _size = 0; 179 | } 180 | 181 | _stream.Flush(); 182 | } 183 | 184 | private int ReadInt() 185 | { 186 | _stream.Read(FileDictionaryUtils.intBuff, 0, 4); 187 | 188 | int* p; 189 | fixed (byte* bp = FileDictionaryUtils.intBuff) 190 | { 191 | p = (int*)bp; 192 | } 193 | 194 | return *p; 195 | } 196 | 197 | private void WriteInt(int val) 198 | { 199 | int* p; 200 | fixed (byte* bp = FileDictionaryUtils.intBuff) 201 | { 202 | p = (int*)bp; 203 | } 204 | 205 | *p = val; 206 | 207 | _stream.Write(FileDictionaryUtils.intBuff, 0, 4); 208 | } 209 | 210 | private void ResetVal(int offset) 211 | { 212 | if (offset < 0) 213 | { 214 | return; 215 | } 216 | 217 | // 拿到原来的string值 218 | int len; 219 | TKey key = GetKey(offset, out len); 220 | 221 | DoSetVal(key, offset); 222 | } 223 | 224 | private void DoSetVal(TKey key, int offset, int resizeTime = 0) 225 | { 226 | // 计算CRC32 并计算出来一个索引 227 | int index = (int)(funs.GetHashCode(key) & (_capacity - 1)); 228 | 229 | bool isReset = false; 230 | for (int i = 0; i < MAX_CONFLIGCT_TIME; i++) 231 | { 232 | int ii = index + i; 233 | if (SetVal(ii, offset)) 234 | { 235 | isReset = true; 236 | break; 237 | } 238 | } 239 | 240 | if (!isReset) 241 | { 242 | if (resizeTime > 4) 243 | { 244 | StringBuilder sb = new StringBuilder(); 245 | sb.Append("hash code conflict is too high,you can change hashcode,values:"); 246 | for (int i = 0; i < MAX_CONFLIGCT_TIME; i++) 247 | { 248 | int ii = index + i; 249 | _stream.Seek((ii + SIZE_COUNT) * 4, SeekOrigin.Begin); 250 | int addr = ReadInt(); 251 | int keyLen = 0; 252 | var k = GetKey(addr, out keyLen); 253 | sb.Append(k); 254 | sb.Append(","); 255 | } 256 | sb.Append(key); 257 | throw new Exception(sb.ToString()); 258 | } 259 | else 260 | { 261 | Resize(_capacity * 2); 262 | resizeTime++; 263 | } 264 | 265 | 266 | DoSetVal(key, offset, resizeTime); 267 | } 268 | } 269 | 270 | private bool SetVal(int index, int offset) 271 | { 272 | _stream.Seek((index + SIZE_COUNT) * 4, SeekOrigin.Begin); 273 | int v = ReadInt(); 274 | if (v < 0) 275 | { 276 | _stream.Seek(-4, SeekOrigin.Current); 277 | WriteInt(offset); 278 | return true; 279 | } 280 | 281 | return false; 282 | } 283 | 284 | private TKey GetKey(int offset, out int len) 285 | { 286 | _stream.Seek(offset + _dataOffset, SeekOrigin.Begin); 287 | len = ReadInt(); 288 | return funs.DeserializeKey(_stream, len); 289 | } 290 | 291 | private bool TryGetValue(TKey key, out TValue value, out int ofs, out int index, out int oldValueLen, out int keyLen) 292 | { 293 | value = default(TValue); 294 | ofs = 0; 295 | // 计算CRC32 并计算出来一个索引 296 | index = (int)(funs.GetHashCode(key) & (_capacity - 1)); 297 | oldValueLen = 0; 298 | keyLen = 0; 299 | 300 | bool isFind = false; 301 | TKey k; 302 | int offset; 303 | for (int i = 0; i < MAX_CONFLIGCT_TIME; i++) 304 | { 305 | int ii = index + i; 306 | _stream.Seek((ii + SIZE_COUNT) * 4, SeekOrigin.Begin); 307 | offset = ReadInt(); 308 | // 没有值 309 | if (offset < 0) 310 | { 311 | return false; 312 | } 313 | 314 | k = GetKey(offset, out keyLen); 315 | if (k.Equals(key)) 316 | { 317 | ofs = offset; 318 | oldValueLen = ReadInt(); 319 | value = funs.DeserializeValue(_stream, oldValueLen); 320 | AddToCache(key, value); 321 | return true; 322 | } 323 | } 324 | 325 | return isFind; 326 | } 327 | 328 | #endregion 329 | 330 | #region public 331 | 332 | // 333 | public FileDictionary(SubFileStream subFileStream) 334 | { 335 | _stream = subFileStream; 336 | funs = FileDictionaryDelegateFactory.GetFileDictionaryDelegate(); 337 | if (funs == null) 338 | { 339 | throw new Exception(string.Format("Implement IFileDictionaryDelegate<{0}, {1}>", typeof(TKey).Name, typeof(TValue).Name) ); 340 | } 341 | 342 | _capacity = ReadInt(); 343 | _size = ReadInt(); 344 | _dataOffset = (_capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4; 345 | } 346 | 347 | 348 | // 文件存在就从文件里面读取 capacity,文件不存在就用capacity new 一个 文件hash表出来 349 | public FileDictionary(string fileName, int capacity = 1024, bool isClear = false) 350 | { 351 | bool fileExit = File.Exists(fileName); 352 | if (isClear && fileExit) 353 | { 354 | File.Delete(fileName); 355 | fileExit = false; 356 | } 357 | 358 | _stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); 359 | _fileName = fileName; 360 | funs = FileDictionaryDelegateFactory.GetFileDictionaryDelegate(); 361 | if (funs == null) 362 | { 363 | throw new Exception(string.Format("Implement IFileDictionaryDelegate<{0}, {1}>", typeof(TKey).Name, typeof(TValue).Name) ); 364 | } 365 | 366 | // 文件不存在就先写入点东西 367 | if (!fileExit) 368 | { 369 | //capacity = Math.Max(1024, capacity); 370 | capacity = FindNextPowerOfTwo(capacity); 371 | Resize(capacity); 372 | } 373 | else 374 | { 375 | _capacity = ReadInt(); 376 | _size = ReadInt(); 377 | _dataOffset = (_capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4; 378 | } 379 | } 380 | 381 | public void Flush() 382 | { 383 | _stream.Flush(); 384 | } 385 | 386 | public void Close() 387 | { 388 | _stream.Flush(); 389 | _stream.Close(); 390 | _stream = null; 391 | } 392 | 393 | #endregion 394 | 395 | #region interface 396 | 397 | public bool Contains(object key) 398 | { 399 | throw new NotImplementedException(); 400 | } 401 | 402 | public IEnumerator> GetEnumerator() 403 | { 404 | _stream.Seek(_dataOffset, SeekOrigin.Begin); 405 | 406 | for (int i = 0; i < _size; i++) 407 | { 408 | int len = ReadInt(); 409 | var key = funs.DeserializeKey(_stream, len); 410 | len = ReadInt(); 411 | var value = funs.DeserializeValue(_stream, len); 412 | 413 | yield return new KeyValuePair(key, value); 414 | } 415 | } 416 | 417 | 418 | IEnumerator IEnumerable.GetEnumerator() 419 | { 420 | return GetEnumerator(); 421 | } 422 | 423 | public void AddNewValue(TKey key, TValue value) 424 | { 425 | if (_stream is SubFileStream) 426 | { 427 | Debug.LogError("subfile stream is readonly"); 428 | return; 429 | } 430 | int offset; 431 | ++_size; 432 | _stream.Seek(4, SeekOrigin.Begin); 433 | WriteInt(_size); 434 | offset = (int)_stream.Length - _dataOffset; 435 | 436 | _stream.Seek(offset + _dataOffset, SeekOrigin.Begin); 437 | funs.SerializeKey(_stream, key); 438 | funs.SerializeValue(_stream, value); 439 | 440 | DoSetVal(key, offset); 441 | } 442 | 443 | public void Add(TKey key, TValue value) 444 | { 445 | if (_stream is SubFileStream) 446 | { 447 | Debug.LogError("subfile stream is readonly"); 448 | return; 449 | } 450 | 451 | TValue oldValue; 452 | int offset, index, oldValueLen, keyLen; 453 | bool hasValue = TryGetValue(key, out oldValue, out offset, out index, out oldValueLen, out keyLen); 454 | if (hasValue) 455 | { 456 | if (oldValue.Equals(value)) return; 457 | } 458 | int newValueLen = funs.GetValueLen(value); 459 | if (hasValue && oldValueLen >= newValueLen) 460 | { 461 | _stream.Seek(offset + _dataOffset + 4 + keyLen, SeekOrigin.Begin); 462 | funs.SerializeValue(_stream, value); 463 | } 464 | else if (hasValue) 465 | { 466 | offset = (int)_stream.Length - _dataOffset; 467 | 468 | _stream.Seek((index + SIZE_COUNT) * 4, SeekOrigin.Begin); 469 | WriteInt(offset); 470 | 471 | _stream.Seek(offset + _dataOffset, SeekOrigin.Begin); 472 | funs.SerializeKey(_stream, key); 473 | funs.SerializeValue(_stream, value); 474 | } 475 | else 476 | { 477 | ++_size; 478 | _stream.Seek(4, SeekOrigin.Begin); 479 | WriteInt(_size); 480 | offset = (int)_stream.Length - _dataOffset; 481 | 482 | _stream.Seek(offset + _dataOffset, SeekOrigin.Begin); 483 | funs.SerializeKey(_stream, key); 484 | funs.SerializeValue(_stream, value); 485 | 486 | DoSetVal(key, offset); 487 | } 488 | } 489 | 490 | public void Add(KeyValuePair item) 491 | { 492 | Add(item.Key, item.Value); 493 | } 494 | 495 | public void Add(IDictionary dict) 496 | { 497 | foreach (var item in dict) 498 | { 499 | Add(item.Key, item.Value); 500 | } 501 | } 502 | 503 | public void Clear() 504 | { 505 | _stream.Close(); 506 | File.Delete(_fileName); 507 | _dataOffset = 0; 508 | _size = 0; 509 | 510 | _stream = new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); 511 | var capacity = _capacity; 512 | _capacity = 0; 513 | Resize(capacity); 514 | } 515 | 516 | public bool Contains(KeyValuePair item) 517 | { 518 | return ContainsKey(item.Key); 519 | } 520 | 521 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 522 | { 523 | throw new NotImplementedException(); 524 | } 525 | 526 | public bool Remove(KeyValuePair item) 527 | { 528 | return Remove(item.Key); 529 | } 530 | 531 | public void CopyTo(Array array, int index) 532 | { 533 | throw new NotImplementedException(); 534 | } 535 | 536 | public int Count 537 | { 538 | get { return _size; } 539 | } 540 | 541 | public bool IsSynchronized { get; } 542 | public object SyncRoot { get; } 543 | 544 | public bool IsReadOnly 545 | { 546 | get { return false; } 547 | } 548 | 549 | public object this[object key] 550 | { 551 | get => throw new NotImplementedException(); 552 | set => throw new NotImplementedException(); 553 | } 554 | 555 | public bool ContainsKey(TKey key) 556 | { 557 | TValue value; 558 | bool find = TryGetValue(key, out value); 559 | return find; 560 | } 561 | 562 | public bool Remove(TKey key) 563 | { 564 | TValue oldValue; 565 | int offset, index, oldLen, keyLen; 566 | bool hasValue = TryGetValue(key, out oldValue, out offset, out index, out oldLen, out keyLen); 567 | if (hasValue) 568 | { 569 | _stream.Seek((index + SIZE_COUNT) * 4, SeekOrigin.Begin); 570 | WriteInt(-1); 571 | _size--; 572 | _stream.Seek(4, SeekOrigin.Begin); 573 | WriteInt(_size); 574 | } 575 | 576 | return hasValue; 577 | } 578 | 579 | public bool TryGetValue(TKey key, out TValue value) 580 | { 581 | // LRU cahce 582 | bool ret = TryGetCache(key, out value); 583 | if (ret) return ret; 584 | 585 | int ofs, index, oldLen, keyLen; 586 | return TryGetValue(key, out value, out ofs, out index, out oldLen, out keyLen); 587 | } 588 | 589 | public TValue this[TKey key] 590 | { 591 | get 592 | { 593 | TValue value; 594 | bool find = TryGetValue(key, out value); 595 | if (!find) 596 | { 597 | value = default(TValue); 598 | } 599 | 600 | return value; 601 | } 602 | set { Add(key, value); } 603 | } 604 | 605 | public Dictionary ToDictionary() 606 | { 607 | Dictionary result = new Dictionary(Count); 608 | _stream.Seek(_dataOffset, SeekOrigin.Begin); 609 | 610 | for (int i = 0; i < _size; i++) 611 | { 612 | int len = ReadInt(); 613 | var key = funs.DeserializeKey(_stream, len); 614 | len = ReadInt(); 615 | var value = funs.DeserializeValue(_stream, len); 616 | 617 | result.Add(key, value); 618 | } 619 | 620 | return result; 621 | } 622 | 623 | public ICollection Keys 624 | { 625 | get { throw new NotImplementedException(); } 626 | } 627 | 628 | public ICollection Values 629 | { 630 | get { throw new NotImplementedException(); } 631 | } 632 | 633 | #endregion 634 | 635 | private void ReleaseUnmanagedResources() 636 | { 637 | // TODO release unmanaged resources here 638 | } 639 | 640 | private void Dispose(bool disposing) 641 | { 642 | ReleaseUnmanagedResources(); 643 | if (disposing) 644 | { 645 | _stream?.Dispose(); 646 | } 647 | } 648 | 649 | public void Dispose() 650 | { 651 | Dispose(true); 652 | GC.SuppressFinalize(this); 653 | } 654 | } 655 | } -------------------------------------------------------------------------------- /CSharp/FileDictionary.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0c80ab3dcf2cac4381a3aadb4278acc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CSharp/FileDictionaryDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using UnityEngine.Experimental.XR; 6 | 7 | namespace ATRI.Collection 8 | { 9 | public static class CRC32 10 | { 11 | private static readonly uint[] table; 12 | 13 | static CRC32() 14 | { 15 | uint polynomial = 0xEDB88320; 16 | table = new uint[256]; 17 | for (uint i = 0; i < 256; i++) 18 | { 19 | uint crc = i; 20 | for (int j = 0; j < 8; j++) 21 | { 22 | if ((crc & 1) == 1) 23 | { 24 | crc = (crc >> 1) ^ polynomial; 25 | } 26 | else 27 | { 28 | crc >>= 1; 29 | } 30 | } 31 | 32 | table[i] = crc; 33 | } 34 | } 35 | 36 | public static uint Compute(byte[] bytes) 37 | { 38 | uint crc = 0xFFFFFFFF; 39 | foreach (byte b in bytes) 40 | { 41 | crc = (crc >> 8) ^ table[(crc ^ b) & 0xFF]; 42 | } 43 | 44 | return ~crc; 45 | } 46 | } 47 | 48 | public unsafe static class FileDictionaryUtils 49 | { 50 | public static byte[] intBuff = new byte[4]; 51 | 52 | public const int MOVE_BUFF_SIZE = 4 * 4096; 53 | 54 | public static byte[] moveTempBuff = new byte[MOVE_BUFF_SIZE]; 55 | 56 | public const int SERIALIZE_BUFF_SIZE = 4096 * 4; 57 | 58 | public static byte[] SerializeTempBuff = new byte[SERIALIZE_BUFF_SIZE]; 59 | 60 | public static void WriteInt(Stream stream, int val) 61 | { 62 | int* p; 63 | fixed (byte* bp = intBuff) 64 | { 65 | p = (int*)bp; 66 | } 67 | 68 | *p = val; 69 | 70 | stream.Write(intBuff, 0, 4); 71 | } 72 | 73 | public static int ReadInt(Stream stream) 74 | { 75 | stream.Read(intBuff, 0, 4); 76 | 77 | int* p; 78 | fixed (byte* bp = intBuff) 79 | { 80 | p = (int*)bp; 81 | } 82 | 83 | return *p; 84 | } 85 | 86 | #region int 87 | 88 | public static int DeserializeInt(Stream stream, int len) 89 | { 90 | byte[] intBuff = FileDictionaryUtils.intBuff; 91 | stream.Read(intBuff, 0, len); 92 | 93 | int* intb; 94 | fixed (byte* b = intBuff) 95 | { 96 | intb = (int*)(b); 97 | } 98 | 99 | return *intb; 100 | } 101 | 102 | public static void SerializeInt(Stream stream, int value) 103 | { 104 | WriteInt(stream, 4); 105 | 106 | byte[] intBuff = FileDictionaryUtils.intBuff; 107 | int* intb; 108 | fixed (byte* b = intBuff) 109 | { 110 | intb = (int*)(b); 111 | *intb = value; 112 | } 113 | stream.Write(intBuff, 0, 4); 114 | } 115 | #endregion 116 | 117 | #region bool 118 | 119 | public static bool DeserializeBool(Stream stream, int len) 120 | { 121 | byte[] intBuff = FileDictionaryUtils.intBuff; 122 | stream.Read(intBuff, 0, len); 123 | 124 | fixed (byte* b = intBuff) 125 | { 126 | return *b > (byte)0; 127 | } 128 | } 129 | 130 | public static void SerializeBool(Stream stream, bool value) 131 | { 132 | WriteInt(stream, 1); 133 | 134 | byte[] intBuff = FileDictionaryUtils.intBuff; 135 | fixed (byte* b = intBuff) 136 | { 137 | *b = value ? (byte)1: (byte)0; 138 | } 139 | stream.Write(intBuff, 0, 1); 140 | } 141 | #endregion 142 | 143 | #region byte[] 144 | public static byte[] DeserializeBytes(Stream stream, int len) 145 | { 146 | byte[] result = new byte[len]; 147 | stream.Read(result, 0, len); 148 | 149 | return result; 150 | } 151 | 152 | public static void SerializeBytes(Stream stream, byte[] value) 153 | { 154 | WriteInt(stream, value.Length); 155 | stream.Write(value, 0, value.Length); 156 | } 157 | #endregion 158 | 159 | #region string 160 | 161 | public static string DeserializeString(Stream stream, int len) 162 | { 163 | var buff = SerializeTempBuff; 164 | if (len > SerializeTempBuff.Length) 165 | { 166 | buff = new byte[len]; 167 | } 168 | 169 | stream.Read(buff, 0, len); 170 | return System.Text.Encoding.UTF8.GetString(buff, 0, len); 171 | } 172 | 173 | public static void SerializeString(Stream stream, string value) 174 | { 175 | int len = System.Text.Encoding.UTF8.GetByteCount(value); 176 | var buff = SerializeTempBuff; 177 | if (len <= SerializeTempBuff.Length) 178 | { 179 | System.Text.Encoding.UTF8.GetBytes(value, 0, value.Length, buff, 0); 180 | WriteInt(stream, len); 181 | stream.Write(buff, 0, len); 182 | } 183 | else 184 | { 185 | byte[] data = System.Text.Encoding.UTF8.GetBytes(value); 186 | WriteInt(stream, data.Length); 187 | stream.Write(data, 0, data.Length); 188 | } 189 | } 190 | 191 | #endregion 192 | 193 | #region string[] 194 | 195 | public static string[] DeserializeStringArray(Stream stream, int len) 196 | { 197 | byte[] data = SerializeTempBuff; 198 | 199 | if (len > data.Length) 200 | { 201 | data = new byte[len]; 202 | } 203 | 204 | stream.Read(data, 0, len); 205 | string[] result = null; 206 | fixed (byte* bp = data) 207 | { 208 | byte* p = bp; 209 | int count = (*(int*)p); 210 | p = p + 4; 211 | 212 | result = new string[count]; 213 | 214 | for (int i = 0; i < count; i++) 215 | { 216 | int strLen = (*(int*)p); 217 | p = p + 4; 218 | result[i] = System.Text.Encoding.UTF8.GetString(data, (int)(p - bp), strLen); 219 | p = p + strLen; 220 | } 221 | } 222 | 223 | return result; 224 | } 225 | 226 | public static int GetValueLen(string[] value) 227 | { 228 | // string len 229 | int len = 4; 230 | for (int i = 0, imax = value.Length; i < imax; i++) 231 | { 232 | len += 4 + UTF8Encoding.UTF8.GetByteCount(value[i]); 233 | } 234 | return len; 235 | } 236 | 237 | public static void SerializeStringArray(Stream stream, string[] value) 238 | { 239 | WriteInt(stream, GetValueLen(value)); 240 | WriteInt(stream, value.Length); 241 | 242 | for (int i = 0, imax = value.Length; i < imax; i++) 243 | { 244 | var data = System.Text.Encoding.UTF8.GetBytes(value[i]); 245 | WriteInt(stream, data.Length); 246 | stream.Write(data, 0, data.Length); 247 | } 248 | } 249 | #endregion 250 | } 251 | 252 | public interface IFileDictionaryDelegate 253 | { 254 | uint GetHashCode(TKey key); 255 | 256 | TValue DeserializeValue(Stream stream, int len); 257 | 258 | int GetValueLen(TValue value); 259 | 260 | void SerializeValue(Stream stream, TValue value); 261 | 262 | TKey DeserializeKey(Stream stream, int len); 263 | void SerializeKey(Stream stream, TKey value); 264 | } 265 | 266 | public class IntBytesFileDictionaryDelegate : IFileDictionaryDelegate 267 | { 268 | public static IntBytesFileDictionaryDelegate Default = new IntBytesFileDictionaryDelegate(); 269 | public uint GetHashCode(int key) 270 | { 271 | return (uint)key * 2654435761U; 272 | } 273 | 274 | public int GetValueLen(byte[] value) 275 | { 276 | return value.Length; 277 | } 278 | 279 | public byte[] DeserializeValue(Stream stream, int len) 280 | { 281 | return FileDictionaryUtils.DeserializeBytes(stream, len); 282 | } 283 | 284 | public void SerializeValue(Stream stream, byte[] value) 285 | { 286 | FileDictionaryUtils.SerializeBytes(stream, value); 287 | } 288 | 289 | public int DeserializeKey(Stream stream, int len) 290 | { 291 | return FileDictionaryUtils.DeserializeInt(stream, len); 292 | } 293 | 294 | public void SerializeKey(Stream stream, int value) 295 | { 296 | FileDictionaryUtils.SerializeInt(stream, value); 297 | } 298 | } 299 | 300 | public class IntBoolFileDictionaryDelegate : IFileDictionaryDelegate 301 | { 302 | public static IntBoolFileDictionaryDelegate Default = new IntBoolFileDictionaryDelegate(); 303 | public uint GetHashCode(int key) 304 | { 305 | return (uint)key * 2654435761U; 306 | } 307 | 308 | public bool DeserializeValue(Stream stream, int len) 309 | { 310 | return FileDictionaryUtils.DeserializeBool(stream, len); 311 | } 312 | 313 | public int GetValueLen(bool value) 314 | { 315 | return 1; 316 | } 317 | 318 | public void SerializeValue(Stream stream, bool value) 319 | { 320 | FileDictionaryUtils.SerializeBool(stream, value); 321 | } 322 | 323 | public int DeserializeKey(Stream stream, int len) 324 | { 325 | return FileDictionaryUtils.DeserializeInt(stream, len); 326 | } 327 | 328 | public void SerializeKey(Stream stream, int value) 329 | { 330 | FileDictionaryUtils.SerializeInt(stream, value); 331 | } 332 | 333 | } 334 | 335 | public class IntStringFileDictionaryDelegate : IFileDictionaryDelegate 336 | { 337 | public static IntStringFileDictionaryDelegate Default = new IntStringFileDictionaryDelegate(); 338 | 339 | public uint GetHashCode(int key) 340 | { 341 | return (uint)key * 2654435761U; 342 | } 343 | 344 | public int GetValueLen(string value) 345 | { 346 | return System.Text.Encoding.UTF8.GetByteCount(value); 347 | } 348 | 349 | public string DeserializeValue(Stream stream, int len) 350 | { 351 | return FileDictionaryUtils.DeserializeString(stream, len); 352 | } 353 | 354 | public void SerializeValue(Stream stream, string value) 355 | { 356 | FileDictionaryUtils.SerializeString(stream, value); 357 | } 358 | 359 | public int DeserializeKey(Stream stream, int len) 360 | { 361 | return FileDictionaryUtils.DeserializeInt(stream, len); 362 | } 363 | 364 | public void SerializeKey(Stream stream, int value) 365 | { 366 | FileDictionaryUtils.SerializeInt(stream, value); 367 | } 368 | } 369 | 370 | public class IntIntFileDictionaryDelegate : IFileDictionaryDelegate 371 | { 372 | public static IntIntFileDictionaryDelegate Default = new IntIntFileDictionaryDelegate(); 373 | public uint GetHashCode(int key) 374 | { 375 | return (uint)key * 2654435761U; 376 | } 377 | 378 | public int DeserializeValue(Stream stream, int len) 379 | { 380 | return FileDictionaryUtils.DeserializeInt(stream, len); 381 | } 382 | 383 | public int GetValueLen(int value) 384 | { 385 | return 4; 386 | } 387 | 388 | public void SerializeValue(Stream stream, int value) 389 | { 390 | FileDictionaryUtils.SerializeInt(stream, value); 391 | } 392 | 393 | public int DeserializeKey(Stream stream, int len) 394 | { 395 | return FileDictionaryUtils.DeserializeInt(stream, len); 396 | } 397 | 398 | public void SerializeKey(Stream stream, int value) 399 | { 400 | FileDictionaryUtils.SerializeInt(stream, value); 401 | } 402 | 403 | } 404 | 405 | public class IntStringArrayFileDictionaryDelegate : IFileDictionaryDelegate 406 | { 407 | public static IntStringArrayFileDictionaryDelegate Default = new IntStringArrayFileDictionaryDelegate(); 408 | public uint GetHashCode(int key) 409 | { 410 | return (uint)key * 2654435761U; 411 | } 412 | 413 | public string[] DeserializeValue(Stream stream, int len) 414 | { 415 | return FileDictionaryUtils.DeserializeStringArray(stream, len); 416 | } 417 | 418 | public int GetValueLen(string[] value) 419 | { 420 | // string len 421 | int len = 4; 422 | for (int i = 0, imax = value.Length; i < imax; i++) 423 | { 424 | len += 4 + UTF8Encoding.UTF8.GetByteCount(value[i]); 425 | } 426 | return len; 427 | } 428 | 429 | public void SerializeValue(Stream stream, string[] value) 430 | { 431 | FileDictionaryUtils.SerializeStringArray(stream, value); 432 | } 433 | 434 | public int DeserializeKey(Stream stream, int len) 435 | { 436 | return FileDictionaryUtils.DeserializeInt(stream, len); 437 | } 438 | 439 | public void SerializeKey(Stream stream, int value) 440 | { 441 | FileDictionaryUtils.SerializeInt(stream, value); 442 | } 443 | 444 | } 445 | 446 | public class StringBytesFileDictionaryDelegate : IFileDictionaryDelegate 447 | { 448 | public static StringBytesFileDictionaryDelegate Default = new StringBytesFileDictionaryDelegate(); 449 | 450 | public uint GetHashCode(string key) 451 | { 452 | var data = System.Text.Encoding.UTF8.GetBytes(key); 453 | return CRC32.Compute(data); 454 | } 455 | 456 | public int GetValueLen(byte[] value) 457 | { 458 | return value.Length; 459 | } 460 | 461 | public byte[] DeserializeValue(Stream stream, int len) 462 | { 463 | return FileDictionaryUtils.DeserializeBytes(stream, len); 464 | } 465 | 466 | public void SerializeValue(Stream stream, byte[] value) 467 | { 468 | FileDictionaryUtils.SerializeBytes(stream, value); 469 | } 470 | 471 | public string DeserializeKey(Stream stream, int len) 472 | { 473 | return FileDictionaryUtils.DeserializeString(stream, len); 474 | } 475 | 476 | public void SerializeKey(Stream stream, string value) 477 | { 478 | FileDictionaryUtils.SerializeString(stream, value); 479 | } 480 | 481 | } 482 | 483 | public class StringIntFileDictionaryDelegate : IFileDictionaryDelegate 484 | { 485 | public static StringIntFileDictionaryDelegate Default = new StringIntFileDictionaryDelegate(); 486 | 487 | public uint GetHashCode(string key) 488 | { 489 | var data = System.Text.Encoding.UTF8.GetBytes(key); 490 | return CRC32.Compute(data); 491 | } 492 | 493 | public int GetValueLen(int value) 494 | { 495 | return 4; 496 | } 497 | 498 | public int DeserializeValue(Stream stream, int len) 499 | { 500 | return FileDictionaryUtils.DeserializeInt(stream, len); 501 | } 502 | 503 | public void SerializeValue(Stream stream, int value) 504 | { 505 | FileDictionaryUtils.SerializeInt(stream, value); 506 | } 507 | 508 | public string DeserializeKey(Stream stream, int len) 509 | { 510 | return FileDictionaryUtils.DeserializeString(stream, len); 511 | } 512 | 513 | public void SerializeKey(Stream stream, string value) 514 | { 515 | FileDictionaryUtils.SerializeString(stream, value); 516 | } 517 | 518 | } 519 | 520 | public class StringBoolFileDictionaryDelegate : IFileDictionaryDelegate 521 | { 522 | public static StringBoolFileDictionaryDelegate Default = new StringBoolFileDictionaryDelegate(); 523 | 524 | public uint GetHashCode(string key) 525 | { 526 | var data = System.Text.Encoding.UTF8.GetBytes(key); 527 | return CRC32.Compute(data); 528 | } 529 | 530 | public int GetValueLen(bool value) 531 | { 532 | return 1; 533 | } 534 | 535 | public bool DeserializeValue(Stream stream, int len) 536 | { 537 | return FileDictionaryUtils.DeserializeBool(stream, len); 538 | } 539 | 540 | public void SerializeValue(Stream stream, bool value) 541 | { 542 | FileDictionaryUtils.SerializeBool(stream, value); 543 | } 544 | 545 | public string DeserializeKey(Stream stream, int len) 546 | { 547 | return FileDictionaryUtils.DeserializeString(stream, len); 548 | } 549 | 550 | public void SerializeKey(Stream stream, string value) 551 | { 552 | FileDictionaryUtils.SerializeString(stream, value); 553 | } 554 | 555 | } 556 | 557 | public class StringStringFileDictionaryDelegate : IFileDictionaryDelegate 558 | { 559 | public static StringStringFileDictionaryDelegate Default = new StringStringFileDictionaryDelegate(); 560 | 561 | public uint GetHashCode(string key) 562 | { 563 | var data = System.Text.Encoding.UTF8.GetBytes(key); 564 | return CRC32.Compute(data); 565 | } 566 | 567 | public int GetValueLen(string value) 568 | { 569 | return System.Text.Encoding.UTF8.GetByteCount(value); 570 | } 571 | 572 | public string DeserializeValue(Stream stream, int len) 573 | { 574 | return FileDictionaryUtils.DeserializeString(stream, len); 575 | } 576 | 577 | public void SerializeValue(Stream stream, string value) 578 | { 579 | FileDictionaryUtils.SerializeString(stream, value); 580 | } 581 | 582 | public string DeserializeKey(Stream stream, int len) 583 | { 584 | return FileDictionaryUtils.DeserializeString(stream, len); 585 | } 586 | 587 | public void SerializeKey(Stream stream, string value) 588 | { 589 | FileDictionaryUtils.SerializeString(stream, value); 590 | } 591 | } 592 | 593 | public class StringStringArrayFileDictionaryDelegate : IFileDictionaryDelegate 594 | { 595 | public static StringStringArrayFileDictionaryDelegate Default = new StringStringArrayFileDictionaryDelegate(); 596 | public uint GetHashCode(string key) 597 | { 598 | var data = System.Text.Encoding.UTF8.GetBytes(key); 599 | return CRC32.Compute(data); 600 | } 601 | 602 | public string[] DeserializeValue(Stream stream, int len) 603 | { 604 | return FileDictionaryUtils.DeserializeStringArray(stream, len); 605 | } 606 | 607 | public int GetValueLen(string[] value) 608 | { 609 | // string len 610 | int len = 4; 611 | for (int i = 0, imax = value.Length; i < imax; i++) 612 | { 613 | len += 4 + UTF8Encoding.UTF8.GetByteCount(value[i]); 614 | } 615 | return len; 616 | } 617 | 618 | public void SerializeValue(Stream stream, string[] value) 619 | { 620 | FileDictionaryUtils.SerializeStringArray(stream, value); 621 | } 622 | 623 | public string DeserializeKey(Stream stream, int len) 624 | { 625 | return FileDictionaryUtils.DeserializeString(stream, len); 626 | } 627 | 628 | public void SerializeKey(Stream stream, string value) 629 | { 630 | FileDictionaryUtils.SerializeString(stream, value); 631 | } 632 | } 633 | 634 | public static class FileDictionaryDelegateFactory 635 | { 636 | public static IFileDictionaryDelegate GetFileDictionaryDelegate() 637 | { 638 | if (typeof(TKey) == typeof(string) && typeof(TValue) == typeof(byte[])) 639 | return (IFileDictionaryDelegate)StringBytesFileDictionaryDelegate.Default; 640 | 641 | if (typeof(TKey) == typeof(string) && typeof(TValue) == typeof(string)) 642 | return (IFileDictionaryDelegate)StringStringFileDictionaryDelegate.Default; 643 | 644 | if (typeof(TKey) == typeof(string) && typeof(TValue) == typeof(string[])) 645 | return (IFileDictionaryDelegate)StringStringArrayFileDictionaryDelegate.Default; 646 | 647 | if (typeof(TKey) == typeof(string) && typeof(TValue) == typeof(int)) 648 | return (IFileDictionaryDelegate)StringIntFileDictionaryDelegate.Default; 649 | 650 | if (typeof(TKey) == typeof(string) && typeof(TValue) == typeof(bool)) 651 | return (IFileDictionaryDelegate)StringBoolFileDictionaryDelegate.Default; 652 | 653 | if (typeof(TKey) == typeof(int) && typeof(TValue) == typeof(byte[])) 654 | return (IFileDictionaryDelegate)IntBytesFileDictionaryDelegate.Default; 655 | 656 | if (typeof(TKey) == typeof(int) && typeof(TValue) == typeof(string)) 657 | return (IFileDictionaryDelegate)IntStringFileDictionaryDelegate.Default; 658 | 659 | if (typeof(TKey) == typeof(int) && typeof(TValue) == typeof(string[])) 660 | return (IFileDictionaryDelegate)IntStringArrayFileDictionaryDelegate.Default; 661 | 662 | if (typeof(TKey) == typeof(int) && typeof(TValue) == typeof(int)) 663 | return (IFileDictionaryDelegate)IntIntFileDictionaryDelegate.Default; 664 | 665 | if (typeof(TKey) == typeof(int) && typeof(TValue) == typeof(bool)) 666 | return (IFileDictionaryDelegate)IntBoolFileDictionaryDelegate.Default; 667 | 668 | return null; 669 | } 670 | } 671 | 672 | } -------------------------------------------------------------------------------- /CSharp/FileDictionaryDelegate.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9403db99121540a1b45e5a336a9a776b 3 | timeCreated: 1696938542 -------------------------------------------------------------------------------- /CSharp/SubFileStream.cs: -------------------------------------------------------------------------------- 1 | namespace ATRI.Collection 2 | { 3 | using System; 4 | using System.IO; 5 | 6 | public class SubFileStream : Stream 7 | { 8 | private Stream _baseStream; 9 | private long _start; 10 | private long _length; 11 | private long _position; 12 | 13 | public SubFileStream(Stream baseStream, long start, long length) 14 | { 15 | _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); 16 | _start = start; 17 | _length = length; 18 | _position = 0; 19 | 20 | if (!_baseStream.CanRead || !_baseStream.CanSeek) 21 | { 22 | throw new ArgumentException("The base stream must support reading and seeking.", nameof(baseStream)); 23 | } 24 | 25 | if (start < 0 || length < 0 || start + length > _baseStream.Length) 26 | { 27 | throw new ArgumentOutOfRangeException(nameof(start), 28 | "The specified range is outside the bounds of the base stream."); 29 | } 30 | } 31 | 32 | public override bool CanRead => _baseStream.CanRead; 33 | public override bool CanSeek => _baseStream.CanSeek; 34 | public override bool CanWrite => false; 35 | public override long Length => _length; 36 | 37 | public override long Position 38 | { 39 | get => _position; 40 | set 41 | { 42 | if (value < 0 || value > _length) 43 | { 44 | throw new ArgumentOutOfRangeException(nameof(value), 45 | "Position must be within the bounds of the sub stream."); 46 | } 47 | 48 | _position = value; 49 | } 50 | } 51 | 52 | public override void Flush() 53 | { 54 | throw new NotSupportedException("SubFileStream does not support writing."); 55 | } 56 | 57 | public override int Read(byte[] buffer, int offset, int count) 58 | { 59 | if (buffer == null) 60 | throw new ArgumentNullException(nameof(buffer)); 61 | if (offset < 0) 62 | throw new ArgumentOutOfRangeException(nameof(offset)); 63 | if (count < 0) 64 | throw new ArgumentOutOfRangeException(nameof(count)); 65 | if (buffer.Length - offset < count) 66 | throw new ArgumentException("Invalid offset and length."); 67 | 68 | if (_position >= _length) 69 | return 0; 70 | 71 | _baseStream.Position = _start + _position; 72 | int bytesRead = _baseStream.Read(buffer, offset, (int)Math.Min(count, _length - _position)); 73 | _position += bytesRead; 74 | return bytesRead; 75 | } 76 | 77 | public override long Seek(long offset, SeekOrigin origin) 78 | { 79 | long newPosition; 80 | switch (origin) 81 | { 82 | case SeekOrigin.Begin: 83 | newPosition = offset; 84 | break; 85 | case SeekOrigin.Current: 86 | newPosition = _position + offset; 87 | break; 88 | case SeekOrigin.End: 89 | newPosition = _length + offset; 90 | break; 91 | default: 92 | throw new ArgumentException("Invalid seek origin.", nameof(origin)); 93 | } 94 | 95 | if (newPosition < 0 || newPosition > _length) 96 | { 97 | throw new ArgumentOutOfRangeException(nameof(offset), 98 | "Seek position is outside the bounds of the sub stream."); 99 | } 100 | 101 | _position = newPosition; 102 | return _position; 103 | } 104 | 105 | public override void SetLength(long value) 106 | { 107 | throw new NotSupportedException("SubFileStream does not support setting length."); 108 | } 109 | 110 | public override void Write(byte[] buffer, int offset, int count) 111 | { 112 | throw new NotSupportedException("SubFileStream does not support writing."); 113 | } 114 | 115 | protected override void Dispose(bool disposing) 116 | { 117 | if (disposing) 118 | { 119 | _baseStream?.Dispose(); 120 | _baseStream = null; 121 | } 122 | 123 | base.Dispose(disposing); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /CSharp/SubFileStream.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5befb0ffd12b416c90aa31486de8f839 3 | timeCreated: 1716804618 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2024 leinlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Lua/FileDictionary.lua: -------------------------------------------------------------------------------- 1 | 2 | local function array_class() 3 | -- 构建类 4 | local clazz = {} 5 | local meta = { __index = clazz } 6 | -- new 方法创建类对象 7 | clazz.new = function(...) 8 | -- 构造一个对象 9 | local instance = {} 10 | -- 设置对象的元表为当前类,这样,对象就可以调用当前类生命的方法了 11 | setmetatable(instance, meta) 12 | if clazz.ctor then 13 | clazz.ctor(instance, ...) 14 | end 15 | return instance 16 | end 17 | return clazz 18 | end 19 | 20 | local lshift 21 | local rshift 22 | local bxor 23 | local band 24 | local bnot 25 | 26 | local wb = "w+b" 27 | local rb = "r+b" 28 | 29 | if jit then 30 | local bit32 = require("bit") 31 | lshift = bit32.lshift 32 | rshift = bit32.rshift 33 | bxor = bit32.bxor 34 | band = bit32.band 35 | bnot = bit32.bnot 36 | wb = "wb+" 37 | rb = "rb+" 38 | else 39 | -- lua 5.1写 移位计算 会报语法错误,所以直接用dostring绕过去 40 | lshift = load([[return function(a, b) return a << b end]])() 41 | rshift = load([[return function(a, b) return a >> b end]])() 42 | bxor = load([[return function(a, b) return a ~ b end]])() 43 | band = load([[return function(a, b) return a & b end]])() 44 | bnot = load([[return function(a) return ~a end]])() 45 | end 46 | local strByte = string.byte 47 | local strChar = string.char 48 | local strLen = string.len 49 | local type = type 50 | 51 | -- CRC32表 52 | local crc32_table = {} 53 | for i = 0, 255 do 54 | local crc = i 55 | for j = 1, 8 do 56 | if crc % 2 == 1 then 57 | crc = bxor(0xEDB88320, rshift(crc, 1)) 58 | else 59 | crc = rshift(crc, 1) 60 | end 61 | end 62 | crc32_table[i] = crc 63 | end 64 | 65 | -- 计算CRC32值 66 | local function crc32(str) 67 | local crc = 0xFFFFFFFF 68 | for i = 1, #str do 69 | local byte = strByte(str, i) 70 | crc = bxor(crc32_table[bxor(byte, band(crc, 0xFF))], rshift(crc, 8)) 71 | end 72 | local n = bnot(crc) 73 | 74 | if n < 0 then 75 | return 4294967296 + n -- 4294967296 = 2^32 76 | else 77 | return n -- 如果n是非负数,直接返回 78 | end 79 | end 80 | 81 | local function int32Hash(value) 82 | -- 使用位运算和一些常数来计算哈希码 83 | local hash = (value * 2654435761) % 4294967296 84 | return hash 85 | end 86 | 87 | local function calHash(value) 88 | local t = type(value) 89 | if t == "string" then 90 | return crc32(value) 91 | elseif t == "number" then 92 | return int32Hash(value) 93 | end 94 | end 95 | 96 | local function String2IntEx(str, index) 97 | if not index then 98 | index = 1 99 | end 100 | return strByte(str, index) + strByte(str, index + 1) * 0X100 101 | + strByte(str, index + 2) * 0X10000 + strByte(str, index + 3) * 0X1000000 102 | end 103 | 104 | local function String2Int(str) 105 | return strByte(str, 1) + strByte(str, 2) * 0X100 106 | + strByte(str, 3) * 0X10000 + strByte(str, 4) * 0X1000000 107 | end 108 | 109 | local function Int2String(num) 110 | local b1,b2,b3,b4 = 0 111 | b1 = num % 0X100 112 | num = (num - b1) / 0X100 113 | b2 = num % 0X100 114 | num = (num - b2) / 0X100 115 | b3 = num % 0X100 116 | num = (num - b3) / 0X100 117 | b4 = num % 0X100 118 | return strChar(b1, b2, b3, b4) 119 | end 120 | 121 | local SIZE_COUNT = 2 122 | local MAX_CONFLIGCT_TIME = 8 123 | local MOVE_BUFF_SIZE = 4096 124 | local INIT_BUFF = Int2String(0xFFFFFFFF) 125 | local DEFAULT_VALUE = 0xFFFFFFFF 126 | 127 | ---@class FileDictionary 128 | ---@field protected _capacity number 129 | ---@field protected _size number 130 | ---@field protected _dataOffset number 131 | 132 | ---@type FileDictionary 133 | local FileDictionary = array_class() 134 | 135 | local function FileExit(path) 136 | local fs = io.open(path, "rb") 137 | if fs then 138 | fs:close() 139 | end 140 | return fs 141 | end 142 | 143 | 144 | function FileDictionary:ctor(path, capacity, isClear) 145 | self:Open(path, capacity, isClear) 146 | end 147 | 148 | function FileDictionary:Open(path, capacity, isClear) 149 | local fileExit = FileExit(path) 150 | ---@type file 151 | local fs 152 | if capacity then 153 | if not fileExit or isClear then 154 | fs = io.open(path, wb) 155 | fileExit = false 156 | else 157 | fs = io.open(path, rb) 158 | end 159 | else 160 | fs = io.open(path, "rb") 161 | end 162 | 163 | self.isNumberKey = true 164 | if fileExit then 165 | self._fs = fs 166 | self._capacity = self:ReadInt() 167 | self._capacity_ = self._capacity - 1 168 | self._size = self:ReadInt() 169 | self._dataOffset = (self._capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4 170 | elseif capacity then 171 | self._fs = fs 172 | self._capacity = 0 173 | self._size = 0 174 | self._dataOffset = 0 175 | capacity = self:FindNextPowerOfTwo(capacity) 176 | self:Resize(capacity) 177 | end 178 | end 179 | 180 | function FileDictionary:SetKeyString() 181 | self.isNumberKey = false 182 | end 183 | 184 | ---@private 185 | ---@return number 186 | function FileDictionary:ReadInt() 187 | return String2Int(self._fs:read(4)) 188 | end 189 | 190 | ---@param val string 191 | function FileDictionary:WriteInt(val) 192 | self._fs:write(Int2String(val)) 193 | end 194 | 195 | local ReadInt = FileDictionary.ReadInt 196 | local WriteInt = FileDictionary.WriteInt 197 | 198 | ---@private 199 | ---@return boolean 200 | function FileDictionary:GetKey(offset) 201 | local fs = self._fs 202 | fs:seek("set", offset + self._dataOffset) 203 | local len = ReadInt(self) 204 | 205 | return fs:read(len) 206 | end 207 | 208 | local GetKey = FileDictionary.GetKey 209 | 210 | ---@private 211 | function FileDictionary:FindNextPowerOfTwo(number) 212 | if (number <= 0) then 213 | return 1 -- 最小的2幂是2^0 = 1 214 | end 215 | 216 | local result = 1 217 | while (result < number) do 218 | result = lshift(result, 1) -- 左移一位,相当于乘以2 219 | end 220 | 221 | return result 222 | end 223 | 224 | ---@private 225 | function FileDictionary:Resize(capacity) 226 | local _fs = self._fs 227 | local _dataOffset = self._dataOffset 228 | local _capacity = self._capacity 229 | 230 | _fs:seek("set", 0) 231 | -- 写入capacity 232 | WriteInt(self, capacity) 233 | WriteInt(self, self._size) 234 | 235 | if (_dataOffset ~= 0) then 236 | -- 移动数据区的数据 237 | local endIndex = _fs:seek("end"); 238 | local delta = (capacity - _capacity) * 4; 239 | local index = endIndex; 240 | 241 | while (index > _dataOffset) do 242 | local newIndex = index - MOVE_BUFF_SIZE; 243 | if (_dataOffset > newIndex) then 244 | newIndex = _dataOffset 245 | end 246 | local size = index - newIndex; 247 | 248 | _fs:seek("set", newIndex) 249 | local tmp = _fs:read(size) 250 | 251 | _fs:seek("set", newIndex + delta) 252 | _fs:write(tmp) 253 | 254 | index = newIndex 255 | end 256 | 257 | -- 把索引区的数据都读取到内存中 258 | 259 | _fs:seek("set", 4 * SIZE_COUNT) 260 | local oldData = _fs:read(4 * (_capacity + MAX_CONFLIGCT_TIME)) 261 | -- 开辟空间,并把索引区数据全部改成-1 262 | _fs:seek("set", 4 * SIZE_COUNT) 263 | for _ = 1,capacity + MAX_CONFLIGCT_TIME do 264 | _fs:write(INIT_BUFF) 265 | end 266 | 267 | self._dataOffset = (capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4 268 | self._capacity = capacity 269 | self._capacity_ = self._capacity - 1 270 | -- 重新插入 索引区数据 271 | 272 | for i = 0,_capacity - 1 do 273 | local offset = String2IntEx(oldData, i * 4 + 1) 274 | if (offset ~= DEFAULT_VALUE) then 275 | self:ResetVal(offset) 276 | end 277 | end 278 | else 279 | -- 开辟空间默认值为-1 280 | for _ = 1,capacity + MAX_CONFLIGCT_TIME do 281 | _fs:write(INIT_BUFF) 282 | end 283 | self._dataOffset = (capacity + SIZE_COUNT + MAX_CONFLIGCT_TIME) * 4; 284 | self._capacity = capacity 285 | self._capacity_ = self._capacity - 1 286 | self._size = 0; 287 | end 288 | 289 | _fs:flush() 290 | end 291 | 292 | ---@param offset number 293 | function FileDictionary:ResetVal(offset) 294 | if (offset == DEFAULT_VALUE) then 295 | return 296 | end 297 | 298 | -- 拿到原来的string值 299 | local key = GetKey(self, offset) 300 | self:DoSetVal(key, offset) 301 | end 302 | 303 | ---@private 304 | ---@param key string 305 | ---@param offset number 306 | function FileDictionary:DoSetVal(key, offset) 307 | -- 计算CRC32 并计算出来一个索引 308 | local index = band(calHash(key), self._capacity_) 309 | 310 | local isReset = false 311 | for i = 0,MAX_CONFLIGCT_TIME do 312 | if (self:SetVal(index + i, offset)) then 313 | isReset = true 314 | break 315 | end 316 | end 317 | 318 | if (not isReset) then 319 | self:Resize(self._capacity * 2); 320 | self:DoSetVal(key, offset); 321 | end 322 | end 323 | 324 | ---@private 325 | ---@param index number 326 | ---@param offset number 327 | function FileDictionary:SetVal(index, offset) 328 | local fs = self._fs 329 | fs:seek("set", (index + SIZE_COUNT) * 4) 330 | local v = ReadInt(self) 331 | if (v == DEFAULT_VALUE) then 332 | fs:seek("cur", -4) 333 | WriteInt(self, offset) 334 | return true 335 | end 336 | return false 337 | end 338 | 339 | ---region public 340 | 341 | function FileDictionary:GetSize() 342 | return self._size 343 | end 344 | 345 | ---@param key string 346 | function FileDictionary:TryGetValue(key) 347 | if not self._fs then 348 | error("file not exit") 349 | return false 350 | end 351 | 352 | if type(key) == "number" then 353 | key = Int2String(key) 354 | end 355 | 356 | local fs = self._fs 357 | local isFind = false 358 | local value = "" 359 | local offset = 0 360 | local index = calHash(key) % self._capacity 361 | 362 | for i = 0,MAX_CONFLIGCT_TIME do 363 | fs:seek("set", (index + i + SIZE_COUNT) * 4) 364 | offset = ReadInt(self) 365 | 366 | if (offset == DEFAULT_VALUE) then 367 | return false 368 | end 369 | local k = GetKey(self, offset) 370 | if (k == key) then 371 | local len = ReadInt(self) 372 | value = fs:read(len) 373 | isFind = true 374 | break 375 | end 376 | end 377 | 378 | return isFind,value,offset,index 379 | end 380 | 381 | ---@param value string 382 | ---@param key string 383 | function FileDictionary:SetValue(key, value) 384 | if not self._fs then 385 | error("file not exit") 386 | return false 387 | end 388 | 389 | -- 只支持int的key,不支持long的 390 | if type(key) == "number" then 391 | key = Int2String(key) 392 | end 393 | 394 | local hasValue,oldValue,offset,index = self:TryGetValue(key) 395 | 396 | local len = 0; 397 | if (hasValue) then 398 | if (oldValue == value) then return end 399 | end 400 | local fs = self._fs 401 | local _dataOffset = self._dataOffset 402 | 403 | if (hasValue and strLen(oldValue) >= strLen(value)) then 404 | local utf8Len = strLen(key) 405 | fs:seek("set", offset + _dataOffset + 4 + utf8Len); 406 | 407 | WriteInt(self, strLen(value)) 408 | fs:write(value) 409 | elseif (hasValue) then 410 | offset = fs:seek("end") - _dataOffset 411 | 412 | len = strLen(key) 413 | fs:seek("set", offset + _dataOffset) 414 | WriteInt(self, len) 415 | fs:write(key) 416 | 417 | len = strLen(value) 418 | WriteInt(self, len) 419 | fs:write(value) 420 | 421 | fs:seek("set", (index + SIZE_COUNT) * 4) 422 | WriteInt(self, offset) 423 | else 424 | self._size = self._size + 1 425 | fs:seek("set", 4) 426 | WriteInt(self, self._size) 427 | offset = fs:seek("end") - _dataOffset 428 | 429 | len = strLen(key) 430 | fs:seek("set", offset + _dataOffset) 431 | WriteInt(self, len) 432 | fs:write(key) 433 | 434 | len = strLen(value) 435 | WriteInt(self, len) 436 | fs:write(value) 437 | 438 | self:DoSetVal(key, offset); 439 | end 440 | end 441 | 442 | 443 | function FileDictionary:RemoveKey(key) 444 | if not self._fs then 445 | error("file not exit") 446 | return false 447 | end 448 | 449 | -- 只支持int的key,不支持long的 450 | if type(key) == "number" then 451 | key = Int2String(key) 452 | end 453 | local hasValue,oldValue,offset,index = self:TryGetValue(key) 454 | 455 | if (hasValue) then 456 | self._fs:seek("set", (index + SIZE_COUNT) * 4) 457 | WriteInt(self, DEFAULT_VALUE) 458 | self._size = self._size - 1 459 | self._fs:seek("set", 4) 460 | WriteInt(self, self._size) 461 | end 462 | end 463 | 464 | function FileDictionary:ToLuaTable() 465 | if not self._fs then 466 | error("file not exit") 467 | return false 468 | end 469 | 470 | local fs = self._fs 471 | local _capacity = self._capacity 472 | local _dataOffset = self._dataOffset 473 | 474 | local tb = {} 475 | for i = 0,_capacity - 1 do 476 | fs:seek("set", 4 * (SIZE_COUNT + i)) 477 | local offset = String2Int(fs:read(4)) 478 | if (offset ~= DEFAULT_VALUE) then 479 | fs:seek("set", offset + _dataOffset) 480 | local len = ReadInt(self) 481 | local key = fs:read(len) 482 | len = ReadInt(self) 483 | local value = fs:read(len) 484 | tb[key] = value 485 | end 486 | end 487 | 488 | return tb 489 | end 490 | 491 | function FileDictionary:ToLuaTable() 492 | if not self._fs then 493 | error("file not exit") 494 | return false 495 | end 496 | 497 | local fs = self._fs 498 | local _capacity = self._capacity 499 | local _dataOffset = self._dataOffset 500 | 501 | local tb = {} 502 | for i = 0,_capacity - 1 do 503 | fs:seek("set", 4 * (SIZE_COUNT + i)) 504 | local offset = String2Int(fs:read(4)) 505 | if (offset ~= DEFAULT_VALUE) then 506 | fs:seek("set", offset + _dataOffset) 507 | local len = ReadInt(self) 508 | local key = fs:read(len) 509 | len = ReadInt(self) 510 | local value = fs:read(len) 511 | tb[key] = value 512 | end 513 | end 514 | 515 | return tb 516 | end 517 | 518 | -- 注意这里面如果数据被修改 删除过 会报错 519 | function FileDictionary:ipairs() 520 | if not self._fs then 521 | error("file not exit") 522 | return false 523 | end 524 | 525 | local fs = self._fs 526 | local _dataOffset = self._dataOffset 527 | 528 | local i = 1 529 | local count = self._size 530 | local offset = _dataOffset 531 | -- 闭包函数 532 | return function () 533 | if i > count then 534 | return 535 | end 536 | fs:seek("set", offset) 537 | local len = ReadInt(self) 538 | local key = fs:read(len) 539 | if self.isNumberKey then 540 | key = String2Int(key) 541 | end 542 | offset = offset + len + 4 543 | len = ReadInt(self) 544 | local value = fs:read(len) 545 | offset = offset + len + 4 546 | i = i + 1 547 | 548 | return key,value 549 | end 550 | end 551 | 552 | function FileDictionary:Close() 553 | if self._fs then 554 | self._fs:close() 555 | end 556 | end 557 | 558 | 559 | ---endregion 560 | 561 | return FileDictionary 562 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://socialify.git.ci/leinlin/FileDictionary/image?description=1&descriptionEditable=A%20project%20for%20ATRI%2CCSharp%20Collection.&forks=1&issues=1&language=1&logo=https%3A%2F%2Fi.loli.net%2F2020%2F11%2F12%2FYcINCkyp8vK2inD.png&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light) 2 | 3 | ### 👋 Here is ATRI FileDictionary - 一个有厨力的字典配置系统 4 | 5 | ## ✨ 特性概览 | Features 6 | - 纯用算法实现的hash表,没有用到任何C#的字典功能便于修改优化性能 7 | - 完全开源,没有引用任何第三方库 8 | - 实现了几乎全部的C# 字典特性,使用起来非常方便 9 | - 只查询的的表,内存消耗几乎为0.效率在骁龙845机器的读取上为1ms,可以读取100个数据 10 | 11 | 12 | ### 🚀 使用范例 13 | 14 | 编辑器下创建字典的文件 15 | ``` 16 | var fileDictionary = new FileDictionary("test.bin", 1024, true); 17 | // 添加数据 18 | for (int i = 0; i <= 100; i++) 19 | { 20 | fileDictionary[i] = "test" + i.ToString(); 21 | } 22 | // 23 | fileDictionary.Close() 24 | ``` 25 | 26 | runtime下读取文件 27 | ``` 28 | var fileDictionary = new FileDictionary("test.bin", 1024); 29 | // 添加 30 | for (int i = 0; i <= 100; i++) 31 | { 32 | fileDictionary[i] = "bf1ecbbf0a2d38e3d5c7f7ab43524985"; 33 | } 34 | // 查询 35 | var value = fileDictionary[100]; 36 | 37 | // 字典拥有修改和删除,但是使用的时候需要注意性能 38 | 39 | // 修改 40 | fileDictionary[100] = "new str"; 41 | 42 | // 删除 43 | fileDictionary.Remove(100); 44 | 45 | // 遍历 46 | foreach (var item in fileDictionary) 47 | { 48 | // your code 49 | } 50 | 51 | // 转字典 52 | var dict = fileDictionary.ToDictionary(); 53 | ``` 54 | 55 | ### 多字典查询 56 | ``` 57 | FileDictionary f = new FileDictionary("test.bin", 1024, true); 58 | for (int i = 0; i < 10000; i++) 59 | { 60 | f.Add(i.ToString(), i % 2 == 0); 61 | } 62 | f.Dispose(); 63 | 64 | FileDictionary f2 = new FileDictionary("test2.bin", 1024, true); 65 | for (int i = 0; i < 10000; i++) 66 | { 67 | f2.Add(i.ToString(), i % 2 == 0); 68 | } 69 | f2.Dispose(); 70 | 71 | FileDictionary f3 = new FileDictionary("test3.bin", 1024, true); 72 | for (int i = 0; i < 10000; i++) 73 | { 74 | f3.Add(i, i.ToString()); 75 | } 76 | f3.Dispose(); 77 | 78 | FileDB fdb = new FileDB("file.fdb"); 79 | // 编辑器下直接合并为一个大DB 80 | fdb.MergeEditor("test.bin", "test2.bin", "test3.bin"); 81 | 82 | // 运行中合并FileDictionary 为DB,这样可以避免热更的时候更出去过大的文件 83 | fdb.MergeRuntime("test.bin", "test2.bin", "test3.bin"); 84 | 85 | bool v; 86 | if (fdb.TryGetValueByTableName("test", "0", out v)) 87 | { 88 | print("test success:" + v); 89 | } 90 | else 91 | { 92 | print("test fail"); 93 | } 94 | 95 | if (fdb.TryGetValueByTableName("test2", "3", out v)) 96 | { 97 | print("test2 success:" + v); 98 | } 99 | else 100 | { 101 | print("test2 fail"); 102 | } 103 | 104 | string strV; 105 | if (fdb.TryGetValueByTableName("test3", 100, out strV)) 106 | { 107 | print("test3 success:" + strV); 108 | } 109 | else 110 | { 111 | print("test3 fail"); 112 | } 113 | ``` 114 | 115 | 116 | ### 📱 目前库里面支持的类型有 117 | - 118 | - 119 | - 120 | - 121 | - 122 | - 123 | - 124 | - 125 | - 126 | - 127 | 128 | ### 📖 使用疑问 129 | - 添加新的字典类型:如果需要支持新的kv类型,可以 继承 IFileDictionaryDelegate接口实现对应的序列化、反序列化、hashcode。最后在在FileDictionaryDelegateFactory的GetFileDictionaryDelegate中添加对应的代码。 130 | - probuf或者其他的二进制存储推荐使用 使用的时候反序列化,当然如果嫌弃麻烦可以自己写一个将二进制序列化为class的IFileDictionaryDelegate接口类 131 | - 如果需要把配置表转到内存中,推荐使用ToDictionary,然后直接放到内存的字典中,之后将本字典释放掉即可 132 | - 如果不需要数据,建议直接Dipose掉FileDictionary 133 | --------------------------------------------------------------------------------