<<C语言接口与实现>> 第三章 原子
2014-04-04 00:00
381 查看
摘要: 课后题的自我解答
十舍七匹狼于~ http://www.oschina.net/
3.1
使用2048作为hash桶的数目: 链表最长的有5个元素,最少的1个元素,
绝大部分是在1-3个元素之间, 耗费的时间是[totaltime = 0.006123s];
使用素数2039作为hash桶的数目,几乎没有出现在某个链表有长达5个元
素的情况,绝大部分是2-3个元素,可见分布更平均,并且耗时更少了
[totaltime = 0.000001s].不过,我后来再次测试了,发现即使使用
2048作为hash桶的数目,耗时也是[totaltime = 0.000001s]。不过
hash表分布的更平均却是不争的事实。最后,只有x86的机器,所以无法
判断机器的关联性。即使有不同机器我也尚不知道从何处判断关联性的大小。
测算时间使用了gettimeofday函数, 测试hash表的分布情况, 使用自写
的Atom_read函数遍历hash表, 然后写入文件, 没具体统计数字,肉眼观察了下。
主程序3_1.c
其中getOneword函数使用的是书中提供的函数,在getOneword.c中
其中Atom_read函数如下:
3.2
对hash的理论了解很少,只关注了实际使用。如果想寻找多种多样的hash函数,
业界著名的可参考下面的URL:
http://www.partow.net/programming/hashfunctions/index.html
在Libevent中,它的hash函数很简单,只是将一个struct event 的地址单纯的
右移6位得到hash码。可见,还是应该根据自身的需要编写,不过作为我来说,
一般会选择高德纳的。
3.3
使用strncmp有个显著缺陷,那就是遇到'\0'就会终止比较。比如有2个字符序列:
str1="abcd\0g" 和 str2="abcd\0m", 那么使用strncmp函数就会就会认为str1和
str2相等,这不可接受。书中使用逐个字符比较,当然也可以使用memcmp进行比较。
但这里对于书中的写法,仍然有个现实的考量,就是它会在拷贝完毕字符串之后显
式的缀上'\0', 作者明显是为了取字符串方便, 不过这由应用程序调用时,是会产
生二义性的。
3.4
本题不明白。据说这种 char str[1] 的写法很hack.
3.5
写上hash码对于特定操作是有明显的好处: 当要扩展这个hash表时,不需要
再做一次hash了,直接将每个节点的hash码模上新的hash桶的数目就得到了
在新hash表中所在的链表。这样做不仅节省了时间,还带来了一个额外的好
处,就是在旧表中同一个表项的节点,在新表中仍然在一起,因为它们再旧
表中的hash码是相同的,模上新的hash桶当然也相同了。这样在搜索表项时,
旧有代码照用不误。
3.6
Atom_length函数之所以会慢,根本原因在于,最坏情况下它要遍历所有存在
于hash表中的节点。一个简单的解决方法是,稍微复杂化原子的数据结构,
即添加使用上述的hash码。有了hash码就可以直接定位到特定的某个链表。
那么对atom的改造变为
并且也要改造Atom_new函数,它不能再返回p->str, 因为这不够用了,
它要返回p这个atom结构体指针。
3.7
extern void Atom_init(int hint); 实现这个函数的现实意义暂时还没发现。
3.8
代码见下面,其中四个字符串是在做 3.1 题时找到的具有相同hash码的两个字符串:
3_8.c
其中 Atom_list Atom_free Atom_reset 函数见下面:
3.9
见下述代码,主要使用了可变参数列表。
3_9.c
Atom_vload Atom_aload 函数见下面:
3.10
检查 const char *Atom_add(const char *str, int len) 参数中的str不为 NULL.
最后列出添加了新函数的atom.c文件,其中没有使用后面内存管理章节的FREE和ALLOC函数,使用的是标准库函数,目的是编译方便,现在编译时只需要:
十舍七匹狼于~ http://www.oschina.net/
3.1
使用2048作为hash桶的数目: 链表最长的有5个元素,最少的1个元素,
绝大部分是在1-3个元素之间, 耗费的时间是[totaltime = 0.006123s];
使用素数2039作为hash桶的数目,几乎没有出现在某个链表有长达5个元
素的情况,绝大部分是2-3个元素,可见分布更平均,并且耗时更少了
[totaltime = 0.000001s].不过,我后来再次测试了,发现即使使用
2048作为hash桶的数目,耗时也是[totaltime = 0.000001s]。不过
hash表分布的更平均却是不争的事实。最后,只有x86的机器,所以无法
判断机器的关联性。即使有不同机器我也尚不知道从何处判断关联性的大小。
测算时间使用了gettimeofday函数, 测试hash表的分布情况, 使用自写
的Atom_read函数遍历hash表, 然后写入文件, 没具体统计数字,肉眼观察了下。
主程序3_1.c
#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "assert.h" #include "atom.h" #include "getOneword.h" int main(int argc, char const *argv[]) { char buf[1024]; int size; FILE *fp; int num; struct timeval tv_start, tv_end; double timeuse, totaltime = 0; size = sizeof(buf); fp = fopen("test_3_1.txt", "rb"); assert(fp); for (num = 0; num < 10000; ++num) { if (getOneword(fp, buf, size, first, rest) == 0) break; gettimeofday(&tv_start, NULL); Atom_new(buf, strlen(buf)); gettimeofday(&tv_end, NULL); timeuse = (tv_end.tv_sec - tv_start.tv_sec) * 1000000 + (tv_end.tv_usec - tv_start.tv_usec); timeuse /= 1000000; totaltime += timeuse; } printf("[totaltime = %fs]\n", totaltime); fclose(fp); Atom_read(); return 0; }
其中getOneword函数使用的是书中提供的函数,在getOneword.c中
#include <stdio.h> #include <ctype.h> int getOneword(FILE *fp, char *buf, int size, int first(int c), int rest(int c)) { int i = 0; int c; c = getc(fp); for ( ; c != EOF; c = getc(fp)) if (first(c)) { if (i < size - 1) buf[i++] = c; c = getc(fp); break; } for ( ; c != EOF && rest(c); c = getc(fp)) if (i < size - 1) buf[i++] = c; if (i < size) buf[i] = '\0'; else buf[size - 1] = '\0'; if (c != EOF) ungetc(c, fp); return i > 0; } int first(int c) { return isalpha(c); } int rest(int c) { return isalpha(c) || c == '_'; }
其中Atom_read函数如下:
int Atom_read() { int i; struct atom *p; FILE *fp; fp = fopen("result.txt", "wb"); assert(fp); for (i = 0; i < NELEMS(buckets); i++) if (buckets[i] != NULL) { for (p = buckets[i]; p; p = p->link) fprintf(fp, "buckets[%d]: [%s]\n", i, p->str); fprintf(fp, "\n\n\n"); } fclose(fp); return i > 0; }
3.2
对hash的理论了解很少,只关注了实际使用。如果想寻找多种多样的hash函数,
业界著名的可参考下面的URL:
http://www.partow.net/programming/hashfunctions/index.html
在Libevent中,它的hash函数很简单,只是将一个struct event 的地址单纯的
右移6位得到hash码。可见,还是应该根据自身的需要编写,不过作为我来说,
一般会选择高德纳的。
3.3
使用strncmp有个显著缺陷,那就是遇到'\0'就会终止比较。比如有2个字符序列:
str1="abcd\0g" 和 str2="abcd\0m", 那么使用strncmp函数就会就会认为str1和
str2相等,这不可接受。书中使用逐个字符比较,当然也可以使用memcmp进行比较。
但这里对于书中的写法,仍然有个现实的考量,就是它会在拷贝完毕字符串之后显
式的缀上'\0', 作者明显是为了取字符串方便, 不过这由应用程序调用时,是会产
生二义性的。
3.4
本题不明白。据说这种 char str[1] 的写法很hack.
3.5
写上hash码对于特定操作是有明显的好处: 当要扩展这个hash表时,不需要
再做一次hash了,直接将每个节点的hash码模上新的hash桶的数目就得到了
在新hash表中所在的链表。这样做不仅节省了时间,还带来了一个额外的好
处,就是在旧表中同一个表项的节点,在新表中仍然在一起,因为它们再旧
表中的hash码是相同的,模上新的hash桶当然也相同了。这样在搜索表项时,
旧有代码照用不误。
3.6
Atom_length函数之所以会慢,根本原因在于,最坏情况下它要遍历所有存在
于hash表中的节点。一个简单的解决方法是,稍微复杂化原子的数据结构,
即添加使用上述的hash码。有了hash码就可以直接定位到特定的某个链表。
那么对atom的改造变为
static struct atom { struct atom *link; unsigned long atom_hash; int len; char *str; } *buckets[2048];
并且也要改造Atom_new函数,它不能再返回p->str, 因为这不够用了,
它要返回p这个atom结构体指针。
3.7
extern void Atom_init(int hint); 实现这个函数的现实意义暂时还没发现。
3.8
代码见下面,其中四个字符串是在做 3.1 题时找到的具有相同hash码的两个字符串:
3_8.c
#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "assert.h" #include "atom.h" int main(int argc, char const *argv[]) { char *s1 = "rebuttal"; char *s2 = "handler"; char *s3 = "hpl"; char *s4 = "Zero"; const char *p1 = Atom_new(s1, strlen(s1)); const char *p2 = Atom_new(s2, strlen(s2)); const char *p3 = Atom_new(s3, strlen(s3)); const char *p4 = Atom_new(s4, strlen(s4)); Atom_list(); Atom_free(p1); Atom_free(p3); // Atom_reset(); Atom_list(); return 0; }
其中 Atom_list Atom_free Atom_reset 函数见下面:
void Atom_free(const char *str) { struct atom *p, *prev; int i; assert(str); for (i = 0; i < NELEMS(buckets); i++) for (p = buckets[i]; p; p = p->link) { if (p->str == str) { printf("to free: [%s]\n", str); if (p == buckets[i]) buckets[i] = p->link; else prev->link = p->link; free(p); // p = NULL; } prev = p; } } void Atom_list() { struct atom *p; int i; for (i = 0; i < NELEMS(buckets); i++) for (p = buckets[i]; p; p = p->link) printf("%d: [%s]\n", i, p->str); } void Atom_reset(void) { struct atom *p; int i; for (i = 0; i < NELEMS(buckets); i++) for (p = buckets[i]; p;) { buckets[i] = p->link; free(p); p = buckets[i]; } }
3.9
见下述代码,主要使用了可变参数列表。
3_9.c
#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "assert.h" #include "atom.h" int main(int argc, char const *argv[]) { char *s1 = "rebuttal"; char *s2 = "handler"; char *s3 = "hpl"; char *s4 = "Zero"; printf("Atom_vload:\n"); Atom_vload(s1, s2, s3, s4, NULL); Atom_list(); Atom_reset(); printf("\n\nAtom_aload:\n"); const char *strs[] = {"rebuttal", "handler", "hpl", "Zero", NULL}; Atom_aload(strs); Atom_list(); Atom_reset(); return 0; }
Atom_vload Atom_aload 函数见下面:
void Atom_vload(const char *str, ...) { va_list ap; va_start(ap, str); for (; str; str = va_arg(ap, const char *)) Atom_new(str, strlen(str)); va_end(ap); } void Atom_aload(const char *strs[]) { int i; const char *p; for (i = 0, p = strs[i]; p; p = strs[++i]) Atom_new(p, strlen(p)); }
3.10
检查 const char *Atom_add(const char *str, int len) 参数中的str不为 NULL.
最后列出添加了新函数的atom.c文件,其中没有使用后面内存管理章节的FREE和ALLOC函数,使用的是标准库函数,目的是编译方便,现在编译时只需要:
gcc -g 3_1.c atom.c getword.c
相关文章推荐
- <<C语言接口与实现>> 第五章 内存管理
- Bean都初始化完成后,实现ApplicationListener<ContextRefreshedEvent>接口
- 对象的比较与排序(三):实现IComparable<T>和IComparer<T>泛型接口
- 汗一下,.Net的单维数组自动实现IList<T>接口
- 循环队列及C语言实现<一>
- C语言接口与实现创建可重用软件的技术读书笔记(3)---原子
- (转)实现这两个接口ModelDriven<T>,Preparable有什么用?
- <asp.net3.5商用开发架构精解>不实现接口成员
- C语言实现<读取>和<写入> *.ini文件。
- 接口List<E>常用实现类分析
- 循环队列及C语言实现<二>
- [翻译]List<T>为什么实现了那么多接口?
- C# List<> 实现 IComparer 接口 排序
- myBatis 实现用户表增删查改操作<方法1 没有使用接口的>(最终版)
- 父类实现IComparable<T>接口,子类无法使用~
- <数据结构>单链表的C语言实现
- 实现这两个接口ModelDriven<T>,Preparable有什么用?
- List<>根据指定属性排序(实现IComparer接口)
- windows下C语言实现<读取>和<写入> *.ini文件。
- <Win32_15>用纯C语言来实现WP8中磁贴动态翻转的功能