前言
数据结构专栏,经典数据结构的分析和应用,同时关联贫道的收费专栏--数据类型设计
引入
探讨汉字和标准键盘字符在底层的存储和查找,既是独立内容,又和笔者另一篇贴数据类型设计_散列表经典应用_字典有关联,方便读者自行选择.
字符(汉字)在硬件层面的表示
如笔者前面帖子所述,字符(汉字)本质上是一张无规则点状图,如下图(蓝线部分表示汉字"二")

上图:汉字"二",表示为{1,1},{1,2},{3,0},{3,1},{3,2},{3,3}.一个字是n个二维点坐标.char zifu[n][2].
如果是C++表示如下:
//字符类型
struct zifu{
vector<Point> vp; //点集合
};
//点类型,无颜色
struct Point{
char x_cord; //x坐标
char y_cord; //y坐标
}
有两个隐含的假设条件:
1.x坐标和y坐标的数据类型char,限定255.无论那个字符,横向和纵向最多256个点
2.点是没有颜色的,如果有颜色,数据类型Point有差别.参考笔者另一个贴:数据类设计_图片类设计之8_自由图形类设计_(前端架构)
两点说明:
1.这里的二维坐标点采用默认大小(假定字号8),也就是说每个字符对应的二维点坐标是固定且不同于其他字符的.暂时不考虑字体缩放带来的影响.
2.二维点如何表示成汉字?每个汉字将在屏幕上占用一个方块位置,基点对应上图中行0,列0的位置.每输入一个汉字,把这些二维点集合从字库中提取出来,写入这个方块中.像田字格一样,如图
---------来源于某度,左上角那个点对应基点,外框相当于屏幕方块,坐标系向右向下为正
字符(汉字)的存储
上面分析了字符的表示,接下来看字符是如何存储的.
首先,存储以文件格式进行.那么,一个文件存一个字符吗?在<深入理解计算机系统>P632提到文件存在元数据(每生成一个文件都将有额外的数据产生).而上面的汉字"二"自身占据12个字节(可能还没有元数据多),显然一个文件存一个字符是不经济的.
那么,很自然想到用一个文件存一个音节的字符(注意:这里仍然是以拼音输入法为参照,实际可能未必如此,将在后面说明).字符属于复杂数据 (复杂数据在笔者另一篇贴:数据类型设计_数据的概念的"高级语言中数据分类"有提及).当多个复杂数据放入一个文件中时,需要一张索引表来标识每个数据的长度.举例:假设音节er有三个汉字"二","而"和"尔",放入了同一个文件er.dat中,那么和er.dat同时存在有一个索引表文件(设为er.index),他的内容如下:
|----|------|------|
| 序号 | 对应汉字 | 所占空间 |
| 1 | 二 | 12 |
| 2 | 而 | 40 |
| 3 | 尔 | 30 |
| 4 | ... | ... |
| er.index(部分) |||
注意:中间项"对应汉字"不需要表现出来,只作为理解使用.查找到索引表之后,根据对应汉字所在位置,计算出指针偏移,然后获得这个字符.
解释:每个复杂数据的访问有三点要准确:指针指向首地址要正确,指针偏移量要准确---复杂数据中包含每个简单数据(字长单元及以下长度的数据)的长度,复杂数据的总长度要准确---保证指针指向下一个复杂数据的准确位置.
索引表的存储
索引表非常重要,索引表+数据文件=数据.输入搜索条件,根据索引表和数据文件,即找到数据
索引表和字符一样,考虑怎样存储.索引表是复杂数据,其数据类型为
struct Index{
vector<short> vs;
}
short类型的由来:前面假设字符的长度和宽度方向点最大为255,则所占空间最大是65535.因此在索引表中每有1个字符,占用2个字节空间.前面假设音节er有三个汉字,占用6字节空间,也是很小的.所以考虑将所有e开头的索引表放入同一个文件(假设为e.index)中.
假设e开头的音节有ei,en,er这三个.建立一个e.index把3个音节的索引表放进去,如图
|----|------|------|----|------|------|----|------|------|
| 序号 | 对应汉字 | 所占空间 | 序号 | 对应汉字 | 所占空间 | 序号 | 对应汉字 | 所占空间 |
| 1 | 欸 | 80 | 1 | 嗯 | 80 | 1 | 二 | 12 |
| 2 | 诶 | 60 | 2 | 恩 | 40 | 2 | 而 | 40 |
| 3 | 誒 | 100 | 3 | 摁 | 60 | 3 | 尔 | 30 |
| ei.index内容 ||| en.index内容 ||| er.index内容 |||
| | | | | | | | | |
| e.index(表) |||||||||
为方便描述,e开头音节包括3个(实际可能不止),每个音节包含的汉字设定为3个(忽略实际).注意:e.index是索引文件,包含了ei.index,en.index及er.index,符合上一小节的红色描述部分.所以e.index需要一个索引表.设计如下:
|----|----|----------|------|------|------|
| 序号 | 音节 | 地址 | 汉字个数 | 指针偏移 | (说明) |
| 1 | ei | ei.dat地址 | 3 | 0 | |
| 2 | en | en.dat地址 | 3 | 6 | |
| 3 | er | er.dat地址 | 3 | 12 | |
| | | | | | |
| e.index索引(部分) ||||||
说明:
1>++音节++这一项用于输入字符的检索,比如输入ei,再用指针指向ei.dat(未描述),去里面找数据.
2>++汉字个数++ 和++指针偏移++ 始终是2倍关系,这里使用指针偏移,表示从e.index表开始查找
3>上表中的++地址++项,假设为内存中的绝对地址,操作系统加载后即可使用,无需从硬盘传入数据(简化理解)
第二层索引表
与此同时,e.index的索引部分(上表)也需要做存储,遇到前面同样的问题.解决方法相同,和其他的索引表合成一个文件,假设叫做pinyin.index,设计如下:
|----|-----|----------|------|----|-----|----------|------|
| 序号 | 音节 | 地址 | 指针偏移 | 序号 | 音节 | 地址 | 指针偏移 |
| 1 | a | a.dat地址 | 0 | 5 | ei | ei.dat地址 | 0 |
| 2 | ai | ai.dat地址 | n | 6 | en | en.dat地址 | 6 |
| 3 | an | an.dat地址 | p | 7 | er | er.dat地址 | 12 |
| 4 | ... | ... | ... | 8 | ... | ... | ... |
| | | | | | | | |
| pinyin.index(表) ||||||||
第三层索引表
再用一张声母表(shengmu.index)来检索每张索引表的地址,如图
|----|----|-----------|-----|-----|-----------|
| 序号 | 声母 | 地址 | 序号 | 声母 | 地址 |
| 1 | a | a.index地址 | 5 | e | e.index地址 |
| 2 | b | b.index地址 | ... | ... | ... |
| 3 | c | c.index地址 | ... | ... | ... |
| 4 | d | d.index地址 | 26 | z | z.index地址 |
| | | | | | |
| shengmu.index(表) ||||||
说明:设计时有些内容没有太重视,例如e.index,e开头的有额,鹅等汉字没表达.还有声母表不需要26项,如u,i开头的没必要做在表中,方便理解忽略掉部分细节.
至此汉字库的结构完毕,上述后缀为(表)的留下,总共有3张表,加若干数据文件(.dat)
算法
增加删除略,主要看如何查找,即输入音节的同时如何找到汉字
当输入一个音节,如en时,现在第三层索引表中找到e.index的地址,然后在pinyin.index中查找到en.dat的地址---这时找到所有发音为en的汉字,指针偏移量6,把e.index的地址加上6,获得一个数字80,此时找到第一个汉字嗯,他不是要找的字,但没有关系,可以把后面的一串汉字都取出来.选择"恩",也可以记录下来,这需要前端的一些设计,不展开.
输入法浅析
把字库的存储和查找设计好以后,即可以设计输入法了.虽然现在弄一个输入法出来或许不会有很大价值,比如win10的微软输入法,某狗输入法都很是很成熟的产品,但在思考过程中会对数据的组织,数据类型的设计有不错的学习效果.笔者将在另一专栏对输入法的前端做一些分析
小结
从字库设计上,对数据的访问的几个要素:指针,数据单元(简单数据)的访问,数据所占总空间的实现
预告
输入法前端,词语输入,记忆输入
adv:内容细致,条理清晰,循序渐进,敬请关注笔者数据类型设计专栏,有更多精彩内容呈现.