/** * Yudit Unicode Editor Source File * * GNU Copyright (C) 1997-2006 Gaspar Sinai * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * dated June 1991. See file COPYYING for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "SBHashtable.h" #include "SObject.h" #include "SExcept.h" #include #include #include #define MAX_DATA_BYTES 8 #define HASH_INITIAL_SIZE 7 #define PSIZE (sizeof(char*)) #define RESIZE_AT 70 // Percent #define PRIME_GROW_PERC 70 // Percent. static unsigned int largerPrime (unsigned int in); static unsigned int sqrimax (unsigned int sq); static char* getNullVector(); int SBHashtable::debugLevel = 0; class SBucketItem : public SObject { public: SBucketItem (const SString& key, const char* value, int len); SBucketItem (const SBucketItem& i); virtual ~SBucketItem() {} virtual SObject* clone () const { SBucketItem* n = new SBucketItem(*this); CHECK_NEW(n); return n; } // Hope for the best - this should be aligned... union { double d; void* v; char c[MAX_DATA_BYTES]; } value; SString key; unsigned int hash; }; #define SBucketVector SBucketItem** static SBucketVector _subBucketAppend (SBucketVector bv, SBucketItem* bi); static unsigned int _subBucketVectorSize (SBucketVector bv); #define GET_BUCKET(_arr, _mod) *((SBucketItem***)&_arr[_mod*PSIZE]) //typedef SVector SBucketVector; /** * Create a signle bucketitem * @param k is the key * @param v is the value * @param len is the size. max is 8 */ SBucketItem::SBucketItem (const SBucketItem& item) : SObject(), key (item.key) { hash =item.hash; memcpy (&value, &item.value, MAX_DATA_BYTES); } /** * Create a signle bucketitem * @param k is the key * @param v is the value * @param len is the size. max is 8 */ SBucketItem::SBucketItem (const SString& k, const char* v, int len) : SObject(), key(k) { if (len > MAX_DATA_BYTES) { fprintf (stderr, "Internal error: SBucketItem:: bad data length\n"); // Dump here. } hash = key.hashCode (); memcpy (&value, v, len); } /** * Set the debug level * @param level 0 means no debug printouts. * @return the previous level */ int SBHashtable::debug (int level) { int old = debugLevel; debugLevel = level; return old; } SObject* SBHashtable::clone() const { SBHashtable * n = new SBHashtable (*this); CHECK_NEW(n); return n; } /** * This is the vector class. I don't prefer using STL. */ SBHashtable::SBHashtable(void) { buffer = new SHShared(HASH_INITIAL_SIZE * PSIZE); CHECK_NEW(buffer); memset (buffer->array, 0, HASH_INITIAL_SIZE * PSIZE); } /** * Create a vector from a const vector. This is * used when you do * SBHashtable v = old; * @param v is the old */ SBHashtable::SBHashtable (const SBHashtable& v) { // Assign the common buffer and increment the reference count buffer = (SHShared*) v.buffer; buffer->count++; } /** * Assign a vector from a const vector. This is * used when you do * SBHashtable v; * v = old; * @param v is the old */ SBHashtable& SBHashtable::operator=(const SBHashtable& v) { refer (v); return *this; } /** * Assign a vector from a const vector by changing the reference. * @param v is the input vector */ void SBHashtable::refer(const SBHashtable& v) { if (&v==this) return; cleanup (); buffer = (SHShared*) v.buffer; buffer->count++; return ; } /** * The destructor */ SBHashtable::~SBHashtable() { cleanup(); } /** * Return the bucket array size. */ unsigned int SBHashtable::size( ) const { return buffer->arraySize/PSIZE; } /** * Return the bucket array size. * param sub is the subbucket index. */ unsigned int SBHashtable::size(int sub) const { SBucketVector bv = GET_BUCKET (buffer->array, sub); if (bv == 0) return 0; return _subBucketVectorSize (bv); } /** * Remake the hash */ void SBHashtable::rehash() { if (buffer->count != 1) { fprintf (stderr, "Internal error: SBuffer::rehash\n"); exit (1); } SHShared *sb = buffer; unsigned int bigger = largerPrime(buffer->arraySize/PSIZE); if (debugLevel > 1) { fprintf (stderr, "SBHashtable::rehash from %u to %u\n", (unsigned int)(buffer->arraySize/PSIZE), bigger); } buffer = new SHShared (bigger * PSIZE); CHECK_NEW(buffer); memset (buffer->array, 0, bigger * PSIZE); SBucketVector bo; SBucketVector bn; for (unsigned int i=0; iarraySize/PSIZE; i++) { bo = GET_BUCKET (sb->array, i); if (bo == 0) continue; // Move the items over... for (unsigned j=0; bo[j] != 0; j++) { SBucketItem* bi =bo[j]; unsigned int mod = bi->hash % (buffer->arraySize/PSIZE); bn = GET_BUCKET (buffer->array, mod); GET_BUCKET(buffer->array, mod) = _subBucketAppend (bn, bi); buffer->vectorSize++; } delete bo; } delete sb; } /** * Replace char's with len length at index * @param index is the reference index of len blocks * @param e is the pointer to the char array to be saved * @param len is the length of the block. */ void SBHashtable::put (const SString& index, const char* e, int len, bool replace) { // Resize buckets if (buffer->vectorSize*PSIZE >= (buffer->arraySize * RESIZE_AT)/100) { rehash (); } unsigned int mod = index.hashCode() % (buffer->arraySize/PSIZE); SBucketItem* sbi = new SBucketItem (index, e, len); CHECK_NEW(sbi); SBucketVector bv = GET_BUCKET (buffer->array, mod); if (bv==0) { //fprintf (stderr, "NEW BV %u\n", mod); bv = new SBucketItem*[2]; CHECK_NEW(bv); bv[0] = sbi; bv[1] = 0; GET_BUCKET (buffer->array, mod)=bv; buffer->vectorSize++; return; } //fprintf (stderr, "OLD BV %u\n", mod); // Find the old one and delete it. if (replace) { for (unsigned int i=0; bv[i]!= 0; i++) { SBucketItem* bi = bv[i]; if (bi->key == index) { delete bi; bv[i] = sbi; return; } } } bv = _subBucketAppend (bv, sbi); GET_BUCKET(buffer->array, mod) = bv; buffer->vectorSize++; //fprintf (stderr, "NOREPLACE AFTER %u (%u)\n", _subBucketVectorSize (bv), mod); //fprintf (stderr, "NOREPLACE AFTER %u\n", _subBucketVectorSize (bv)); //fprintf (stderr, "NOREPLACE AFTER %u\n", // _subBucketVectorSize (GET_BUCKET(buffer->array, mod))); } /** * Remove char's with len length at index * @param index is the reference index of len blocks * @param len is the length of the block. */ void SBHashtable::remove (const SString& index) { derefer (); unsigned int mod = index.hashCode() % (buffer->arraySize/PSIZE); SBucketVector bv = GET_BUCKET (buffer->array, mod); if (bv == 0) return; SBucketItem* bi; for (unsigned int i=0; bv[i] != 0; i++) { bi = bv[i]; if (bi->key == index) { buffer->vectorSize--; delete bi; unsigned int j; for (j=i; bv[j]!=0; j++) { bv[j] = bv[j+1]; } if (j==1) { delete bv; GET_BUCKET (buffer->array, mod) = 0; break; } break; } } } /** * Clear the array and make a new SHShared. */ void SBHashtable::clear () { cleanup (); buffer = new SHShared(HASH_INITIAL_SIZE * PSIZE); CHECK_NEW(buffer); memset (buffer->array, 0, HASH_INITIAL_SIZE * PSIZE); } /** * clean up. usually called before delete. * remove the buffer or its reference. This is private! */ void SBHashtable::cleanup () { if (!buffer) return; // already clean. if (buffer->count==1) { SBucketVector bv; for (unsigned int i=0; iarraySize/PSIZE; i++) { bv = GET_BUCKET (buffer->array, i); if (bv) { SBucketItem* bi; for (unsigned int j=0; bv[j] != 0; j++) { bi = bv[j]; delete bi; } delete bv; } } delete buffer; } else { buffer->count--; } buffer = 0; } /** * Return the element at index. * @param index is the reference index of len blocks */ const char* SBHashtable::get (const SString& index) const { unsigned int mod = index.hashCode() % (buffer->arraySize/PSIZE); SBucketVector bv = GET_BUCKET (buffer->array, mod); if (bv == 0) return getNullVector(); SBucketItem* bi; for (unsigned int i=0; bv[i] != 0; i++) { bi = bv[i]; if (bi->key == index) { return ((char*) &bi->value); } } return getNullVector(); } /** * Return the element at index. * @param index is the reference index of len blocks */ const char* SBHashtable::get (unsigned int bucket, unsigned int subbucket) const { SBucketVector bv = GET_BUCKET (buffer->array, bucket); if (bv == 0) return getNullVector(); for (unsigned int i=0; bv[i] != 0; i++) { if (i==subbucket) { SBucketItem* bi = bv[i]; return ((const char*) &bi->value); } } return getNullVector(); } /** * This could be used if you don't want to disturb the rehash. */ void SBHashtable::put (unsigned int bucket, unsigned int subbucket, const char* e, int len) { derefer (); SBucketVector bv = GET_BUCKET (buffer->array, bucket); if (bv == 0) return; SBucketItem* bi = bv[subbucket]; memcpy (&bi->value, e, len); } /** * Return the element at index. * @param index is the reference index of len blocks */ const SString& SBHashtable::key (unsigned int bucket, unsigned int subbucket) const { SBucketVector bv = GET_BUCKET (buffer->array, bucket); if (bv == 0) return SStringNull; SBucketItem* bi = bv[subbucket]; return (bi->key); } /** * Get the list of keys * @param keys is the string list of output */ void SBHashtable::keys(SStringVector* keys) const { keys->clear(); for (unsigned int i=0; iappend(key(i,j)); } } } /** * The array will change. If the buffer is shared copy the buffer. */ void SBHashtable::derefer() { if (buffer->count==1) return; buffer->count--; SHShared* oldBuffer = buffer; // This copies the array.. buffer = new SHShared (*oldBuffer); CHECK_NEW(buffer); for (unsigned int i=0; iarraySize/PSIZE; i++) { SBucketVector bv = GET_BUCKET (oldBuffer->array, i); if (bv ==0) { GET_BUCKET (buffer->array, i) = 0; continue; } // This will not work this way. The deferer routine // that comes after this can not recreate objects - // It can not put two things in the same array! // Note that we don't count references of contained object. // Copying read-only hash is ok, but read write can be expensive. // GET_BUCKET (buffer->array, i) = new SBucketVector(*bv); // Copy the whole array comment this // Uncomment of the above one does not work. unsigned int ssize = _subBucketVectorSize (bv); SBucketVector nv = new SBucketItem* [ssize+1]; CHECK_NEW (nv); for (unsigned int j=0; jarray, i) = nv; } } /** * These are needed for a real fast hash */ static unsigned int _subBucketVectorSize (SBucketVector bv) { if (bv == 0) return 0; unsigned int i; for (i=0; bv[i]!=0; i++) {} return i; } /** * These are needed for a real fast hash */ static SBucketVector _subBucketAppend (SBucketVector bv, SBucketItem* bi) { unsigned int count = _subBucketVectorSize (bv); SBucketVector nv = new SBucketItem*[count +2]; CHECK_NEW (nv); if (count != 0) { memcpy (nv, bv, count * sizeof (SBucketItem*)); } if (bv != 0) { delete bv; } nv[count] = bi; nv[count+1] = 0; return nv; } /** * Give me a lerger prime than number. */ static unsigned int largerPrime (unsigned int base) { /* It is enough to check if it is dividable by the square root of the fn. */ unsigned int sqrb = sqrimax (base + (base*PRIME_GROW_PERC)/100); unsigned int sqro = sqrimax (base) + 1; if (sqro >= sqrb) sqrb = sqro+1; unsigned int result = (sqrb * sqrb) -1; unsigned int i; while (true) { for (i=2; icount==1) return; SBHashtable::derefer(); const SObject* r; SString k; for (unsigned int i=0; iclone(); SBHashtable::put (i, j, (char*) &newr, sizeof (newr)); } } } void SOHashtable::cleanup() { if (buffer->count!=1) { return; } SObject* r; for (unsigned int i=0; i