/** * @throws Exception */ protected function finish() { // Hack for DBA cross-check $this->hplist = array_reverse($this->hplist); // Calculate the number of items that will be in each hashtable $counts = array_fill(0, 256, 0); foreach ($this->hplist as $item) { ++$counts[255 & $item['h']]; } // Fill in $starts with the *end* indexes $starts = array(); $pos = 0; for ($i = 0; $i < 256; ++$i) { $pos += $counts[$i]; $starts[$i] = $pos; } // Excessively clever and indulgent code to simultaneously fill $packedTables // with the packed hashtables, and adjust the elements of $starts // to actually point to the starts instead of the ends. $packedTables = array_fill(0, $this->numentries, false); foreach ($this->hplist as $item) { $packedTables[--$starts[255 & $item['h']]] = $item; } $final = ''; for ($i = 0; $i < 256; ++$i) { $count = $counts[$i]; // The size of the hashtable will be double the item count. // The rest of the slots will be empty. $len = $count + $count; $final .= pack('VV', $this->pos, $len); $hashtable = array(); for ($u = 0; $u < $len; ++$u) { $hashtable[$u] = array('h' => 0, 'p' => 0); } // Fill the hashtable, using the next empty slot if the hashed slot // is taken. for ($u = 0; $u < $count; ++$u) { $hp = $packedTables[$starts[$i] + $u]; $where = Util::unsignedMod(Util::unsignedShiftRight($hp['h'], 8), $len); while ($hashtable[$where]['p']) { if (++$where == $len) { $where = 0; } } $hashtable[$where] = $hp; } // Write the hashtable for ($u = 0; $u < $len; ++$u) { $buf = pack('vvV', $hashtable[$u]['h'] & 0xffff, Util::unsignedShiftRight($hashtable[$u]['h'], 16), $hashtable[$u]['p']); $this->write($buf); $this->posplus(8); } } // Write the pointer array at the start of the file rewind($this->handle); if (ftell($this->handle) != 0) { $this->throwException('Error rewinding to start of file "' . $this->tmpFileName . '".'); } $this->write($final); }
/** * @param string $key * @return bool */ protected function findNext($key) { if (!$this->loop) { $u = Util::hash($key); $buf = $this->read(8, $u << 3 & 2047); $this->hslots = $this->unpack31(substr($buf, 4)); if (!$this->hslots) { return false; } $this->hpos = $this->unpack31(substr($buf, 0, 4)); $this->khash = $u; $u = Util::unsignedShiftRight($u, 8); $u = Util::unsignedMod($u, $this->hslots); $u <<= 3; $this->kpos = $this->hpos + $u; } while ($this->loop < $this->hslots) { $buf = $this->read(8, $this->kpos); $pos = $this->unpack31(substr($buf, 4)); if (!$pos) { return false; } $this->loop += 1; $this->kpos += 8; if ($this->kpos == $this->hpos + ($this->hslots << 3)) { $this->kpos = $this->hpos; } $u = $this->unpackSigned(substr($buf, 0, 4)); if ($u === $this->khash) { $buf = $this->read(8, $pos); $keyLen = $this->unpack31(substr($buf, 0, 4)); if ($keyLen == strlen($key) && $this->match($key, $pos + 8)) { // Found $this->dlen = $this->unpack31(substr($buf, 4)); $this->dpos = $pos + 8 + $keyLen; return true; } } } return false; }
/** * Search the CDB file for a key. * * Sets `dataLen` and `dataPos` properties if successful. * * @param string $key * @return bool Whether the key was found. */ protected function find($key) { $keyLen = strlen($key); $u = Util::hash($key); $upos = $u << 3 & 2047; $hashSlots = $this->readInt31($upos + 4); if (!$hashSlots) { return false; } $hashPos = $this->readInt31($upos); $keyHash = $u; $u = Util::unsignedShiftRight($u, 8); $u = Util::unsignedMod($u, $hashSlots); $u <<= 3; $keyPos = $hashPos + $u; for ($i = 0; $i < $hashSlots; $i++) { $hash = $this->readInt32($keyPos); $pos = $this->readInt31($keyPos + 4); if (!$pos) { return false; } $keyPos += 8; if ($keyPos == $hashPos + ($hashSlots << 3)) { $keyPos = $hashPos; } if ($hash === $keyHash) { if ($keyLen === $this->readInt31($pos)) { $dataLen = $this->readInt31($pos + 4); $dataPos = $pos + 8 + $keyLen; $foundKey = $this->read($pos + 8, $keyLen); if ($foundKey === $key) { // Found $this->dataLen = $dataLen; $this->dataPos = $dataPos; return true; } } } } return false; }