13.PHP核心技术与最佳实践 --- Hash 算法与数据库实现

1.Hash 函数
	hash 表又称散列表,通过关键字key映射到数组中的一个位置来访问记录,以加快查找的速度。这个映射的函数称为hash函数,
  存放记录的数组称为hash表。
    hash 函数的作用是把任意长度的输入,通过hash算法变成固定长度的输出,该输出值就是 hash 值。这种转换是一种压缩映射,
  也就是 hash 值的空间通常远小于输入空间,不同的输入可能会散列成相同的输出,而不可能从 hash 值来唯一的确定输入值。
    一个好的hash函数应该慢如以下条件:每个关键字都可以均匀的分布到hash表的任意一个位置,并与其他已被散列的hash表中的
  关键字不发生冲突。


2.Hash 算法
	关键字k可能是整数或者字符串。可以按照关键字的类型设计不同的hash算法,整数关键字的hash 算法有以下几种:
	1.直接取余法
		关键字 k 除以 hash 表的大小 m 取余 :
		h(k) = k mod m

	2.乘积取整法
		关键字 k 乘以一个常数 A(0 < A < 1),并抽取出 kA 的小数部分。然后用 hash 表大小 m 乘以这个值,再取整数部分即可。
		h(k) = floor(m * (kA mod 1))
		kA mod 1 表示 kA 的小数部分,floor 是取整操作。

	当关键字是字符串的时候,就不能用上面的 hash 算法。因为字符串是由字符组成的,所以可以把字符串所有字符的 ASCII 码加起来得到
  一个整数,然后再按照上面的 hash 算法去计算得到。
  	function hash($key, $m) {
  		$strlen = strlen($key);
  		for ($i = 0; $i < $strlen; $i++) {
  			$hashval += ord($key[$i]);
  		}
  		return $hashval % $m;
  	}


3.Hash 表
	Hash 表的时间复杂度为 O(1)。
	Hash 表的实现步骤:
	1.创建一个固定大小的数组用于存放数据
	2.设计 hash 函数
	3.通过 hash 函数把关键字映射到数组的某个位置,并在此位置上进行数据存取

	//HashTable 类
class HashTable
{
    private $buckets;
    private $size = 10;

    public function __construct()
    {
        $this->buckets = new SplFixedArray($this->size);
    }

    //就散 hash 值
    private function hashfunc($key)
    {
        $strlen = strlen($key);
        $hashval = 0;
        for ($i = 0; $i < $strlen; $i++) {
            $hashval += ord($key[$i]);
        }
        return $hashval % $this->size;
    }

    //插入操作
    public function insert($key, $val)
    {
        $index = $this->hashfunc($key);
        $this->buckets[$index] = $val;
    }

    //查找
    public function find($key)
    {
        $index = $this->hashfunc($key);
        return $this->buckets[$index];
    }
}

$ht = new HashTable();
$ht->insert('key1', 'value1');
$ht->insert('key12', 'value2');

echo $ht->find('key1') . PHP_EOL;  //value2
echo $ht->find('key12') . PHP_EOL;  //value2, hash 冲突


	Hash 冲突: 不同关键字通过 hash 函数计算出来的 hash 值相同。解决冲突的常见方法有:开放定址法和拉链法。
	1.拉链法解决冲突
		将所有相同 hash 值的关键字节点链接在同一个链表中。拉链法是把相同 hash 值的关键字节点以一个链表链接起来,那么在查找元素的时候,
	  就必须遍历这条链表,比较链表中每个元素的关键字与查找的关键字是否相等,如果相等就是我们要找的元素。
	  	因为节点需要保持关键字(key)和数据(value),同时还要记录具有相同 hash 值的节点。所有创建一个 HashNode 类存储这些信息,HashNode
	  结构如下:

	  //HashTable 类
class HashTable
{
    private $buckets;
    private $size = 10;

    public function __construct()
    {
        $this->buckets = new SplFixedArray($this->size);
    }

    //就散 hash 值
    private function hashfunc($key)
    {
        $strlen = strlen($key);
        $hashval = 0;
        for ($i = 0; $i < $strlen; $i++) {
            $hashval += ord($key[$i]);
        }
        return $hashval % $this->size;
    }

    //插入操作
    //修改后的插入算法如下:
    //1.使用hash函数计算关键字的 hash值,通过 hash 值定位到 hash 表的指定位置
    //2.如果此位置已经被其他节点占用,把新节点的 $nextNode 指向此节点,否则把新节点的$nextNode 设置为null
    //3.把新节点保存到 hash 表的当前位置
    public function insert($key, $val)
    {
        $index = $this->hashfunc($key);
        if (isset($this->buckets[$index])) {
            $newNode = new HashNode($key,$val,$this->buckets[$index]);
        } else {
            $newNode = new HashNode($key,$val,null);
        }
        $this->buckets[$index] = $newNode;
    }

    //查找
    //1.使用 hash 函数计算关键字的 hash 值,通过 hash 值定位到 hash 表的指定位置
    //2.遍历当前链表,比较链表中每个节点的关键字与查找的关键字是否相等。如果相等,查找成功
    //3.如果整个链表都没有要查找的关键字,查找失败
    public function find($key)
    {
        $index = $this->hashfunc($key);
        $current = $this->buckets[$index];
        while (isset($current)) {
            if ($current->key == $key) {
                return $current->value;
            }
            $current = $current->nextNode;
        }
        return null;
    }
}

$ht = new HashTable();
$ht->insert('key1', 'value1');
$ht->insert('key12', 'value2');

echo $ht->find('key1') . PHP_EOL;  //value2
echo $ht->find('key12') . PHP_EOL;  //value2, hash 冲突


class HashNode
{
    public $key;
    public $value;
    public $nextNode;

    public function __construct($key, $value, $nextNode = null)
    {
        $this->key = $key;
        $this->value = $value;
        $this->nextNode = $nextNode;
    }
}

	2.开放定址法


4.一个小型数据库的实现
	企业级数据库通常使用 B+Tree,或者动态 hash 技术作为索引算法。这些算法的检索速度非常快,但同时也非常复杂。

	使用 php 向文件写入一个整数时,php会先把整数转换为字符串,然后再进行写入操作。
$fp = fopen('data.dat','wb');
fwrite($fp,12,4);
fclose($fp);
	如果想利用 php 向文件写入一个整数的二进制代码时,就不能用上面方法。
	pack 函数的作用是把数据装入一个二进制字符串。
	pack ( string $format [, mixed $args [, mixed $... ]] ) : string  //根据format将给地的参数打包成二进制字符串。

$fp = fopen('data.dat','wb');
$bin = pack("L",12);  //L,代表一个无符号长整型
$bin = pack("LLL",12,12,12);  //3个无符号长整型
fwrite($fp,$bin,4);
fclose($fp);

	unpack ( string $format , string $data [, int $offset = 0 ] ) : array
$fp = fopen('data.dat','wb');
$bin = fread($fp,4);
$pack = unpack('L', $bin);
fclose($fp);
print_r($pack);

	索引文件和数据文件:
	我们使用2个文件存储信息:一个索引文件和一个数据文件。索引文件包含索引值(即键)和指向数据文件中对应数据记录的指针(即偏移量)。
  有许多技术可以用来组织索引文件,以提高键查询的速度和效率。索引文件以 idx 作为后缀,数据文件以 dat 为后缀。
  	索引文件由3部分组成:空闲链表指针,hash表和索引记录表。

hash :

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值