| kcdbext.h | | kcdbext.h | |
| | | | |
| skipping to change at line 52 | | skipping to change at line 52 | |
| class MapReduce { | | class MapReduce { | |
| public: | | public: | |
| class MapEmitter; | | class MapEmitter; | |
| class ValueIterator; | | class ValueIterator; | |
| private: | | private: | |
| class MapVisitor; | | class MapVisitor; | |
| struct MergeLine; | | struct MergeLine; | |
| /** An alias of vector of loaded values. */ | | /** An alias of vector of loaded values. */ | |
| typedef std::vector<std::string> Values; | | typedef std::vector<std::string> Values; | |
| /** The default number of temporary databases. */ | | /** The default number of temporary databases. */ | |
|
| static const size_t MRDEFDBNUM = 8; | | static const size_t DEFDBNUM = 8; | |
| /** The maxinum number of temporary databases. */ | | /** The maxinum number of temporary databases. */ | |
|
| static const size_t MRMAXDBNUM = 256; | | static const size_t MAXDBNUM = 256; | |
| /** The default cache limit. */ | | /** The default cache limit. */ | |
|
| static const int64_t MRDEFCLIM = 512LL << 20; | | static const int64_t DEFCLIM = 512LL << 20; | |
| /** The default cache bucket numer. */ | | /** The default cache bucket numer. */ | |
|
| static const int64_t MRDEFCBNUM = 1048583LL; | | static const int64_t DEFCBNUM = 1048583LL; | |
| /** The bucket number of temprary databases. */ | | /** The bucket number of temprary databases. */ | |
|
| static const int64_t MRDBBNUM = 512LL << 10; | | static const int64_t DBBNUM = 512LL << 10; | |
| /** The page size of temprary databases. */ | | /** The page size of temprary databases. */ | |
|
| static const int32_t MRDBPSIZ = 32768; | | static const int32_t DBPSIZ = 32768; | |
| /** The mapped size of temprary databases. */ | | /** The mapped size of temprary databases. */ | |
|
| static const int64_t MRDBMSIZ = 516LL * 4096; | | static const int64_t DBMSIZ = 516LL * 4096; | |
| /** The page cache capacity of temprary databases. */ | | /** The page cache capacity of temprary databases. */ | |
|
| static const int64_t MRDBPCCAP = 16LL << 20; | | static const int64_t DBPCCAP = 16LL << 20; | |
| public: | | public: | |
| /** | | /** | |
| * Data emitter for the mapper. | | * Data emitter for the mapper. | |
| */ | | */ | |
| class MapEmitter { | | class MapEmitter { | |
| friend class MapReduce; | | friend class MapReduce; | |
| friend class MapReduce::MapVisitor; | | friend class MapReduce::MapVisitor; | |
| public: | | public: | |
| /** | | /** | |
| * Emit a record from the mapper. | | * Emit a record from the mapper. | |
| | | | |
| skipping to change at line 186 | | skipping to change at line 186 | |
| * Execution options. | | * Execution options. | |
| */ | | */ | |
| enum Option { | | enum Option { | |
| XNOLOCK = 1 << 0, ///< avoid locking against update
operations | | XNOLOCK = 1 << 0, ///< avoid locking against update
operations | |
| XNOCOMP = 1 << 1 ///< avoid compression of temporar
y databases | | XNOCOMP = 1 << 1 ///< avoid compression of temporar
y databases | |
| }; | | }; | |
| /** | | /** | |
| * Default constructor. | | * Default constructor. | |
| */ | | */ | |
| explicit MapReduce() : | | explicit MapReduce() : | |
|
| rcomp_(NULL), tmpdbs_(NULL), dbnum_(MRDEFDBNUM), dbclock_(0), keycloc | | db_(NULL), rcomp_(NULL), tmpdbs_(NULL), dbnum_(DEFDBNUM), dbclock_(0) | |
| k_(0), | | , | |
| cache_(NULL), csiz_(0), clim_(MRDEFCLIM), cbnum_(MRDEFCBNUM) { | | cache_(NULL), csiz_(0), clim_(DEFCLIM), cbnum_(DEFCBNUM) { | |
| _assert_(true); | | _assert_(true); | |
| } | | } | |
| /** | | /** | |
| * Destructor. | | * Destructor. | |
| */ | | */ | |
| virtual ~MapReduce() { | | virtual ~MapReduce() { | |
| _assert_(true); | | _assert_(true); | |
| } | | } | |
| /** | | /** | |
| * Map a record data. | | * Map a record data. | |
| | | | |
| skipping to change at line 268 | | skipping to change at line 268 | |
| * @param opts the optional features by bitwise-or: MapReduce::XNOLOCK to
avoid locking | | * @param opts the optional features by bitwise-or: MapReduce::XNOLOCK to
avoid locking | |
| * against update operations by other threads, MapReduce::XNOCOMP to avoi
d compression of | | * against update operations by other threads, MapReduce::XNOCOMP to avoi
d compression of | |
| * temporary databases. | | * temporary databases. | |
| * @return true on success, or false on failure. | | * @return true on success, or false on failure. | |
| */ | | */ | |
| bool execute(BasicDB* db, const std::string& tmppath = "", uint32_t opts
= 0) { | | bool execute(BasicDB* db, const std::string& tmppath = "", uint32_t opts
= 0) { | |
| int64_t count = db->count(); | | int64_t count = db->count(); | |
| if (count < 0) return false; | | if (count < 0) return false; | |
| bool err = false; | | bool err = false; | |
| double stime, etime; | | double stime, etime; | |
|
| | | db_ = db; | |
| rcomp_ = LEXICALCOMP; | | rcomp_ = LEXICALCOMP; | |
| BasicDB* idb = db; | | BasicDB* idb = db; | |
| if (typeid(*db) == typeid(PolyDB)) { | | if (typeid(*db) == typeid(PolyDB)) { | |
| PolyDB* pdb = (PolyDB*)idb; | | PolyDB* pdb = (PolyDB*)idb; | |
| idb = pdb->reveal_inner_db(); | | idb = pdb->reveal_inner_db(); | |
| } | | } | |
| const std::type_info& info = typeid(*idb); | | const std::type_info& info = typeid(*idb); | |
| if (info == typeid(GrassDB)) { | | if (info == typeid(GrassDB)) { | |
| GrassDB* gdb = (GrassDB*)idb; | | GrassDB* gdb = (GrassDB*)idb; | |
| rcomp_ = gdb->rcomp(); | | rcomp_ = gdb->rcomp(); | |
| | | | |
| skipping to change at line 294 | | skipping to change at line 295 | |
| } | | } | |
| tmpdbs_ = new BasicDB*[dbnum_]; | | tmpdbs_ = new BasicDB*[dbnum_]; | |
| if (tmppath.empty()) { | | if (tmppath.empty()) { | |
| if (!logf("prepare", "started to open temporary databases on memory")
) err = true; | | if (!logf("prepare", "started to open temporary databases on memory")
) err = true; | |
| stime = time(); | | stime = time(); | |
| for (size_t i = 0; i < dbnum_; i++) { | | for (size_t i = 0; i < dbnum_; i++) { | |
| GrassDB* gdb = new GrassDB; | | GrassDB* gdb = new GrassDB; | |
| int32_t myopts = 0; | | int32_t myopts = 0; | |
| if (!(opts & XNOCOMP)) myopts |= GrassDB::TCOMPRESS; | | if (!(opts & XNOCOMP)) myopts |= GrassDB::TCOMPRESS; | |
| gdb->tune_options(myopts); | | gdb->tune_options(myopts); | |
|
| gdb->tune_buckets(MRDBBNUM / 2); | | gdb->tune_buckets(DBBNUM / 2); | |
| gdb->tune_page(MRDBPSIZ); | | gdb->tune_page(DBPSIZ); | |
| gdb->tune_page_cache(MRDBPCCAP); | | gdb->tune_page_cache(DBPCCAP); | |
| gdb->tune_comparator(rcomp_); | | gdb->tune_comparator(rcomp_); | |
| gdb->open("%", GrassDB::OWRITER | GrassDB::OCREATE | GrassDB::OTRUN
CATE); | | gdb->open("%", GrassDB::OWRITER | GrassDB::OCREATE | GrassDB::OTRUN
CATE); | |
| tmpdbs_[i] = gdb; | | tmpdbs_[i] = gdb; | |
| } | | } | |
| etime = time(); | | etime = time(); | |
| if (!logf("prepare", "opening temporary databases finished: time=%.6f
", etime - stime)) | | if (!logf("prepare", "opening temporary databases finished: time=%.6f
", etime - stime)) | |
| err = true; | | err = true; | |
| if (err) { | | if (err) { | |
| delete[] tmpdbs_; | | delete[] tmpdbs_; | |
| return false; | | return false; | |
| | | | |
| skipping to change at line 329 | | skipping to change at line 330 | |
| uint32_t tid = Thread::hash() & UINT16MAX; | | uint32_t tid = Thread::hash() & UINT16MAX; | |
| uint32_t ts = time() * 1000; | | uint32_t ts = time() * 1000; | |
| for (size_t i = 0; i < dbnum_; i++) { | | for (size_t i = 0; i < dbnum_; i++) { | |
| std::string childpath = | | std::string childpath = | |
| strprintf("%s%cmr-%04x-%04x-%08x-%03d%ckct", | | strprintf("%s%cmr-%04x-%04x-%08x-%03d%ckct", | |
| tmppath.c_str(), File::PATHCHR, pid, tid, ts, (int)(i
+ 1), File::EXTCHR); | | tmppath.c_str(), File::PATHCHR, pid, tid, ts, (int)(i
+ 1), File::EXTCHR); | |
| TreeDB* tdb = new TreeDB; | | TreeDB* tdb = new TreeDB; | |
| int32_t myopts = TreeDB::TSMALL | TreeDB::TLINEAR; | | int32_t myopts = TreeDB::TSMALL | TreeDB::TLINEAR; | |
| if (!(opts & XNOCOMP)) myopts |= TreeDB::TCOMPRESS; | | if (!(opts & XNOCOMP)) myopts |= TreeDB::TCOMPRESS; | |
| tdb->tune_options(myopts); | | tdb->tune_options(myopts); | |
|
| tdb->tune_buckets(MRDBBNUM); | | tdb->tune_buckets(DBBNUM); | |
| tdb->tune_page(MRDBPSIZ); | | tdb->tune_page(DBPSIZ); | |
| tdb->tune_map(MRDBMSIZ); | | tdb->tune_map(DBMSIZ); | |
| tdb->tune_page_cache(MRDBPCCAP); | | tdb->tune_page_cache(DBPCCAP); | |
| tdb->tune_comparator(rcomp_); | | tdb->tune_comparator(rcomp_); | |
| if (!tdb->open(childpath, TreeDB::OWRITER | TreeDB::OCREATE | TreeD
B::OTRUNCATE)) { | | if (!tdb->open(childpath, TreeDB::OWRITER | TreeDB::OCREATE | TreeD
B::OTRUNCATE)) { | |
| const BasicDB::Error& e = tdb->error(); | | const BasicDB::Error& e = tdb->error(); | |
| db->set_error(_KCCODELINE_, e.code(), e.message()); | | db->set_error(_KCCODELINE_, e.code(), e.message()); | |
| err = true; | | err = true; | |
| } | | } | |
| tmpdbs_[i] = tdb; | | tmpdbs_[i] = tdb; | |
| } | | } | |
| etime = time(); | | etime = time(); | |
| if (!logf("prepare", "opening temporary databases finished: time=%.6f
", etime - stime)) | | if (!logf("prepare", "opening temporary databases finished: time=%.6f
", etime - stime)) | |
| | | | |
| skipping to change at line 379 | | skipping to change at line 380 | |
| } else { | | } else { | |
| MapChecker mapchecker; | | MapChecker mapchecker; | |
| MapVisitor mapvisitor(this, &mapchecker, db->count()); | | MapVisitor mapvisitor(this, &mapchecker, db->count()); | |
| if (!err && !db->iterate(&mapvisitor, false, &mapchecker)) err = true
; | | if (!err && !db->iterate(&mapvisitor, false, &mapchecker)) err = true
; | |
| if (mapvisitor.error()) err = true; | | if (mapvisitor.error()) err = true; | |
| } | | } | |
| if (!logf("clean", "closing the temporary databases")) err = true; | | if (!logf("clean", "closing the temporary databases")) err = true; | |
| stime = time(); | | stime = time(); | |
| for (size_t i = 0; i < dbnum_; i++) { | | for (size_t i = 0; i < dbnum_; i++) { | |
| assert(tmpdbs_[i]); | | assert(tmpdbs_[i]); | |
|
| std::string path = tmpdbs_[i]->path(); | | const std::string& path = tmpdbs_[i]->path(); | |
| if (!tmpdbs_[i]->clear()) { | | if (!tmpdbs_[i]->clear()) { | |
| const BasicDB::Error& e = tmpdbs_[i]->error(); | | const BasicDB::Error& e = tmpdbs_[i]->error(); | |
| db->set_error(_KCCODELINE_, e.code(), e.message()); | | db->set_error(_KCCODELINE_, e.code(), e.message()); | |
| err = true; | | err = true; | |
| } | | } | |
| if (!tmpdbs_[i]->close()) { | | if (!tmpdbs_[i]->close()) { | |
| const BasicDB::Error& e = tmpdbs_[i]->error(); | | const BasicDB::Error& e = tmpdbs_[i]->error(); | |
| db->set_error(_KCCODELINE_, e.code(), e.message()); | | db->set_error(_KCCODELINE_, e.code(), e.message()); | |
| err = true; | | err = true; | |
| } | | } | |
| | | | |
| skipping to change at line 407 | | skipping to change at line 408 | |
| return !err; | | return !err; | |
| } | | } | |
| /** | | /** | |
| * Set the storage configurations. | | * Set the storage configurations. | |
| * @param dbnum the number of temporary databases. | | * @param dbnum the number of temporary databases. | |
| * @param clim the limit size of the internal cache. | | * @param clim the limit size of the internal cache. | |
| * @param cbnum the bucket number of the internal cache. | | * @param cbnum the bucket number of the internal cache. | |
| */ | | */ | |
| void tune_storage(int32_t dbnum, int64_t clim, int64_t cbnum) { | | void tune_storage(int32_t dbnum, int64_t clim, int64_t cbnum) { | |
| _assert_(true); | | _assert_(true); | |
|
| dbnum_ = dbnum > 0 ? dbnum : MRDEFDBNUM; | | dbnum_ = dbnum > 0 ? dbnum : DEFDBNUM; | |
| if (dbnum_ > MRMAXDBNUM) dbnum_ = MRMAXDBNUM; | | if (dbnum_ > MAXDBNUM) dbnum_ = MAXDBNUM; | |
| clim_ = clim > 0 ? clim : MRDEFCLIM; | | clim_ = clim > 0 ? clim : DEFCLIM; | |
| cbnum_ = cbnum > 0 ? cbnum : MRDEFCBNUM; | | cbnum_ = cbnum > 0 ? cbnum : DEFCBNUM; | |
| if (cbnum_ > INT16MAX) cbnum_ = nearbyprime(cbnum_); | | if (cbnum_ > INT16MAX) cbnum_ = nearbyprime(cbnum_); | |
| } | | } | |
| private: | | private: | |
| /** | | /** | |
| * Checker for the map process. | | * Checker for the map process. | |
| */ | | */ | |
| class MapChecker : public BasicDB::ProgressChecker { | | class MapChecker : public BasicDB::ProgressChecker { | |
| public: | | public: | |
| /** constructor */ | | /** constructor */ | |
| explicit MapChecker() : stop_(false) {} | | explicit MapChecker() : stop_(false) {} | |
| | | | |
| skipping to change at line 454 | | skipping to change at line 455 | |
| stime_(0), err_(false) {} | | stime_(0), err_(false) {} | |
| /** get the error flag */ | | /** get the error flag */ | |
| bool error() { | | bool error() { | |
| return err_; | | return err_; | |
| } | | } | |
| /** preprocess the mappter */ | | /** preprocess the mappter */ | |
| void visit_before() { | | void visit_before() { | |
| if (!mr_->preprocess()) err_ = true; | | if (!mr_->preprocess()) err_ = true; | |
| stime_ = time(); | | stime_ = time(); | |
| mr_->dbclock_ = 0; | | mr_->dbclock_ = 0; | |
|
| mr_->keyclock_ = 0; | | | |
| mr_->cache_ = new TinyHashMap(mr_->cbnum_); | | mr_->cache_ = new TinyHashMap(mr_->cbnum_); | |
| mr_->csiz_ = 0; | | mr_->csiz_ = 0; | |
| if (!mr_->logf("map", "started the map process: scale=%lld", (long lo
ng)scale_)) | | if (!mr_->logf("map", "started the map process: scale=%lld", (long lo
ng)scale_)) | |
| err_ = true; | | err_ = true; | |
| } | | } | |
| /** postprocess the mappter and call the reducer */ | | /** postprocess the mappter and call the reducer */ | |
| void visit_after() { | | void visit_after() { | |
| if (mr_->cache_->count() > 0 && !mr_->flush_cache()) err_ = true; | | if (mr_->cache_->count() > 0 && !mr_->flush_cache()) err_ = true; | |
| delete mr_->cache_; | | delete mr_->cache_; | |
|
| if (!mr_->midprocess()) err_ = true; | | | |
| double etime = time(); | | double etime = time(); | |
| if (!mr_->logf("map", "the map process finished: time=%.6f", etime -
stime_)) | | if (!mr_->logf("map", "the map process finished: time=%.6f", etime -
stime_)) | |
| err_ = true; | | err_ = true; | |
|
| | | if (!mr_->midprocess()) err_ = true; | |
| if (!err_ && !mr_->execute_reduce()) err_ = true; | | if (!err_ && !mr_->execute_reduce()) err_ = true; | |
| if (!mr_->postprocess()) err_ = true; | | if (!mr_->postprocess()) err_ = true; | |
| } | | } | |
| private: | | private: | |
| /** visit a record */ | | /** visit a record */ | |
| const char* visit_full(const char* kbuf, size_t ksiz, | | const char* visit_full(const char* kbuf, size_t ksiz, | |
| const char* vbuf, size_t vsiz, size_t* sp) { | | const char* vbuf, size_t vsiz, size_t* sp) { | |
| if (!mr_->map(kbuf, ksiz, vbuf, vsiz, &emitter_)) { | | if (!mr_->map(kbuf, ksiz, vbuf, vsiz, &emitter_)) { | |
| checker_->stop(); | | checker_->stop(); | |
| err_ = true; | | err_ = true; | |
| | | | |
| skipping to change at line 533 | | skipping to change at line 533 | |
| /** | | /** | |
| * Flush all cache records. | | * Flush all cache records. | |
| * @return true on success, or false on failure. | | * @return true on success, or false on failure. | |
| */ | | */ | |
| bool flush_cache() { | | bool flush_cache() { | |
| _assert_(true); | | _assert_(true); | |
| bool err = false; | | bool err = false; | |
| if (!logf("map", "started to flushing the cache: count=%lld size=%lld", | | if (!logf("map", "started to flushing the cache: count=%lld size=%lld", | |
| (long long)cache_->count(), (long long)csiz_)) err = true; | | (long long)cache_->count(), (long long)csiz_)) err = true; | |
| double stime = time(); | | double stime = time(); | |
|
| BasicDB* db = tmpdbs_[dbclock_]; | | BasicDB* tmpdb = tmpdbs_[dbclock_]; | |
| TinyHashMap::Sorter sorter(cache_); | | TinyHashMap::Sorter sorter(cache_); | |
| const char* kbuf, *vbuf; | | const char* kbuf, *vbuf; | |
| size_t ksiz, vsiz; | | size_t ksiz, vsiz; | |
| while ((kbuf = sorter.get(&ksiz, &vbuf, &vsiz)) != NULL) { | | while ((kbuf = sorter.get(&ksiz, &vbuf, &vsiz)) != NULL) { | |
|
| if (!db->append(kbuf, ksiz, vbuf, vsiz)) err = true; | | if (!tmpdb->append(kbuf, ksiz, vbuf, vsiz)) { | |
| | | const BasicDB::Error& e = tmpdb->error(); | |
| | | db_->set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| sorter.step(); | | sorter.step(); | |
| } | | } | |
| cache_->clear(); | | cache_->clear(); | |
| csiz_ = 0; | | csiz_ = 0; | |
| dbclock_ = (dbclock_ + 1) % dbnum_; | | dbclock_ = (dbclock_ + 1) % dbnum_; | |
| double etime = time(); | | double etime = time(); | |
| if (!logf("map", "flushing the cache finished: time=%.6f", etime - stim
e)) err = true; | | if (!logf("map", "flushing the cache finished: time=%.6f", etime - stim
e)) err = true; | |
| return !err; | | return !err; | |
| } | | } | |
| /** | | /** | |
| | | | |
| skipping to change at line 627 | | skipping to change at line 631 | |
| _assert_(kbuf && ksiz <= MEMMAXSIZ); | | _assert_(kbuf && ksiz <= MEMMAXSIZ); | |
| bool err = false; | | bool err = false; | |
| ValueIterator iter(values.begin(), values.end()); | | ValueIterator iter(values.begin(), values.end()); | |
| if (!reduce(kbuf, ksiz, &iter)) err = true; | | if (!reduce(kbuf, ksiz, &iter)) err = true; | |
| return !err; | | return !err; | |
| } | | } | |
| /** Dummy constructor to forbid the use. */ | | /** Dummy constructor to forbid the use. */ | |
| MapReduce(const MapReduce&); | | MapReduce(const MapReduce&); | |
| /** Dummy Operator to forbid the use. */ | | /** Dummy Operator to forbid the use. */ | |
| MapReduce& operator =(const MapReduce&); | | MapReduce& operator =(const MapReduce&); | |
|
| | | /** The internal database. */ | |
| | | BasicDB* db_; | |
| /** The record comparator. */ | | /** The record comparator. */ | |
| Comparator* rcomp_; | | Comparator* rcomp_; | |
| /** The temporary databases. */ | | /** The temporary databases. */ | |
| BasicDB** tmpdbs_; | | BasicDB** tmpdbs_; | |
| /** The number of temporary databases. */ | | /** The number of temporary databases. */ | |
| size_t dbnum_; | | size_t dbnum_; | |
| /** The logical clock for temporary databases. */ | | /** The logical clock for temporary databases. */ | |
| int64_t dbclock_; | | int64_t dbclock_; | |
|
| /** The logical clock for keys. */ | | | |
| int64_t keyclock_; | | | |
| /** The cache for emitter. */ | | /** The cache for emitter. */ | |
| TinyHashMap* cache_; | | TinyHashMap* cache_; | |
| /** The current size of the cache for emitter. */ | | /** The current size of the cache for emitter. */ | |
| int64_t csiz_; | | int64_t csiz_; | |
| /** The limit size of the cache for emitter. */ | | /** The limit size of the cache for emitter. */ | |
| int64_t clim_; | | int64_t clim_; | |
| /** The bucket number of the cache for emitter. */ | | /** The bucket number of the cache for emitter. */ | |
| int64_t cbnum_; | | int64_t cbnum_; | |
| }; | | }; | |
| | | | |
|
| | | /** | |
| | | * Index database. | |
| | | * @note This class is designed to implement an indexing storage with an ef | |
| | | ficient appending | |
| | | * operation for the existing record values. This class is a wrapper of th | |
| | | e polymorphic | |
| | | * database, featuring buffering mechanism to alleviate IO overhead in the | |
| | | database layer. This | |
| | | * class can be inherited but overwriting methods is forbidden. Before eve | |
| | | ry database operation, | |
| | | * it is necessary to call the IndexDB::open method in order to open a data | |
| | | base file and connect | |
| | | * the database object to it. To avoid data missing or corruption, it is i | |
| | | mportant to close | |
| | | * every database file by the IndexDB::close method when the database is no | |
| | | longer in use. It | |
| | | * is forbidden for multible database objects in a process to open the same | |
| | | database at the same | |
| | | * time. It is forbidden to share a database object with child processes. | |
| | | */ | |
| | | class IndexDB { | |
| | | private: | |
| | | /** The default number of temporary databases. */ | |
| | | static const size_t DEFDBNUM = 8; | |
| | | /** The maxinum number of temporary databases. */ | |
| | | static const size_t MAXDBNUM = 256; | |
| | | /** The default cache limit size. */ | |
| | | static const int64_t DEFCLIM = 256LL << 20; | |
| | | /** The default cache bucket number. */ | |
| | | static const int64_t DEFCBNUM = 1048583LL; | |
| | | /** The bucket number of temprary databases. */ | |
| | | static const int64_t DBBNUM = 512LL << 10; | |
| | | /** The page size of temprary databases. */ | |
| | | static const int32_t DBPSIZ = 32768; | |
| | | /** The mapped size of temprary databases. */ | |
| | | static const int64_t DBMSIZ = 516LL * 4096; | |
| | | /** The page cache capacity of temprary databases. */ | |
| | | static const int64_t DBPCCAP = 16LL << 20; | |
| | | public: | |
| | | /** | |
| | | * Default constructor. | |
| | | */ | |
| | | explicit IndexDB() : | |
| | | mlock_(), db_(), omode_(0), | |
| | | rcomp_(NULL), tmppath_(""), tmpdbs_(NULL), dbnum_(DEFDBNUM), dbclock_ | |
| | | (0), | |
| | | cache_(NULL), csiz_(0), clim_(0) { | |
| | | _assert_(true); | |
| | | } | |
| | | /** | |
| | | * Destructor. | |
| | | * @note If the database is not closed, it is closed implicitly. | |
| | | */ | |
| | | virtual ~IndexDB() { | |
| | | _assert_(true); | |
| | | if (omode_ != 0) close(); | |
| | | } | |
| | | /** | |
| | | * Get the last happened error. | |
| | | * @return the last happened error. | |
| | | */ | |
| | | BasicDB::Error error() const { | |
| | | _assert_(true); | |
| | | return db_.error(); | |
| | | } | |
| | | /** | |
| | | * Set the error information. | |
| | | * @param file the file name of the program source code. | |
| | | * @param line the line number of the program source code. | |
| | | * @param func the function name of the program source code. | |
| | | * @param code an error code. | |
| | | * @param message a supplement message. | |
| | | */ | |
| | | void set_error(const char* file, int32_t line, const char* func, | |
| | | BasicDB::Error::Code code, const char* message) { | |
| | | _assert_(file && line > 0 && func && message); | |
| | | db_.set_error(file, line, func, code, message); | |
| | | } | |
| | | /** | |
| | | * Set the error information without source code information. | |
| | | * @param code an error code. | |
| | | * @param message a supplement message. | |
| | | */ | |
| | | void set_error(BasicDB::Error::Code code, const char* message) { | |
| | | _assert_(message); | |
| | | db_.set_error(_KCCODELINE_, code, message); | |
| | | } | |
| | | /** | |
| | | * Open a database file. | |
| | | * @param path the path of a database file. The same as with PolyDB. In | |
| | | addition, the | |
| | | * following tuning parameters are supported. "idxclim" specifies the li | |
| | | mit size of the | |
| | | * internal cache. "idxcbnum" the bucket number of the internal cache. | |
| | | "idxdbnum" specifies | |
| | | * the number of internal databases. "idxtmppath' specifies the path of | |
| | | the temporary | |
| | | * directory. | |
| | | * @param mode the connection mode. The same as with PolyDB. | |
| | | * @return true on success, or false on failure. | |
| | | * @note Every opened database must be closed by the IndexDB::close metho | |
| | | d when it is no longer | |
| | | * in use. It is not allowed for two or more database objects in the sam | |
| | | e process to keep | |
| | | * their connections to the same database file at the same time. | |
| | | */ | |
| | | bool open(const std::string& path = ":", | |
| | | uint32_t mode = BasicDB::OWRITER | BasicDB::OCREATE) { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ != 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "already opened"); | |
| | | return false; | |
| | | } | |
| | | std::vector<std::string> elems; | |
| | | strsplit(path, '#', &elems); | |
| | | int64_t clim = 0; | |
| | | int64_t cbnum = 0; | |
| | | size_t dbnum = 0; | |
| | | std::string tmppath = ""; | |
| | | std::vector<std::string>::iterator it = elems.begin(); | |
| | | std::vector<std::string>::iterator itend = elems.end(); | |
| | | if (it != itend) ++it; | |
| | | while (it != itend) { | |
| | | std::vector<std::string> fields; | |
| | | if (strsplit(*it, '=', &fields) > 1) { | |
| | | const char* key = fields[0].c_str(); | |
| | | const char* value = fields[1].c_str(); | |
| | | if (!std::strcmp(key, "idxclim") || !std::strcmp(key, "idxcachelimi | |
| | | t")) { | |
| | | clim = atoix(value); | |
| | | } else if (!std::strcmp(key, "idxcbnum") || !std::strcmp(key, "idxc | |
| | | achebuckets")) { | |
| | | cbnum = atoix(value); | |
| | | } else if (!std::strcmp(key, "idxdbnum")) { | |
| | | dbnum = atoix(value); | |
| | | } else if (!std::strcmp(key, "idxtmppath")) { | |
| | | tmppath = value; | |
| | | } | |
| | | } | |
| | | ++it; | |
| | | } | |
| | | if (!db_.open(path, mode)) return false; | |
| | | tmppath_ = tmppath; | |
| | | rcomp_ = LEXICALCOMP; | |
| | | BasicDB* idb = &db_; | |
| | | if (typeid(db_) == typeid(PolyDB)) { | |
| | | PolyDB* pdb = (PolyDB*)idb; | |
| | | idb = pdb->reveal_inner_db(); | |
| | | } | |
| | | const std::type_info& info = typeid(*idb); | |
| | | if (info == typeid(GrassDB)) { | |
| | | GrassDB* gdb = (GrassDB*)idb; | |
| | | rcomp_ = gdb->rcomp(); | |
| | | } else if (info == typeid(TreeDB)) { | |
| | | TreeDB* tdb = (TreeDB*)idb; | |
| | | rcomp_ = tdb->rcomp(); | |
| | | } else if (info == typeid(ForestDB)) { | |
| | | ForestDB* fdb = (ForestDB*)idb; | |
| | | rcomp_ = fdb->rcomp(); | |
| | | } | |
| | | dbnum_ = dbnum < MAXDBNUM ? dbnum : MAXDBNUM; | |
| | | dbclock_ = 0; | |
| | | if ((mode & BasicDB::OWRITER) && dbnum > 0) { | |
| | | tmpdbs_ = new BasicDB*[dbnum_]; | |
| | | if (tmppath_.empty()) { | |
| | | report(_KCCODELINE_, "started to open temporary databases on memory | |
| | | "); | |
| | | double stime = time(); | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | GrassDB* gdb = new GrassDB; | |
| | | gdb->tune_options(GrassDB::TCOMPRESS); | |
| | | gdb->tune_buckets(DBBNUM / 2); | |
| | | gdb->tune_page(DBPSIZ); | |
| | | gdb->tune_page_cache(DBPCCAP); | |
| | | gdb->tune_comparator(rcomp_); | |
| | | gdb->open("%", GrassDB::OWRITER | GrassDB::OCREATE | GrassDB::OTR | |
| | | UNCATE); | |
| | | tmpdbs_[i] = gdb; | |
| | | } | |
| | | double etime = time(); | |
| | | report(_KCCODELINE_, "opening temporary databases finished: time=%. | |
| | | 6f", etime - stime); | |
| | | } else { | |
| | | File::Status sbuf; | |
| | | if (!File::status(tmppath_, &sbuf) || !sbuf.isdir) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::NOREPOS, "no such directo | |
| | | ry"); | |
| | | delete[] tmpdbs_; | |
| | | db_.close(); | |
| | | return false; | |
| | | } | |
| | | report(_KCCODELINE_, "started to open temporary databases under %s" | |
| | | , tmppath.c_str()); | |
| | | double stime = time(); | |
| | | uint32_t pid = getpid() & UINT16MAX; | |
| | | uint32_t tid = Thread::hash() & UINT16MAX; | |
| | | uint32_t ts = time() * 1000; | |
| | | bool err = false; | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | std::string childpath = | |
| | | strprintf("%s%cidx-%04x-%04x-%08x-%03d%ckct", | |
| | | tmppath_.c_str(), File::PATHCHR, pid, tid, ts, | |
| | | (int)(i + 1), File::EXTCHR); | |
| | | TreeDB* tdb = new TreeDB; | |
| | | tdb->tune_options(TreeDB::TSMALL | TreeDB::TLINEAR); | |
| | | tdb->tune_buckets(DBBNUM); | |
| | | tdb->tune_page(DBPSIZ); | |
| | | tdb->tune_map(DBMSIZ); | |
| | | tdb->tune_page_cache(DBPCCAP); | |
| | | tdb->tune_comparator(rcomp_); | |
| | | if (!tdb->open(childpath, TreeDB::OWRITER | TreeDB::OCREATE | Tre | |
| | | eDB::OTRUNCATE)) { | |
| | | const BasicDB::Error& e = tdb->error(); | |
| | | set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| | | tmpdbs_[i] = tdb; | |
| | | } | |
| | | double etime = time(); | |
| | | report(_KCCODELINE_, "opening temporary databases finished: time=%. | |
| | | 6f", etime - stime); | |
| | | if (err) { | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | delete tmpdbs_[i]; | |
| | | } | |
| | | delete[] tmpdbs_; | |
| | | db_.close(); | |
| | | return false; | |
| | | } | |
| | | } | |
| | | } else { | |
| | | tmpdbs_ = NULL; | |
| | | } | |
| | | if (mode & BasicDB::OWRITER) { | |
| | | cache_ = new TinyHashMap(cbnum > 0 ? cbnum : DEFCBNUM); | |
| | | } else { | |
| | | cache_ = NULL; | |
| | | } | |
| | | clim_ = clim > 0 ? clim : DEFCLIM; | |
| | | csiz_ = 0; | |
| | | omode_ = mode; | |
| | | return true; | |
| | | } | |
| | | /** | |
| | | * Close the database file. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool close() { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | if (cache_) { | |
| | | if (!flush_cache()) err = true; | |
| | | delete cache_; | |
| | | if (tmpdbs_) { | |
| | | if (!merge_tmpdbs()) err = true; | |
| | | report(_KCCODELINE_, "closing the temporary databases"); | |
| | | double stime = time(); | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | BasicDB* tmpdb = tmpdbs_[i]; | |
| | | const std::string& path = tmpdb->path(); | |
| | | if (!tmpdb->close()) { | |
| | | const BasicDB::Error& e = tmpdb->error(); | |
| | | set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| | | if (!tmppath_.empty()) File::remove(path); | |
| | | delete tmpdb; | |
| | | } | |
| | | double etime = time(); | |
| | | report(_KCCODELINE_, "closing the temporary databases finished: %.6 | |
| | | f", etime - stime); | |
| | | delete[] tmpdbs_; | |
| | | } | |
| | | } | |
| | | if (!db_.close()) err = true; | |
| | | omode_ = 0; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Set the value of a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @param vbuf the pointer to the value region. | |
| | | * @param vsiz the size of the value region. | |
| | | * @return true on success, or false on failure. | |
| | | * @note If no record corresponds to the key, a new record is created. I | |
| | | f the corresponding | |
| | | * record exists, the value is overwritten. | |
| | | */ | |
| | | bool set(const char* kbuf, size_t ksiz, const char* vbuf, size_t vsiz) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ && vbuf && vsiz <= MEMMAXSIZ); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | if (!clean_dbs(kbuf, ksiz)) err = true; | |
| | | cache_->append(kbuf, ksiz, vbuf, vsiz); | |
| | | csiz_ += ksiz + vsiz; | |
| | | if (csiz_ > clim_ && !flush_cache()) err = false; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Set the value of a record. | |
| | | * @note Equal to the original DB::set method except that the parameters | |
| | | are std::string. | |
| | | */ | |
| | | bool set(const std::string& key, const std::string& value) { | |
| | | _assert_(true); | |
| | | return set(key.c_str(), key.size(), value.c_str(), value.size()); | |
| | | } | |
| | | /** | |
| | | * Add a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @param vbuf the pointer to the value region. | |
| | | * @param vsiz the size of the value region. | |
| | | * @return true on success, or false on failure. | |
| | | * @note If no record corresponds to the key, a new record is created. I | |
| | | f the corresponding | |
| | | * record exists, the record is not modified and false is returned. | |
| | | */ | |
| | | bool add(const char* kbuf, size_t ksiz, const char* vbuf, size_t vsiz) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ && vbuf && vsiz <= MEMMAXSIZ); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | if (check_impl(kbuf, ksiz)) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::DUPREC, "record duplication") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | cache_->append(kbuf, ksiz, vbuf, vsiz); | |
| | | csiz_ += ksiz + vsiz; | |
| | | if (csiz_ > clim_ && !flush_cache()) err = false; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Set the value of a record. | |
| | | * @note Equal to the original DB::add method except that the parameters | |
| | | are std::string. | |
| | | */ | |
| | | bool add(const std::string& key, const std::string& value) { | |
| | | _assert_(true); | |
| | | return add(key.c_str(), key.size(), value.c_str(), value.size()); | |
| | | } | |
| | | /** | |
| | | * Replace the value of a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @param vbuf the pointer to the value region. | |
| | | * @param vsiz the size of the value region. | |
| | | * @return true on success, or false on failure. | |
| | | * @note If no record corresponds to the key, no new record is created an | |
| | | d false is returned. | |
| | | * If the corresponding record exists, the value is modified. | |
| | | */ | |
| | | bool replace(const char* kbuf, size_t ksiz, const char* vbuf, size_t vsiz | |
| | | ) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ && vbuf && vsiz <= MEMMAXSIZ); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | if (!check_impl(kbuf, ksiz)) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::NOREC, "no record"); | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | if (!clean_dbs(kbuf, ksiz)) err = true; | |
| | | cache_->append(kbuf, ksiz, vbuf, vsiz); | |
| | | csiz_ += ksiz + vsiz; | |
| | | if (csiz_ > clim_ && !flush_cache()) err = false; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Replace the value of a record. | |
| | | * @note Equal to the original DB::replace method except that the paramet | |
| | | ers are std::string. | |
| | | */ | |
| | | bool replace(const std::string& key, const std::string& value) { | |
| | | _assert_(true); | |
| | | return replace(key.c_str(), key.size(), value.c_str(), value.size()); | |
| | | } | |
| | | /** | |
| | | * Append the value of a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @param vbuf the pointer to the value region. | |
| | | * @param vsiz the size of the value region. | |
| | | * @return true on success, or false on failure. | |
| | | * @note If no record corresponds to the key, a new record is created. I | |
| | | f the corresponding | |
| | | * record exists, the given value is appended at the end of the existing | |
| | | value. | |
| | | */ | |
| | | bool append(const char* kbuf, size_t ksiz, const char* vbuf, size_t vsiz) | |
| | | { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ && vbuf && vsiz <= MEMMAXSIZ); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | cache_->append(kbuf, ksiz, vbuf, vsiz); | |
| | | csiz_ += ksiz + vsiz; | |
| | | if (csiz_ > clim_ && !flush_cache()) err = false; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Set the value of a record. | |
| | | * @note Equal to the original DB::append method except that the paramete | |
| | | rs are std::string. | |
| | | */ | |
| | | bool append(const std::string& key, const std::string& value) { | |
| | | _assert_(true); | |
| | | return append(key.c_str(), key.size(), value.c_str(), value.size()); | |
| | | } | |
| | | /** | |
| | | * Remove a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @return true on success, or false on failure. | |
| | | * @note If no record corresponds to the key, false is returned. | |
| | | */ | |
| | | bool remove(const char* kbuf, size_t ksiz) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | if (!clean_dbs(kbuf, ksiz)) err = true; | |
| | | cache_->remove(kbuf, ksiz); | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Remove a record. | |
| | | * @note Equal to the original DB::remove method except that the paramete | |
| | | r is std::string. | |
| | | */ | |
| | | bool remove(const std::string& key) { | |
| | | _assert_(true); | |
| | | return remove(key.c_str(), key.size()); | |
| | | } | |
| | | /** | |
| | | * Retrieve the value of a record. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @param sp the pointer to the variable into which the size of the regio | |
| | | n of the return | |
| | | * value is assigned. | |
| | | * @return the pointer to the value region of the corresponding record, o | |
| | | r NULL on failure. | |
| | | * @note If no record corresponds to the key, NULL is returned. Because | |
| | | an additional zero | |
| | | * code is appended at the end of the region of the return value, the ret | |
| | | urn value can be | |
| | | * treated as a C-style string. Because the region of the return value i | |
| | | s allocated with the | |
| | | * the new[] operator, it should be released with the delete[] operator w | |
| | | hen it is no longer | |
| | | * in use. | |
| | | */ | |
| | | char* get(const char* kbuf, size_t ksiz, size_t* sp) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ && sp); | |
| | | ScopedRWLock lock(&mlock_, false); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) return db_.get(kbuf, ksiz, sp); | |
| | | size_t dvsiz = 0; | |
| | | char* dvbuf = db_.get(kbuf, ksiz, &dvsiz); | |
| | | size_t cvsiz = 0; | |
| | | const char* cvbuf = cache_->get(kbuf, ksiz, &cvsiz); | |
| | | struct Record { | |
| | | char* buf; | |
| | | size_t size; | |
| | | }; | |
| | | Record* recs = NULL; | |
| | | bool hit = false; | |
| | | size_t rsiz = 0; | |
| | | if (tmpdbs_) { | |
| | | recs = new Record[dbnum_]; | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | BasicDB* tmpdb = tmpdbs_[i]; | |
| | | Record* rp = recs + i; | |
| | | rp->buf = tmpdb->get(kbuf, ksiz, &rp->size); | |
| | | if (rp->buf) { | |
| | | rsiz += rp->size; | |
| | | hit = true; | |
| | | } | |
| | | } | |
| | | } | |
| | | if (!hit) { | |
| | | delete[] recs; | |
| | | if (!dvbuf && !cvbuf) return NULL; | |
| | | if (!dvbuf) { | |
| | | dvbuf = new char[cvsiz+1]; | |
| | | std::memcpy(dvbuf, cvbuf, cvsiz); | |
| | | *sp = cvsiz; | |
| | | return dvbuf; | |
| | | } | |
| | | if (!cvbuf) { | |
| | | *sp = dvsiz; | |
| | | return dvbuf; | |
| | | } | |
| | | char* rbuf = new char[dvsiz+cvsiz+1]; | |
| | | std::memcpy(rbuf, dvbuf, dvsiz); | |
| | | std::memcpy(rbuf + dvsiz, cvbuf, cvsiz); | |
| | | delete[] dvbuf; | |
| | | *sp = dvsiz + cvsiz; | |
| | | return rbuf; | |
| | | } | |
| | | if (dvbuf) rsiz += dvsiz; | |
| | | if (cvbuf) rsiz += cvsiz; | |
| | | char* rbuf = new char[rsiz+1]; | |
| | | char* wp = rbuf; | |
| | | if (dvbuf) { | |
| | | std::memcpy(wp, dvbuf, dvsiz); | |
| | | wp += dvsiz; | |
| | | delete[] dvbuf; | |
| | | } | |
| | | if (cvbuf) { | |
| | | std::memcpy(wp, cvbuf, cvsiz); | |
| | | wp += cvsiz; | |
| | | } | |
| | | if (recs) { | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | Record* rp = recs + i; | |
| | | if (rp->buf) { | |
| | | std::memcpy(wp, rp->buf, rp->size); | |
| | | wp += rp->size; | |
| | | delete[] rp->buf; | |
| | | } | |
| | | } | |
| | | delete[] recs; | |
| | | } | |
| | | *sp = rsiz; | |
| | | return rbuf; | |
| | | } | |
| | | /** | |
| | | * Retrieve the value of a record. | |
| | | * @note Equal to the original DB::get method except that the first param | |
| | | eters is the key | |
| | | * string and the second parameter is a string to contain the result and | |
| | | the return value is | |
| | | * bool for success. | |
| | | */ | |
| | | bool get(const std::string& key, std::string* value) { | |
| | | _assert_(value); | |
| | | size_t vsiz; | |
| | | char* vbuf = get(key.c_str(), key.size(), &vsiz); | |
| | | if (!vbuf) return false; | |
| | | value->clear(); | |
| | | value->append(vbuf, vsiz); | |
| | | delete[] vbuf; | |
| | | return true; | |
| | | } | |
| | | /** | |
| | | * Synchronize updated contents with the file and the device. | |
| | | * @param hard true for physical synchronization with the device, or fals | |
| | | e for logical | |
| | | * synchronization with the file system. | |
| | | * @param proc a postprocessor object. If it is NULL, no postprocessing | |
| | | is performed. | |
| | | * @return true on success, or false on failure. | |
| | | * @note The operation of the postprocessor is performed atomically and o | |
| | | ther threads accessing | |
| | | * the same record are blocked. To avoid deadlock, any explicit database | |
| | | operation must not | |
| | | * be performed in this function. | |
| | | */ | |
| | | bool synchronize(bool hard = false, BasicDB::FileProcessor* proc = NULL) | |
| | | { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | bool err = false; | |
| | | if (!flush_cache()) err = true; | |
| | | if (tmpdbs_ && !merge_tmpdbs()) err = true; | |
| | | if (!db_.synchronize(hard, proc)) err = true; | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Remove all records. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool clear() { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, true); | |
| | | if (omode_ == 0) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "not opened"); | |
| | | return false; | |
| | | } | |
| | | if (!cache_) { | |
| | | set_error(_KCCODELINE_, BasicDB::Error::INVALID, "permission denied") | |
| | | ; | |
| | | return false; | |
| | | } | |
| | | cache_->clear(); | |
| | | csiz_ = 0; | |
| | | return db_.clear(); | |
| | | } | |
| | | /** | |
| | | * Get the number of records. | |
| | | * @return the number of records, or -1 on failure. | |
| | | */ | |
| | | int64_t count() { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, false); | |
| | | return count_impl(); | |
| | | } | |
| | | /** | |
| | | * Get the size of the database file. | |
| | | * @return the size of the database file in bytes, or -1 on failure. | |
| | | */ | |
| | | int64_t size() { | |
| | | _assert_(true); | |
| | | ScopedRWLock lock(&mlock_, false); | |
| | | return size_impl(); | |
| | | } | |
| | | /** | |
| | | * Get the path of the database file. | |
| | | * @return the path of the database file, or an empty string on failure. | |
| | | */ | |
| | | std::string path() { | |
| | | _assert_(true); | |
| | | return db_.path(); | |
| | | } | |
| | | /** | |
| | | * Get the miscellaneous status information. | |
| | | * @param strmap a string map to contain the result. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool status(std::map<std::string, std::string>* strmap) { | |
| | | _assert_(strmap); | |
| | | return db_.status(strmap); | |
| | | } | |
| | | /** | |
| | | * Reveal the inner database object. | |
| | | * @return the inner database object, or NULL on failure. | |
| | | */ | |
| | | PolyDB* reveal_inner_db() { | |
| | | _assert_(true); | |
| | | return &db_; | |
| | | } | |
| | | /** | |
| | | * Create a cursor object. | |
| | | * @return the return value is the created cursor object. | |
| | | * @note Because the object of the return value is allocated by the const | |
| | | ructor, it should be | |
| | | * released with the delete operator when it is no longer in use. | |
| | | */ | |
| | | BasicDB::Cursor* cursor() { | |
| | | _assert_(true); | |
| | | return db_.cursor(); | |
| | | } | |
| | | /** | |
| | | * Write a log message. | |
| | | * @param file the file name of the program source code. | |
| | | * @param line the line number of the program source code. | |
| | | * @param func the function name of the program source code. | |
| | | * @param kind the kind of the event. Logger::DEBUG for debugging, Logge | |
| | | r::INFO for normal | |
| | | * information, Logger::WARN for warning, and Logger::ERROR for fatal err | |
| | | or. | |
| | | * @param message the supplement message. | |
| | | */ | |
| | | void log(const char* file, int32_t line, const char* func, BasicDB::Logge | |
| | | r::Kind kind, | |
| | | const char* message) { | |
| | | _assert_(file && line > 0 && func && message); | |
| | | db_.log(file, line, func, kind, message); | |
| | | } | |
| | | /** | |
| | | * Set the internal logger. | |
| | | * @param logger the logger object. | |
| | | * @param kinds kinds of logged messages by bitwise-or: Logger::DEBUG for | |
| | | debugging, | |
| | | * Logger::INFO for normal information, Logger::WARN for warning, and Log | |
| | | ger::ERROR for fatal | |
| | | * error. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool tune_logger(BasicDB::Logger* logger, | |
| | | uint32_t kinds = BasicDB::Logger::WARN | BasicDB::Logger | |
| | | ::ERROR) { | |
| | | _assert_(logger); | |
| | | return db_.tune_logger(logger, kinds); | |
| | | } | |
| | | /** | |
| | | * Set the internal meta operation trigger. | |
| | | * @param trigger the trigger object. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool tune_meta_trigger(BasicDB::MetaTrigger* trigger) { | |
| | | _assert_(trigger); | |
| | | return db_.tune_meta_trigger(trigger); | |
| | | } | |
| | | protected: | |
| | | /** | |
| | | * Report a message for debugging. | |
| | | * @param file the file name of the program source code. | |
| | | * @param line the line number of the program source code. | |
| | | * @param func the function name of the program source code. | |
| | | * @param format the printf-like format string. | |
| | | * @param ... used according to the format string. | |
| | | */ | |
| | | void report(const char* file, int32_t line, const char* func, const char* | |
| | | format, ...) { | |
| | | _assert_(file && line > 0 && func && format); | |
| | | std::string message; | |
| | | va_list ap; | |
| | | va_start(ap, format); | |
| | | vstrprintf(&message, format, ap); | |
| | | va_end(ap); | |
| | | db_.log(file, line, func, BasicDB::Logger::INFO, message.c_str()); | |
| | | } | |
| | | private: | |
| | | /** | |
| | | * Flush all cache records. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool flush_cache() { | |
| | | _assert_(true); | |
| | | bool err = false; | |
| | | double stime = time(); | |
| | | report(_KCCODELINE_, "flushing the cache"); | |
| | | if (tmpdbs_) { | |
| | | BasicDB* tmpdb = tmpdbs_[dbclock_]; | |
| | | TinyHashMap::Sorter sorter(cache_); | |
| | | const char* kbuf, *vbuf; | |
| | | size_t ksiz, vsiz; | |
| | | while ((kbuf = sorter.get(&ksiz, &vbuf, &vsiz)) != NULL) { | |
| | | if (!tmpdb->append(kbuf, ksiz, vbuf, vsiz)) { | |
| | | const BasicDB::Error& e = tmpdb->error(); | |
| | | db_.set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| | | sorter.step(); | |
| | | } | |
| | | dbclock_ = (dbclock_ + 1) % dbnum_; | |
| | | } else { | |
| | | TinyHashMap::Sorter sorter(cache_); | |
| | | const char* kbuf, *vbuf; | |
| | | size_t ksiz, vsiz; | |
| | | while ((kbuf = sorter.get(&ksiz, &vbuf, &vsiz)) != NULL) { | |
| | | if (!db_.append(kbuf, ksiz, vbuf, vsiz)) err = true; | |
| | | sorter.step(); | |
| | | } | |
| | | } | |
| | | cache_->clear(); | |
| | | csiz_ = 0; | |
| | | double etime = time(); | |
| | | report(_KCCODELINE_, "flushing the cache finished: time=%.6f", etime - | |
| | | stime); | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Merge temporary databases. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool merge_tmpdbs() { | |
| | | _assert_(true); | |
| | | bool err = false; | |
| | | report(_KCCODELINE_, "merging the temporary databases"); | |
| | | double stime = time(); | |
| | | if (!db_.merge(tmpdbs_, dbnum_, PolyDB::MAPPEND)) err = true; | |
| | | dbclock_ = 0; | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | BasicDB* tmpdb = tmpdbs_[i]; | |
| | | if (!tmpdb->clear()) { | |
| | | const BasicDB::Error& e = tmpdb->error(); | |
| | | set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| | | } | |
| | | double etime = time(); | |
| | | report(_KCCODELINE_, "merging the temporary databases finished: %.6f", | |
| | | etime - stime); | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Remove a record from databases. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool clean_dbs(const char* kbuf, size_t ksiz) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ); | |
| | | if (db_.remove(kbuf, ksiz)) return true; | |
| | | bool err = false; | |
| | | if (db_.error() != BasicDB::Error::NOREC) err = true; | |
| | | if (tmpdbs_) { | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | BasicDB* tmpdb = tmpdbs_[i]; | |
| | | if (!tmpdb->remove(kbuf, ksiz)) { | |
| | | const BasicDB::Error& e = tmpdb->error(); | |
| | | if (e != BasicDB::Error::NOREC) { | |
| | | set_error(_KCCODELINE_, e.code(), e.message()); | |
| | | err = true; | |
| | | } | |
| | | } | |
| | | } | |
| | | } | |
| | | return !err; | |
| | | } | |
| | | /** | |
| | | * Check whether a record exists. | |
| | | * @param kbuf the pointer to the key region. | |
| | | * @param ksiz the size of the key region. | |
| | | * @return true if the record exists, or false if not. | |
| | | */ | |
| | | bool check_impl(const char* kbuf, size_t ksiz) { | |
| | | _assert_(kbuf && ksiz <= MEMMAXSIZ); | |
| | | char vbuf; | |
| | | if (db_.get(kbuf, ksiz, &vbuf, 1) >= 0) return true; | |
| | | if (cache_) { | |
| | | size_t vsiz; | |
| | | if (cache_->get(kbuf, ksiz, &vsiz)) return true; | |
| | | if (tmpdbs_) { | |
| | | for (size_t i = 0; i < dbnum_; i++) { | |
| | | BasicDB* tmpdb = tmpdbs_[i]; | |
| | | if (tmpdb->get(kbuf, ksiz, &vbuf, 1)) return true; | |
| | | } | |
| | | } | |
| | | } | |
| | | return false; | |
| | | } | |
| | | /** | |
| | | * Get the number of records. | |
| | | * @return the number of records, or -1 on failure. | |
| | | */ | |
| | | int64_t count_impl() { | |
| | | _assert_(true); | |
| | | int64_t dbcnt = db_.count(); | |
| | | if (dbcnt < 0) return -1; | |
| | | if (!cache_) return dbcnt; | |
| | | int64_t ccnt = cache_->count(); | |
| | | return dbcnt > ccnt ? dbcnt : ccnt; | |
| | | } | |
| | | /** | |
| | | * Get the size of the database file. | |
| | | * @return the size of the database file in bytes. | |
| | | */ | |
| | | int64_t size_impl() { | |
| | | _assert_(true); | |
| | | int64_t dbsiz = db_.size(); | |
| | | if (dbsiz < 0) return -1; | |
| | | return dbsiz + csiz_; | |
| | | } | |
| | | /** Dummy constructor to forbid the use. */ | |
| | | IndexDB(const IndexDB&); | |
| | | /** Dummy Operator to forbid the use. */ | |
| | | IndexDB& operator =(const IndexDB&); | |
| | | /** The method lock. */ | |
| | | RWLock mlock_; | |
| | | /** The internal database. */ | |
| | | PolyDB db_; | |
| | | /** The open mode. */ | |
| | | uint32_t omode_; | |
| | | /** The record comparator. */ | |
| | | Comparator* rcomp_; | |
| | | /** The base path of temporary databases. */ | |
| | | std::string tmppath_; | |
| | | /** The temporary databases. */ | |
| | | BasicDB** tmpdbs_; | |
| | | /** The number of temporary databases. */ | |
| | | size_t dbnum_; | |
| | | /** The logical clock for temporary databases. */ | |
| | | int64_t dbclock_; | |
| | | /** The internal cache. */ | |
| | | TinyHashMap* cache_; | |
| | | /** The current size of the internal cache. */ | |
| | | int64_t csiz_; | |
| | | /** The limit size of the internal cache. */ | |
| | | int64_t clim_; | |
| | | }; | |
| | | | |
| } // common namespace | | } // common namespace | |
| | | | |
| #endif // duplication check | | #endif // duplication check | |
| | | | |
| // END OF FILE | | // END OF FILE | |
| | | | |
End of changes. 22 change blocks. |
| 29 lines changed or deleted | | 960 lines changed or added | |
|
| kcmap.h | | kcmap.h | |
| | | | |
| skipping to change at line 24 | | skipping to change at line 24 | |
| | | | |
| #ifndef _KCMAP_H // duplication check | | #ifndef _KCMAP_H // duplication check | |
| #define _KCMAP_H | | #define _KCMAP_H | |
| | | | |
| #include <kccommon.h> | | #include <kccommon.h> | |
| #include <kcutil.h> | | #include <kcutil.h> | |
| | | | |
| namespace kyotocabinet { // common namespace | | namespace kyotocabinet { // common namespace | |
| | | | |
| /** | | /** | |
|
| | | * Doubly-linked hash map. | |
| | | * @param KEY the key type. | |
| | | * @param VALUE the value type. | |
| | | * @param HASH the hash functor. | |
| | | * @param EQUALTO the equality checking functor. | |
| | | */ | |
| | | template <class KEY, class VALUE, | |
| | | class HASH = std::hash<KEY>, class EQUALTO = std::equal_to<KEY> > | |
| | | class LinkedHashMap { | |
| | | public: | |
| | | class Iterator; | |
| | | private: | |
| | | struct Record; | |
| | | /** The default bucket number of hash table. */ | |
| | | static const size_t MAPDEFBNUM = 31; | |
| | | /** The mininum number of buckets to use mmap. */ | |
| | | static const size_t MAPZMAPBNUM = 32768; | |
| | | public: | |
| | | /** | |
| | | * Iterator of records. | |
| | | */ | |
| | | class Iterator { | |
| | | friend class LinkedHashMap; | |
| | | public: | |
| | | /** | |
| | | * Copy constructor. | |
| | | * @param src the source object. | |
| | | */ | |
| | | Iterator(const Iterator& src) : map_(src.map_), rec_(src.rec_) { | |
| | | _assert_(true); | |
| | | } | |
| | | /** | |
| | | * Get the key. | |
| | | */ | |
| | | const KEY& key() { | |
| | | _assert_(true); | |
| | | return rec_->key; | |
| | | } | |
| | | /** | |
| | | * Get the value. | |
| | | */ | |
| | | VALUE& value() { | |
| | | _assert_(true); | |
| | | return rec_->value; | |
| | | } | |
| | | /** | |
| | | * Assignment operator from the self type. | |
| | | * @param right the right operand. | |
| | | * @return the reference to itself. | |
| | | */ | |
| | | Iterator& operator =(const Iterator& right) { | |
| | | _assert_(true); | |
| | | if (&right == this) return *this; | |
| | | map_ = right.map_; | |
| | | rec_ = right.rec_; | |
| | | return *this; | |
| | | } | |
| | | /** | |
| | | * Equality operator with the self type. | |
| | | * @param right the right operand. | |
| | | * @return true if the both are equal, or false if not. | |
| | | */ | |
| | | bool operator ==(const Iterator& right) const { | |
| | | _assert_(true); | |
| | | return map_ == right.map_ && rec_ == right.rec_; | |
| | | } | |
| | | /** | |
| | | * Non-equality operator with the self type. | |
| | | * @param right the right operand. | |
| | | * @return false if the both are equal, or true if not. | |
| | | */ | |
| | | bool operator !=(const Iterator& right) const { | |
| | | _assert_(true); | |
| | | return map_ != right.map_ || rec_ != right.rec_; | |
| | | } | |
| | | /** | |
| | | * Preposting increment operator. | |
| | | * @return the iterator itself. | |
| | | */ | |
| | | Iterator& operator ++() { | |
| | | _assert_(true); | |
| | | rec_ = rec_->next; | |
| | | return *this; | |
| | | } | |
| | | /** | |
| | | * Postpositive increment operator. | |
| | | * @return an iterator of the old position. | |
| | | */ | |
| | | Iterator operator ++(int) { | |
| | | _assert_(true); | |
| | | Iterator old(*this); | |
| | | rec_ = rec_->next; | |
| | | return old; | |
| | | } | |
| | | /** | |
| | | * Preposting decrement operator. | |
| | | * @return the iterator itself. | |
| | | */ | |
| | | Iterator& operator --() { | |
| | | _assert_(true); | |
| | | if (rec_) { | |
| | | rec_ = rec_->prev; | |
| | | } else { | |
| | | rec_ = map_->last_; | |
| | | } | |
| | | return *this; | |
| | | } | |
| | | /** | |
| | | * Postpositive decrement operator. | |
| | | * @return an iterator of the old position. | |
| | | */ | |
| | | Iterator operator --(int) { | |
| | | _assert_(true); | |
| | | Iterator old(*this); | |
| | | if (rec_) { | |
| | | rec_ = rec_->prev; | |
| | | } else { | |
| | | rec_ = map_->last_; | |
| | | } | |
| | | return old; | |
| | | } | |
| | | private: | |
| | | /** | |
| | | * Constructor. | |
| | | * @param map the container. | |
| | | * @param rec the pointer to the current record. | |
| | | */ | |
| | | explicit Iterator(LinkedHashMap* map, Record* rec) : map_(map), rec_(re | |
| | | c) { | |
| | | _assert_(map); | |
| | | } | |
| | | /** The container. */ | |
| | | LinkedHashMap* map_; | |
| | | /** The current record. */ | |
| | | Record* rec_; | |
| | | }; | |
| | | /** | |
| | | * Moving Modes. | |
| | | */ | |
| | | enum MoveMode { | |
| | | MCURRENT, ///< keep the current position | |
| | | MFIRST, ///< move to the first | |
| | | MLAST ///< move to the last | |
| | | }; | |
| | | /** | |
| | | * Default constructor. | |
| | | */ | |
| | | explicit LinkedHashMap() : | |
| | | buckets_(NULL), bnum_(MAPDEFBNUM), first_(NULL), last_(NULL), count_( | |
| | | 0) { | |
| | | _assert_(true); | |
| | | initialize(); | |
| | | } | |
| | | /** | |
| | | * Constructor. | |
| | | * @param bnum the number of buckets of the hash table. | |
| | | */ | |
| | | explicit LinkedHashMap(size_t bnum) : | |
| | | buckets_(NULL), bnum_(bnum), first_(NULL), last_(NULL), count_(0) { | |
| | | _assert_(true); | |
| | | if (bnum_ < 1) bnum_ = MAPDEFBNUM; | |
| | | initialize(); | |
| | | } | |
| | | /** | |
| | | * Destructor. | |
| | | */ | |
| | | ~LinkedHashMap() { | |
| | | _assert_(true); | |
| | | destroy(); | |
| | | } | |
| | | /** | |
| | | * Store a record. | |
| | | * @param key the key. | |
| | | * @param value the value. | |
| | | * @param mode the moving mode. | |
| | | * @return the pointer to the value of the stored record. | |
| | | */ | |
| | | VALUE *set(const KEY& key, const VALUE& value, MoveMode mode) { | |
| | | _assert_(true); | |
| | | size_t bidx = hash_(key) % bnum_; | |
| | | Record* rec = buckets_[bidx]; | |
| | | Record** entp = buckets_ + bidx; | |
| | | while (rec) { | |
| | | if (equalto_(rec->key, key)) { | |
| | | rec->value = value; | |
| | | switch (mode) { | |
| | | default: { | |
| | | break; | |
| | | } | |
| | | case MFIRST: { | |
| | | if (first_ != rec) { | |
| | | if (last_ == rec) last_ = rec->prev; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = NULL; | |
| | | rec->next = first_; | |
| | | first_->prev = rec; | |
| | | first_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | case MLAST: { | |
| | | if (last_ != rec) { | |
| | | if (first_ == rec) first_ = rec->next; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = last_; | |
| | | rec->next = NULL; | |
| | | last_->next = rec; | |
| | | last_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | } | |
| | | return &rec->value; | |
| | | } else { | |
| | | entp = &rec->child; | |
| | | rec = rec->child; | |
| | | } | |
| | | } | |
| | | rec = new Record(key, value); | |
| | | switch (mode) { | |
| | | default: { | |
| | | rec->prev = last_; | |
| | | if (!first_) first_ = rec; | |
| | | if (last_) last_->next = rec; | |
| | | last_ = rec; | |
| | | break; | |
| | | } | |
| | | case MFIRST: { | |
| | | rec->next = first_; | |
| | | if (!last_) last_ = rec; | |
| | | if (first_) first_->prev = rec; | |
| | | first_ = rec; | |
| | | break; | |
| | | } | |
| | | } | |
| | | *entp = rec; | |
| | | count_++; | |
| | | return &rec->value; | |
| | | } | |
| | | /** | |
| | | * Remove a record. | |
| | | * @param key the key. | |
| | | * @return true on success, or false on failure. | |
| | | */ | |
| | | bool remove(const KEY& key) { | |
| | | _assert_(true); | |
| | | size_t bidx = hash_(key) % bnum_; | |
| | | Record* rec = buckets_[bidx]; | |
| | | Record** entp = buckets_ + bidx; | |
| | | while (rec) { | |
| | | if (equalto_(rec->key, key)) { | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | if (rec == first_) first_ = rec->next; | |
| | | if (rec == last_) last_ = rec->prev; | |
| | | *entp = rec->child; | |
| | | count_--; | |
| | | delete rec; | |
| | | return true; | |
| | | } else { | |
| | | entp = &rec->child; | |
| | | rec = rec->child; | |
| | | } | |
| | | } | |
| | | return false; | |
| | | } | |
| | | /** | |
| | | * Migrate a record to another map. | |
| | | * @param key the key. | |
| | | * @param dist the destination map. | |
| | | * @param mode the moving mode. | |
| | | * @return the pointer to the value of the migrated record, or NULL on fa | |
| | | ilure. | |
| | | */ | |
| | | VALUE* migrate(const KEY& key, LinkedHashMap* dist, MoveMode mode) { | |
| | | _assert_(dist); | |
| | | size_t hash = hash_(key); | |
| | | size_t bidx = hash % bnum_; | |
| | | Record* rec = buckets_[bidx]; | |
| | | Record** entp = buckets_ + bidx; | |
| | | while (rec) { | |
| | | if (equalto_(rec->key, key)) { | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | if (rec == first_) first_ = rec->next; | |
| | | if (rec == last_) last_ = rec->prev; | |
| | | *entp = rec->child; | |
| | | count_--; | |
| | | rec->child = NULL; | |
| | | rec->prev = NULL; | |
| | | rec->next = NULL; | |
| | | bidx = hash % dist->bnum_; | |
| | | Record* drec = dist->buckets_[bidx]; | |
| | | entp = dist->buckets_ + bidx; | |
| | | while (drec) { | |
| | | if (dist->equalto_(drec->key, key)) { | |
| | | if (drec->child) rec->child = drec->child; | |
| | | if (drec->prev) { | |
| | | rec->prev = drec->prev; | |
| | | rec->prev->next = rec; | |
| | | } | |
| | | if (drec->next) { | |
| | | rec->next = drec->next; | |
| | | rec->next->prev = rec; | |
| | | } | |
| | | if (dist->first_ == drec) dist->first_ = rec; | |
| | | if (dist->last_ == drec) dist->last_ = rec; | |
| | | *entp = rec; | |
| | | delete drec; | |
| | | switch (mode) { | |
| | | default: { | |
| | | break; | |
| | | } | |
| | | case MFIRST: { | |
| | | if (dist->first_ != rec) { | |
| | | if (dist->last_ == rec) dist->last_ = rec->prev; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = NULL; | |
| | | rec->next = dist->first_; | |
| | | dist->first_->prev = rec; | |
| | | dist->first_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | case MLAST: { | |
| | | if (dist->last_ != rec) { | |
| | | if (dist->first_ == rec) dist->first_ = rec->next; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = dist->last_; | |
| | | rec->next = NULL; | |
| | | dist->last_->next = rec; | |
| | | dist->last_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | } | |
| | | return &rec->value; | |
| | | } else { | |
| | | entp = &drec->child; | |
| | | drec = drec->child; | |
| | | } | |
| | | } | |
| | | switch (mode) { | |
| | | default: { | |
| | | rec->prev = dist->last_; | |
| | | if (!dist->first_) dist->first_ = rec; | |
| | | if (dist->last_) dist->last_->next = rec; | |
| | | dist->last_ = rec; | |
| | | break; | |
| | | } | |
| | | case MFIRST: { | |
| | | rec->next = dist->first_; | |
| | | if (!dist->last_) dist->last_ = rec; | |
| | | if (dist->first_) dist->first_->prev = rec; | |
| | | dist->first_ = rec; | |
| | | break; | |
| | | } | |
| | | } | |
| | | *entp = rec; | |
| | | dist->count_++; | |
| | | return &rec->value; | |
| | | } else { | |
| | | entp = &rec->child; | |
| | | rec = rec->child; | |
| | | } | |
| | | } | |
| | | return NULL; | |
| | | } | |
| | | /** | |
| | | * Retrieve a record. | |
| | | * @param key the key. | |
| | | * @param mode the moving mode. | |
| | | * @return the pointer to the value of the corresponding record, or NULL | |
| | | on failure. | |
| | | */ | |
| | | VALUE* get(const KEY& key, MoveMode mode) { | |
| | | _assert_(true); | |
| | | size_t bidx = hash_(key) % bnum_; | |
| | | Record* rec = buckets_[bidx]; | |
| | | while (rec) { | |
| | | if (equalto_(rec->key, key)) { | |
| | | switch (mode) { | |
| | | default: { | |
| | | break; | |
| | | } | |
| | | case MFIRST: { | |
| | | if (first_ != rec) { | |
| | | if (last_ == rec) last_ = rec->prev; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = NULL; | |
| | | rec->next = first_; | |
| | | first_->prev = rec; | |
| | | first_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | case MLAST: { | |
| | | if (last_ != rec) { | |
| | | if (first_ == rec) first_ = rec->next; | |
| | | if (rec->prev) rec->prev->next = rec->next; | |
| | | if (rec->next) rec->next->prev = rec->prev; | |
| | | rec->prev = last_; | |
| | | rec->next = NULL; | |
| | | last_->next = rec; | |
| | | last_ = rec; | |
| | | } | |
| | | break; | |
| | | } | |
| | | } | |
| | | return &rec->value; | |
| | | } else { | |
| | | rec = rec->child; | |
| | | } | |
| | | } | |
| | | return NULL; | |
| | | } | |
| | | /** | |
| | | * Remove all records. | |
| | | */ | |
| | | void clear() { | |
| | | _assert_(true); | |
| | | if (count_ < 1) return; | |
| | | Record* rec = last_; | |
| | | while (rec) { | |
| | | Record* prev = rec->prev; | |
| | | delete rec; | |
| | | rec = prev; | |
| | | } | |
| | | for (size_t i = 0; i < bnum_; i++) { | |
| | | buckets_[i] = NULL; | |
| | | } | |
| | | first_ = NULL; | |
| | | last_ = NULL; | |
| | | count_ = 0; | |
| | | } | |
| | | /** | |
| | | * Get the number of records. | |
| | | */ | |
| | | size_t count() { | |
| | | _assert_(true); | |
| | | return count_; | |
| | | } | |
| | | /** | |
| | | * Get an iterator at the first record. | |
| | | */ | |
| | | Iterator begin() { | |
| | | _assert_(true); | |
| | | return Iterator(this, first_); | |
| | | } | |
| | | /** | |
| | | * Get an iterator of the end sentry. | |
| | | */ | |
| | | Iterator end() { | |
| | | _assert_(true); | |
| | | return Iterator(this, NULL); | |
| | | } | |
| | | /** | |
| | | * Get an iterator at a record. | |
| | | * @param key the key. | |
| | | * @return the pointer to the value of the corresponding record, or NULL | |
| | | on failure. | |
| | | */ | |
| | | Iterator find(const KEY& key) { | |
| | | _assert_(true); | |
| | | size_t bidx = hash_(key) % bnum_; | |
| | | Record* rec = buckets_[bidx]; | |
| | | while (rec) { | |
| | | if (equalto_(rec->key, key)) { | |
| | | return Iterator(this, rec); | |
| | | } else { | |
| | | rec = rec->child; | |
| | | } | |
| | | } | |
| | | return Iterator(this, NULL); | |
| | | } | |
| | | /** | |
| | | * Get the reference of the key of the first record. | |
| | | * @return the reference of the key of the first record. | |
| | | */ | |
| | | const KEY& first_key() { | |
| | | _assert_(true); | |
| | | return first_->key; | |
| | | } | |
| | | /** | |
| | | * Get the reference of the value of the first record. | |
| | | * @return the reference of the value of the first record. | |
| | | */ | |
| | | VALUE& first_value() { | |
| | | _assert_(true); | |
| | | return first_->value; | |
| | | } | |
| | | /** | |
| | | * Get the reference of the key of the last record. | |
| | | * @return the reference of the key of the last record. | |
| | | */ | |
| | | const KEY& last_key() { | |
| | | _assert_(true); | |
| | | return last_->key; | |
| | | } | |
| | | /** | |
| | | * Get the reference of the value of the last record. | |
| | | * @return the reference of the value of the last record. | |
| | | */ | |
| | | VALUE& last_value() { | |
| | | _assert_(true); | |
| | | return last_->value; | |
| | | } | |
| | | private: | |
| | | /** | |
| | | * Record data. | |
| | | */ | |
| | | struct Record { | |
| | | KEY key; ///< key | |
| | | VALUE value; ///< value | |
| | | Record* child; ///< child record | |
| | | Record* prev; ///< previous record | |
| | | Record* next; ///< next record | |
| | | /** constructor */ | |
| | | explicit Record(const KEY& k, const VALUE& v) : | |
| | | key(k), value(v), child(NULL), prev(NULL), next(NULL) { | |
| | | _assert_(true); | |
| | | } | |
| | | }; | |
| | | /** | |
| | | * Initialize fields. | |
| | | */ | |
| | | void initialize() { | |
| | | _assert_(true); | |
| | | if (bnum_ >= MAPZMAPBNUM) { | |
| | | buckets_ = (Record**)mapalloc(sizeof(*buckets_) * bnum_); | |
| | | } else { | |
| | | buckets_ = new Record*[bnum_]; | |
| | | for (size_t i = 0; i < bnum_; i++) { | |
| | | buckets_[i] = NULL; | |
| | | } | |
| | | } | |
| | | } | |
| | | /** | |
| | | * Clean up fields. | |
| | | */ | |
| | | void destroy() { | |
| | | _assert_(true); | |
| | | Record* rec = last_; | |
| | | while (rec) { | |
| | | Record* prev = rec->prev; | |
| | | delete rec; | |
| | | rec = prev; | |
| | | } | |
| | | if (bnum_ >= MAPZMAPBNUM) { | |
| | | mapfree(buckets_); | |
| | | } else { | |
| | | delete[] buckets_; | |
| | | } | |
| | | } | |
| | | /** Dummy constructor to forbid the use. */ | |
| | | LinkedHashMap(const LinkedHashMap&); | |
| | | /** Dummy Operator to forbid the use. */ | |
| | | LinkedHashMap& operator =(const LinkedHashMap&); | |
| | | /** The functor of the hash function. */ | |
| | | HASH hash_; | |
| | | /** The functor of the equalto function. */ | |
| | | EQUALTO equalto_; | |
| | | /** The bucket array. */ | |
| | | Record** buckets_; | |
| | | /** The number of buckets. */ | |
| | | size_t bnum_; | |
| | | /** The first record. */ | |
| | | Record* first_; | |
| | | /** The last record. */ | |
| | | Record* last_; | |
| | | /** The number of records. */ | |
| | | size_t count_; | |
| | | }; | |
| | | | |
| | | /** | |
| * Memory-saving string hash map. | | * Memory-saving string hash map. | |
| */ | | */ | |
| class TinyHashMap { | | class TinyHashMap { | |
| public: | | public: | |
| class Iterator; | | class Iterator; | |
| private: | | private: | |
| struct Record; | | struct Record; | |
| struct RecordComparator; | | struct RecordComparator; | |
| /** The default bucket number of hash table. */ | | /** The default bucket number of hash table. */ | |
| static const size_t MAPDEFBNUM = 31; | | static const size_t MAPDEFBNUM = 31; | |
| | | | |
| skipping to change at line 657 | | skipping to change at line 1232 | |
| TinyHashMap& operator =(const TinyHashMap&); | | TinyHashMap& operator =(const TinyHashMap&); | |
| /** The bucket array. */ | | /** The bucket array. */ | |
| char** buckets_; | | char** buckets_; | |
| /** The number of buckets. */ | | /** The number of buckets. */ | |
| size_t bnum_; | | size_t bnum_; | |
| /** The number of records. */ | | /** The number of records. */ | |
| size_t count_; | | size_t count_; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
|
| * Doubly-linked hash map. | | * Memory-saving string array list. | |
| * @param KEY the key type. | | | |
| * @param VALUE the value type. | | | |
| * @param HASH the hash functor. | | | |
| * @param EQUALTO the equality checking functor. | | | |
| */ | | */ | |
|
| template <class KEY, class VALUE, | | class TinyArrayList { | |
| class HASH = std::hash<KEY>, class EQUALTO = std::equal_to<KEY> > | | | |
| class LinkedHashMap { | | | |
| public: | | | |
| class Iterator; | | | |
| private: | | | |
| struct Record; | | | |
| /** The default bucket number of hash table. */ | | | |
| static const size_t MAPDEFBNUM = 31; | | | |
| /** The mininum number of buckets to use mmap. */ | | | |
| static const size_t MAPZMAPBNUM = 32768; | | | |
| public: | | public: | |
| /** | | /** | |
|
| * Iterator of records. | | | |
| */ | | | |
| class Iterator { | | | |
| friend class LinkedHashMap; | | | |
| public: | | | |
| /** | | | |
| * Copy constructor. | | | |
| * @param src the source object. | | | |
| */ | | | |
| Iterator(const Iterator& src) : map_(src.map_), rec_(src.rec_) { | | | |
| _assert_(true); | | | |
| } | | | |
| /** | | | |
| * Get the key. | | | |
| */ | | | |
| const KEY& key() { | | | |
| _assert_(true); | | | |
| return rec_->key; | | | |
| } | | | |
| /** | | | |
| * Get the value. | | | |
| */ | | | |
| VALUE& value() { | | | |
| _assert_(true); | | | |
| return rec_->value; | | | |
| } | | | |
| /** | | | |
| * Assignment operator from the self type. | | | |
| * @param right the right operand. | | | |
| * @return the reference to itself. | | | |
| */ | | | |
| Iterator& operator =(const Iterator& right) { | | | |
| _assert_(true); | | | |
| if (&right == this) return *this; | | | |
| map_ = right.map_; | | | |
| rec_ = right.rec_; | | | |
| return *this; | | | |
| } | | | |
| /** | | | |
| * Equality operator with the self type. | | | |
| * @param right the right operand. | | | |
| * @return true if the both are equal, or false if not. | | | |
| */ | | | |
| bool operator ==(const Iterator& right) const { | | | |
| _assert_(true); | | | |
| return map_ == right.map_ && rec_ == right.rec_; | | | |
| } | | | |
| /** | | | |
| * Non-equality operator with the self type. | | | |
| * @param right the right operand. | | | |
| * @return false if the both are equal, or true if not. | | | |
| */ | | | |
| bool operator !=(const Iterator& right) const { | | | |
| _assert_(true); | | | |
| return map_ != right.map_ || rec_ != right.rec_; | | | |
| } | | | |
| /** | | | |
| * Preposting increment operator. | | | |
| * @return the iterator itself. | | | |
| */ | | | |
| Iterator& operator ++() { | | | |
| _assert_(true); | | | |
| rec_ = rec_->next; | | | |
| return *this; | | | |
| } | | | |
| /** | | | |
| * Postpositive increment operator. | | | |
| * @return an iterator of the old position. | | | |
| */ | | | |
| Iterator operator ++(int) { | | | |
| _assert_(true); | | | |
| Iterator old(*this); | | | |
| rec_ = rec_->next; | | | |
| return old; | | | |
| } | | | |
| /** | | | |
| * Preposting decrement operator. | | | |
| * @return the iterator itself. | | | |
| */ | | | |
| Iterator& operator --() { | | | |
| _assert_(true); | | | |
| if (rec_) { | | | |
| rec_ = rec_->prev; | | | |
| } else { | | | |
| rec_ = map_->last_; | | | |
| } | | | |
| return *this; | | | |
| } | | | |
| /** | | | |
| * Postpositive decrement operator. | | | |
| * @return an iterator of the old position. | | | |
| */ | | | |
| Iterator operator --(int) { | | | |
| _assert_(true); | | | |
| Iterator old(*this); | | | |
| if (rec_) { | | | |
| rec_ = rec_->prev; | | | |
| } else { | | | |
| rec_ = map_->last_; | | | |
| } | | | |
| return old; | | | |
| } | | | |
| private: | | | |
| /** | | | |
| * Constructor. | | | |
| * @param map the container. | | | |
| * @param rec the pointer to the current record. | | | |
| */ | | | |
| explicit Iterator(LinkedHashMap* map, Record* rec) : map_(map), rec_(re | | | |
| c) { | | | |
| _assert_(map); | | | |
| } | | | |
| /** The container. */ | | | |
| LinkedHashMap* map_; | | | |
| /** The current record. */ | | | |
| Record* rec_; | | | |
| }; | | | |
| /** | | | |
| * Moving Modes. | | | |
| */ | | | |
| enum MoveMode { | | | |
| MCURRENT, ///< keep the current position | | | |
| MFIRST, ///< move to the first | | | |
| MLAST ///< move to the last | | | |
| }; | | | |
| /** | | | |
| * Default constructor. | | * Default constructor. | |
| */ | | */ | |
|
| explicit LinkedHashMap() : | | explicit TinyArrayList() : recs_() { | |
| buckets_(NULL), bnum_(MAPDEFBNUM), first_(NULL), last_(NULL), count_( | | | |
| 0) { | | | |
| _assert_(true); | | | |
| initialize(); | | | |
| } | | | |
| /** | | | |
| * Constructor. | | | |
| * @param bnum the number of buckets of the hash table. | | | |
| */ | | | |
| explicit LinkedHashMap(size_t bnum) : | | | |
| buckets_(NULL), bnum_(bnum), first_(NULL), last_(NULL), count_(0) { | | | |
| _assert_(true); | | _assert_(true); | |
|
| if (bnum_ < 1) bnum_ = MAPDEFBNUM; | | | |
| initialize(); | | | |
| } | | } | |
| /** | | /** | |
| * Destructor. | | * Destructor. | |
| */ | | */ | |
|
| ~LinkedHashMap() { | | ~TinyArrayList() { | |
| _assert_(true); | | | |
| destroy(); | | | |
| } | | | |
| /** | | | |
| * Store a record. | | | |
| * @param key the key. | | | |
| * @param value the value. | | | |
| * @param mode the moving mode. | | | |
| * @return the pointer to the value of the stored record. | | | |
| */ | | | |
| VALUE *set(const KEY& key, const VALUE& value, MoveMode mode) { | | | |
| _assert_(true); | | | |
| size_t bidx = hash_(key) % bnum_; | | | |
| Record* rec = buckets_[bidx]; | | | |
| Record** entp = buckets_ + bidx; | | | |
| while (rec) { | | | |
| if (equalto_(rec->key, key)) { | | | |
| rec->value = value; | | | |
| switch (mode) { | | | |
| default: { | | | |
| break; | | | |
| } | | | |
| case MFIRST: { | | | |
| if (first_ != rec) { | | | |
| if (last_ == rec) last_ = rec->prev; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = NULL; | | | |
| rec->next = first_; | | | |
| first_->prev = rec; | | | |
| first_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| case MLAST: { | | | |
| if (last_ != rec) { | | | |
| if (first_ == rec) first_ = rec->next; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = last_; | | | |
| rec->next = NULL; | | | |
| last_->next = rec; | | | |
| last_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| } | | | |
| return &rec->value; | | | |
| } else { | | | |
| entp = &rec->child; | | | |
| rec = rec->child; | | | |
| } | | | |
| } | | | |
| rec = new Record(key, value); | | | |
| switch (mode) { | | | |
| default: { | | | |
| rec->prev = last_; | | | |
| if (!first_) first_ = rec; | | | |
| if (last_) last_->next = rec; | | | |
| last_ = rec; | | | |
| break; | | | |
| } | | | |
| case MFIRST: { | | | |
| rec->next = first_; | | | |
| if (!last_) last_ = rec; | | | |
| if (first_) first_->prev = rec; | | | |
| first_ = rec; | | | |
| break; | | | |
| } | | | |
| } | | | |
| *entp = rec; | | | |
| count_++; | | | |
| return &rec->value; | | | |
| } | | | |
| /** | | | |
| * Remove a record. | | | |
| * @param key the key. | | | |
| * @return true on success, or false on failure. | | | |
| */ | | | |
| bool remove(const KEY& key) { | | | |
| _assert_(true); | | | |
| size_t bidx = hash_(key) % bnum_; | | | |
| Record* rec = buckets_[bidx]; | | | |
| Record** entp = buckets_ + bidx; | | | |
| while (rec) { | | | |
| if (equalto_(rec->key, key)) { | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| if (rec == first_) first_ = rec->next; | | | |
| if (rec == last_) last_ = rec->prev; | | | |
| *entp = rec->child; | | | |
| count_--; | | | |
| delete rec; | | | |
| return true; | | | |
| } else { | | | |
| entp = &rec->child; | | | |
| rec = rec->child; | | | |
| } | | | |
| } | | | |
| return false; | | | |
| } | | | |
| /** | | | |
| * Migrate a record to another map. | | | |
| * @param key the key. | | | |
| * @param dist the destination map. | | | |
| * @param mode the moving mode. | | | |
| * @return the pointer to the value of the migrated record, or NULL on fa | | | |
| ilure. | | | |
| */ | | | |
| VALUE* migrate(const KEY& key, LinkedHashMap* dist, MoveMode mode) { | | | |
| _assert_(dist); | | | |
| size_t hash = hash_(key); | | | |
| size_t bidx = hash % bnum_; | | | |
| Record* rec = buckets_[bidx]; | | | |
| Record** entp = buckets_ + bidx; | | | |
| while (rec) { | | | |
| if (equalto_(rec->key, key)) { | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| if (rec == first_) first_ = rec->next; | | | |
| if (rec == last_) last_ = rec->prev; | | | |
| *entp = rec->child; | | | |
| count_--; | | | |
| rec->child = NULL; | | | |
| rec->prev = NULL; | | | |
| rec->next = NULL; | | | |
| bidx = hash % dist->bnum_; | | | |
| Record* drec = dist->buckets_[bidx]; | | | |
| entp = dist->buckets_ + bidx; | | | |
| while (drec) { | | | |
| if (dist->equalto_(drec->key, key)) { | | | |
| if (drec->child) rec->child = drec->child; | | | |
| if (drec->prev) { | | | |
| rec->prev = drec->prev; | | | |
| rec->prev->next = rec; | | | |
| } | | | |
| if (drec->next) { | | | |
| rec->next = drec->next; | | | |
| rec->next->prev = rec; | | | |
| } | | | |
| if (dist->first_ == drec) dist->first_ = rec; | | | |
| if (dist->last_ == drec) dist->last_ = rec; | | | |
| *entp = rec; | | | |
| delete drec; | | | |
| switch (mode) { | | | |
| default: { | | | |
| break; | | | |
| } | | | |
| case MFIRST: { | | | |
| if (dist->first_ != rec) { | | | |
| if (dist->last_ == rec) dist->last_ = rec->prev; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = NULL; | | | |
| rec->next = dist->first_; | | | |
| dist->first_->prev = rec; | | | |
| dist->first_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| case MLAST: { | | | |
| if (dist->last_ != rec) { | | | |
| if (dist->first_ == rec) dist->first_ = rec->next; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = dist->last_; | | | |
| rec->next = NULL; | | | |
| dist->last_->next = rec; | | | |
| dist->last_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| } | | | |
| return &rec->value; | | | |
| } else { | | | |
| entp = &drec->child; | | | |
| drec = drec->child; | | | |
| } | | | |
| } | | | |
| switch (mode) { | | | |
| default: { | | | |
| rec->prev = dist->last_; | | | |
| if (!dist->first_) dist->first_ = rec; | | | |
| if (dist->last_) dist->last_->next = rec; | | | |
| dist->last_ = rec; | | | |
| break; | | | |
| } | | | |
| case MFIRST: { | | | |
| rec->next = dist->first_; | | | |
| if (!dist->last_) dist->last_ = rec; | | | |
| if (dist->first_) dist->first_->prev = rec; | | | |
| dist->first_ = rec; | | | |
| break; | | | |
| } | | | |
| } | | | |
| *entp = rec; | | | |
| dist->count_++; | | | |
| return &rec->value; | | | |
| } else { | | | |
| entp = &rec->child; | | | |
| rec = rec->child; | | | |
| } | | | |
| } | | | |
| return NULL; | | | |
| } | | | |
| /** | | | |
| * Retrieve a record. | | | |
| * @param key the key. | | | |
| * @param mode the moving mode. | | | |
| * @return the pointer to the value of the corresponding record, or NULL | | | |
| on failure. | | | |
| */ | | | |
| VALUE* get(const KEY& key, MoveMode mode) { | | | |
| _assert_(true); | | | |
| size_t bidx = hash_(key) % bnum_; | | | |
| Record* rec = buckets_[bidx]; | | | |
| while (rec) { | | | |
| if (equalto_(rec->key, key)) { | | | |
| switch (mode) { | | | |
| default: { | | | |
| break; | | | |
| } | | | |
| case MFIRST: { | | | |
| if (first_ != rec) { | | | |
| if (last_ == rec) last_ = rec->prev; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = NULL; | | | |
| rec->next = first_; | | | |
| first_->prev = rec; | | | |
| first_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| case MLAST: { | | | |
| if (last_ != rec) { | | | |
| if (first_ == rec) first_ = rec->next; | | | |
| if (rec->prev) rec->prev->next = rec->next; | | | |
| if (rec->next) rec->next->prev = rec->prev; | | | |
| rec->prev = last_; | | | |
| rec->next = NULL; | | | |
| last_->next = rec; | | | |
| last_ = rec; | | | |
| } | | | |
| break; | | | |
| } | | | |
| } | | | |
| return &rec->value; | | | |
| } else { | | | |
| rec = rec->child; | | | |
| } | | | |
| } | | | |
| return NULL; | | | |
| } | | | |
| /** | | | |
| * Remove all records. | | | |
| */ | | | |
| void clear() { | | | |
| _assert_(true); | | _assert_(true); | |
|
| if (count_ < 1) return; | | std::deque<char*>::iterator it = recs_.begin(); | |
| Record* rec = last_; | | std::deque<char*>::iterator itend = recs_.end(); | |
| while (rec) { | | while (it != itend) { | |
| Record* prev = rec->prev; | | delete[] *it; | |
| delete rec; | | ++it; | |
| rec = prev; | | | |
| } | | | |
| for (size_t i = 0; i < bnum_; i++) { | | | |
| buckets_[i] = NULL; | | | |
| } | | } | |
|
| first_ = NULL; | | | |
| last_ = NULL; | | | |
| count_ = 0; | | | |
| } | | } | |
| /** | | /** | |
|
| * Get the number of records. | | * Insert a record at the bottom of the list. | |
| */ | | * @param buf the pointer to the record region. | |
| size_t count() { | | * @param size the size of the record region. | |
| _assert_(true); | | | |
| return count_; | | | |
| } | | | |
| /** | | | |
| * Get an iterator at the first record. | | | |
| */ | | */ | |
|
| Iterator begin() { | | void push(const char* buf, size_t size) { | |
| _assert_(true); | | _assert_(buf && size <= MEMMAXSIZ); | |
| return Iterator(this, first_); | | size_t rsiz = sizevarnum(size) + size; | |
| | | char* rbuf = new char[rsiz]; | |
| | | char* wp = rbuf + writevarnum(rbuf, size); | |
| | | std::memcpy(wp, buf, size); | |
| | | recs_.push_back(rbuf); | |
| } | | } | |
| /** | | /** | |
|
| * Get an iterator of the end sentry. | | * Remove a record at the bottom of the list. | |
| | | * @return true if the operation success, or false if there is no record | |
| | | in the list. | |
| */ | | */ | |
|
| Iterator end() { | | bool pop() { | |
| _assert_(true); | | _assert_(true); | |
|
| return Iterator(this, NULL); | | if (recs_.empty()) return false; | |
| | | delete[] recs_.back(); | |
| | | recs_.pop_back(); | |
| | | return true; | |
| } | | } | |
| /** | | /** | |
|
| * Get an iterator at a record. | | * Insert a record at the top of the list. | |
| * @param key the key. | | * @param buf the pointer to the record region. | |
| * @return the pointer to the value of the corresponding record, or NULL | | * @param size the size of the record region. | |
| on failure. | | | |
| */ | | */ | |
|
| Iterator find(const KEY& key) { | | void unshift(const char* buf, size_t size) { | |
| _assert_(true); | | _assert_(buf && size <= MEMMAXSIZ); | |
| size_t bidx = hash_(key) % bnum_; | | size_t rsiz = sizevarnum(size) + size; | |
| Record* rec = buckets_[bidx]; | | char* rbuf = new char[rsiz]; | |
| while (rec) { | | char* wp = rbuf + writevarnum(rbuf, size); | |
| if (equalto_(rec->key, key)) { | | std::memcpy(wp, buf, size); | |
| return Iterator(this, rec); | | recs_.push_front(rbuf); | |
| } else { | | | |
| rec = rec->child; | | | |
| } | | | |
| } | | | |
| return Iterator(this, NULL); | | | |
| } | | } | |
| /** | | /** | |
|
| * Get the reference of the key of the first record. | | * Remove a record at the top of the list. | |
| * @return the reference of the key of the first record. | | * @return true if the operation success, or false if there is no record | |
| | | in the list. | |
| */ | | */ | |
|
| const KEY& first_key() { | | bool shift() { | |
| _assert_(true); | | _assert_(true); | |
|
| return first_->key; | | if (recs_.empty()) return false; | |
| | | delete[] recs_.front(); | |
| | | recs_.pop_front(); | |
| | | return true; | |
| } | | } | |
| /** | | /** | |
|
| * Get the reference of the value of the first record. | | * Insert a record at the position of the given index of the list. | |
| * @return the reference of the value of the first record. | | * @param buf the pointer to the record region. | |
| | | * @param size the size of the record region. | |
| | | * @param idx the index of the position. It must be equal to or less tha | |
| | | n the number of | |
| | | * records. | |
| */ | | */ | |
|
| VALUE& first_value() { | | void insert(const char* buf, size_t size, size_t idx) { | |
| _assert_(true); | | size_t rsiz = sizevarnum(size) + size; | |
| return first_->value; | | char* rbuf = new char[rsiz]; | |
| | | char* wp = rbuf + writevarnum(rbuf, size); | |
| | | std::memcpy(wp, buf, size); | |
| | | recs_.insert(recs_.begin() + idx, rbuf); | |
| } | | } | |
| /** | | /** | |
|
| * Get the reference of the key of the last record. | | * Remove a record at the position of the given index of the list. | |
| * @return the reference of the key of the last record. | | * @param idx the index of the position. It must be less than the number | |
| | | of records. | |
| */ | | */ | |
|
| const KEY& last_key() { | | void remove(size_t idx) { | |
| _assert_(true); | | _assert_(true); | |
|
| return last_->key; | | std::deque<char*>::iterator it = recs_.begin() + idx; | |
| | | delete[] *it; | |
| | | recs_.erase(it); | |
| } | | } | |
| /** | | /** | |
|
| * Get the reference of the value of the last record. | | * Retrieve a record at the position of the given index of the list. | |
| * @return the reference of the value of the last record. | | * @param idx the index of the position. It must be less than the number | |
| | | of records. | |
| | | * @param sp the pointer to the variable into which the size of the regio | |
| | | n of the return | |
| | | * value is assigned. | |
| | | * @return the pointer to the region of the retrieved record. | |
| */ | | */ | |
|
| VALUE& last_value() { | | const char* get(size_t idx, size_t* sp) { | |
| _assert_(true); | | _assert_(sp); | |
| return last_->value; | | const char* rbuf = recs_[idx]; | |
| | | uint64_t rsiz; | |
| | | const char* rp = rbuf + readvarnum(rbuf, sizeof(uint64_t), &rsiz); | |
| | | *sp = rsiz; | |
| | | return rp; | |
| } | | } | |
|
| private: | | | |
| /** | | | |
| * Record data. | | | |
| */ | | | |
| struct Record { | | | |
| KEY key; ///< key | | | |
| VALUE value; ///< value | | | |
| Record* child; ///< child record | | | |
| Record* prev; ///< previous record | | | |
| Record* next; ///< next record | | | |
| /** constructor */ | | | |
| explicit Record(const KEY& k, const VALUE& v) : | | | |
| key(k), value(v), child(NULL), prev(NULL), next(NULL) { | | | |
| _assert_(true); | | | |
| } | | | |
| }; | | | |
| /** | | /** | |
|
| * Initialize fields. | | * Remove all records. | |
| */ | | */ | |
|
| void initialize() { | | void clear() { | |
| _assert_(true); | | _assert_(true); | |
|
| if (bnum_ >= MAPZMAPBNUM) { | | std::deque<char*>::iterator it = recs_.begin(); | |
| buckets_ = (Record**)mapalloc(sizeof(*buckets_) * bnum_); | | std::deque<char*>::iterator itend = recs_.end(); | |
| } else { | | while (it != itend) { | |
| buckets_ = new Record*[bnum_]; | | delete[] *it; | |
| for (size_t i = 0; i < bnum_; i++) { | | ++it; | |
| buckets_[i] = NULL; | | | |
| } | | | |
| } | | } | |
|
| | | recs_.clear(); | |
| } | | } | |
| /** | | /** | |
|
| * Clean up fields. | | * Get the number of records. | |
| | | * @return the number of records. | |
| */ | | */ | |
|
| void destroy() { | | size_t count() { | |
| _assert_(true); | | _assert_(true); | |
|
| Record* rec = last_; | | return recs_.size(); | |
| while (rec) { | | | |
| Record* prev = rec->prev; | | | |
| delete rec; | | | |
| rec = prev; | | | |
| } | | | |
| if (bnum_ >= MAPZMAPBNUM) { | | | |
| mapfree(buckets_); | | | |
| } else { | | | |
| delete[] buckets_; | | | |
| } | | | |
| } | | } | |
|
| | | private: | |
| /** Dummy constructor to forbid the use. */ | | /** Dummy constructor to forbid the use. */ | |
|
| LinkedHashMap(const LinkedHashMap&); | | TinyArrayList(const TinyArrayList&); | |
| /** Dummy Operator to forbid the use. */ | | /** Dummy Operator to forbid the use. */ | |
|
| LinkedHashMap& operator =(const LinkedHashMap&); | | TinyArrayList& operator =(const TinyArrayList&); | |
| /** The functor of the hash function. */ | | /** The record list. */ | |
| HASH hash_; | | std::deque<char*> recs_; | |
| /** The functor of the equalto function. */ | | | |
| EQUALTO equalto_; | | | |
| /** The bucket array. */ | | | |
| Record** buckets_; | | | |
| /** The number of buckets. */ | | | |
| size_t bnum_; | | | |
| /** The first record. */ | | | |
| Record* first_; | | | |
| /** The last record. */ | | | |
| Record* last_; | | | |
| /** The number of records. */ | | | |
| size_t count_; | | | |
| }; | | }; | |
| | | | |
| } // common namespace | | } // common namespace | |
| | | | |
| #endif // duplication check | | #endif // duplication check | |
| | | | |
| // END OF FILE | | // END OF FILE | |
| | | | |
End of changes. 37 change blocks. |
| 529 lines changed or deleted | | 675 lines changed or added | |
|