游戏人生

颂山川,吟古道,偏失半点笔墨;言世事,问苍生,更差一声长嗟。

Lua 脚本中使用了大量字符串,这些字符串通常用于格式化输出到客户端进行显示。在前
期开发过程当中,为了快速开发的需要,这个问题往往被搁置起来。

在面临本地化需求时,我们的目标是使用尽量少的代价使本地化更加容易。

针对 Lua 脚本中使用的字符串,使用一个名为 messagestable 将所有字符串以
下面的形式进行存储:

messages = {
    hello = "你好",
    world = "中国",
}

在使用时,需要对原来的字符串进行替换:

print('hello' .. ", " .. 'world')

替换为:

print(L('hello') .. ", " .. L('world'))

在指定语言为 “zh_CN” 时,该语句将输出:

你好, 中国

上述功能借助一个简单的模块 i18n 完成,该模块对外提供可配置项如下:

local locale = {
    default_lang = "en_US",     -- default language, READ ONLY
    dir = "locale",             -- parent directory of locale resources
    lang = "",                  -- locale language
    pkg = {},                   -- table contains locale message strings
}

这段小程序 放在了 github 上。

Author: Fox (yulefox at gmail.com)
Site: http://www.yulefox.com
Date: Aug. 13, 2011

Contents

INTRODUCTION

之前没有把 Paul Graham 的 <黑客与画家> 一书读完, 上周就从同事那里把书带回家, 也一直没读, 到这周才有时间读完. 很久没有更新了 (一看时间, 整整 5 个月), 顺便把这篇写了几个月的感想放出来.

前几章的一点看法

这本书前面 8 章讲述的内容, 大多是我并不太感兴趣的, 比如财富, 比如创业. 不是因为它们本身怎么样, 而是因为我一直认为, 它们是没有办法复制的, 我宁可依循我自己的原则. 其中 Graham 的一些观点我比较不能赞同, 有一些我认为可以拿出来讨论.

Graham 用一种颠覆性的观点表达了他对计算机科学的看法. 不得不说, 我并不能完全同意他把计算理论对黑客的重要性等同于颜料化学成分对画家的重要性. 他甚至认为, 黑客新想法的最佳来源, 并非那些名字里有计算机三个字的理论领域, 而是来自于其他创作领域. 与其到计算理论领域寻找创意, 你还不如在绘画中寻找创意.

我无意带着恶意去揣测这与他攻读人工智能博士学位时遭受的挫折是否有关, 显然, 那应该不是一段愉快的记忆, 但这并没有妨碍他成为一名优秀的黑客. 只是, 我们无法也不应该告诉那些试图成为黑客的学生们, 计算机理论基础是多么的无足轻重. 如果需要做一些说明, 我相信大家对黑客的定义或许是有区别的.

我赞同 Graham 所讲的如果你不爱一件事, 你不可能把它做得真正优秀, 要是你很热爱编程, 你就不可避免地会开发你自己的项目. 我也强烈反对多人共同开发同一模块, 说的更直白一点, 我不喜欢别人弄脏我的设计.

从第 9 章开始, Graham 阐述和设计编程相关的内容, 这里就摘抄一些我认为比较重要的 (其实我只是把第 9 章看的比较细致, 其它章节基本都是扫过…).

什么是好的设计

  • 好设计是简单的设计

    少即是多. 当你被迫把东西做得很简单时, 你就被迫直接面对真正的问题, 当你不能用表面的装饰交差时, 你就不得不做好真正的本质部分.

  • 好设计是永不过时的设计

    数学家哈代说: 丑陋的数学在世界上无法生存. 飞机设计师凯利.约翰逊说: 如果解决方法是丑陋的, 那就肯定还有更好的解决方法, 只是还没有发现而已. 显然, 这些经典的观点也同样适用于编码设计.

  • 好设计是解决主要问题的设计

  • 好设计是启发性的设计

    其实, 任何设计是都启发性的, 只是坏设计会成为项目中的 "破窗户".

  • 好设计通常是有点趣味性的设计

    好的设计并非一定要有趣, 但是很难想像完全无趣的设计会是好的设计.

  • 好设计是艰苦的设计

  • 好设计是看似容易的设计

    只见贼吃肉, 没见贼挨打. 结合这句话, 你就很容易理解上面两条的含义了.

  • 好设计是对称的设计

    这条要看怎么理解了. 对称的危险在于它可以用来取代思考, 在大量使用重复的时候这种危险性更大. 只有知道什么是对称, 为什么需要对称才能做到好的对称.

  • 好设计是模仿大自然的设计

  • 好设计是一种再设计

    很少有人一次就把事情做对. 犯错误是很正常的事情. 扔掉早期原型是需要信心的.

  • 好设计是能够复制的设计

  • 好设计常常是奇特的设计

    唯一达到奇特的方法, 就是追求做到好作品, 完成之后再回过头看.

  • 好设计是成批出现的

    这是一个残酷的现实, 因为从历史来看, 大师的时代就是大师的时代. 不管你信不信, 反正我是信了.

  • 好设计常常是大胆的设计

    优秀作品的秘诀就是: 非常严格的品味, 再加上实现这种品味的能力.

我关于写代码的一些琐碎的看法

最后, 我想插一段自己关于写代码的一些想法. 之前我曾试图分析自己现在为什么会对代码的美感如此纠结, 那是无关乎代码运行的.

我有一个与自己要求完美的内心并不一致的方面, 我往往只是提出自己的要求和看法, 但并不会强制要求别人. 我觉得每个人有他自己的自由和尊严, 尤其是聪明的程序员. 但这与项目管理的要求或许是相悖的, 或许是因为自己比较希望自己的信念得到别人的尊重, 我会尽量尊重别人的观点, 至少表面上是这样.

我写代码的时候容易分心. 往往会因为环境不称心, 而分心去改善, 如果碰壁, 会挫伤我的积极性. 举个例子, 为了阅读代码, 我一定会努力寻求一个自己觉得舒服的编辑器, 于是我会在 Emacs 和 Vim 之间徘徊很久; 在发现 Vim 有很多强大的命令时, 我一定试图掌握更多的命令, 直到自己满意为止; 当我再次用 Vim 打开代码后, 我发现我虽然不需要一个强大的 IDE, 但我至少需要 tags, 在遇到不能跳转的 tag 时, 我会变得很烦躁, 进而无所适从.

在编码和其他一些事情上, 我不喜欢将就, 我希望能够尽我所能, 能够把一件事做好. 而且会为了达到这样的目标, 不惜花费掉很多在他人看来不必要的时间.

写代码的时候, 我不会满足于手动编写简单的 makefile, 我希望自己写出的 makefile 看上去足够 "专业", 于是会转去了解 makefile 规则; 然后会尝试 autotools…

这些毛病的一个直接表现是, 我为了解决过程中遇到的一点不爽, 强迫自己去 fix 它, 结果招致更多的不爽, 然后不断的去 fix 它们. 后果就是, 当我再回头的时候, 发现时间已经过去了很久, 我的精力大量投入其中, 耽搁了本来的工作, 虽然时间不能说是浪费, 但效果却并不是很好.

如果说有时候的付出是值得的, 但另一些时候的做法则有 "睡不着觉怨床歪" 的嫌疑. 比如, 比较烦躁的时候, 会觉得鼠标不给力, 耳机/音箱不给力, 显示器不给力… 转去考虑如何让这些莫名其妙的需求被满足.

其实, 回头想想, 这些东西不但没有让我感到羞愧, 而且得到了我内心的纵容, 至少在目前这段时间里, 我的思想就是这个样子, 也决定了我如何去做.

Author: Fox (yulefox at gmail.com)
Site: http://www.yulefox.com
Date: Mar. 14, 2011

Contents

INTRODUCTION

shell 脚本虽然不是非常复杂的程序, 但对于首次接触的我来讲, 多少还是有些忌惮. 不过, 接触任何新事物都需要勇敢面对, 逐步树立信心. 我是冲着把脚本写好去的, 所以, 我的目标是能够写出友好, 健壮, 优美的脚本.

为此, 我希望用 shell 实现一些日常中琐碎的工作, 比如, 应用部署, 数据备份, 用户管理 等, 现在把这些内容放在 Google code 上.

好的编写习惯

shell 脚本总是以 #!/bin/sh 作为第一行, 以通知 shell 使用系统中的 shell 解释器. 脚本第二行注释写上脚本名称是一个好习惯.

要想了解地道的 shell 是什么写法, 最好的方式是找系统或者各种手册中现成的脚本代码.

将脚本目录加到 PATH

为了以后能更加有效的管理和使用脚本, 可以在 home 目录下创建一个 bin 目录以存放常用脚本 (一般以符号链接的形式出现), 并将其添加到 path 中 ($HOME/.profile). 如此一来, 就可以像使用其它系统命令一样直接使用自己的脚本, 而无需加上脚本路径.

path=$path:$HOME/bin

为了使上述改动立即生效, 需要执行下面的命令:

. $HOME/.profile

如何取得执行文件的绝对路径

由于脚本运行目录由用户指定, 有时需要获取执行脚本的绝对路径 (如访问脚本所在目录的其它文件). 由于脚本通常使用符号链接, 因此需要对文件类型加以判断:

if [ -L $0 ] ; then
    FILE=`readlink $0`
    DIR=`dirname $FILE`
elif [ -f $0 ] ; then
    cd `dirname $0`
    DIR=$PWD
else
    echo "[ERR] Unknown file type: $0"
    exit 1
fi

在 dash 中如何进行字符串替换

写脚本的过程中, 用到 bash 自带的 字符串替换功能, 大意是将域名中的 . 替换为 _, 运行时提示 "Bad substitution". 脚本示例如下:

OUTPUT=${$INPUT//\./_}

自 Ubuntu 6.10 开始, 默认的系统 shell sh(1) 被替换为 dash (the Debian Almquist Shell), 取代了 bash (the GNU Bourne-Again Shell).

dash 没有提供自带的字符串替换功能, 只能借助外部 echo, sed 等命令实现:

OUTPUT=`echo $INIPUT | sed 's/\./_/g'`

将 rst 格式文档转换为 blog 可用的 html 代码

docutilsrst2html 可将 rst 格式文档转换为 html 代码, 因为夹杂了大量的 css 等信息, 以及部分细节与我 blog 编写
习惯并不完全吻合, 每次需要再手动整理一下, 这次用 shell 处理了一下, 也算为以后将 rst 格式文档直接生成到 Trac 中做个预研, 以下为全部代码:

# Generate html file name based on rst file.
FILE_RST=$1
FILE_HTML=`echo $FILE_RST | sed 's/\.rst$//g'`".html"
eval rst2html $FILE_RST $FILE_HTML

# Remove redundant meta, css, tag code.
CUR_LN=1
TITLE_LN=`sed -n '/<title>/'= $FILE_HTML`
sed -i "$CUR_LN,`expr $TITLE_LN - 1`"d $FILE_HTML

CUR_LN=`expr $CUR_LN + 1`
HEAD_LN=`sed -n '/<h1 class="title"/'= $FILE_HTML`
sed -i "$CUR_LN, $HEAD_LN"d $FILE_HTML

LAST_LN=`sed -n "$"= $FILE_HTML`
sed -i "`expr $LAST_LN - 2`,$LAST_LN"d $FILE_HTML

sed -i "/<\/\?div/"d $FILE_HTML

# Update individual tags.
sed -i 's/ class="[^"]*"//g' $FILE_HTML
sed -i 's/ class="[^"]*"//g' $FILE_HTML
sed -i 's/\(href="http.*"\)/\1 target="_blank"/g' $FILE_HTML
sed -i 's/h4>/h5>/g' $FILE_HTML
sed -i 's/h3>/h5>/g' $FILE_HTML
sed -i 's/h2>/h4>/g' $FILE_HTML
sed -i 's/h1>/h3>/g' $FILE_HTML
sed -i 's/<pre/<pre class="brush:bash"/g' $FILE_HTML

Author: Fox (yulefox at gmail.com)
Site: http://www.yulefox.com
Date: Feb. 22, 2011

Introduction

MySQL 提供多种存储引擎对数据表进行处理, MySQL 5.1 (如无特殊说明, 本文所使用版本均为 5.1) 引入了新的插件式存储引擎体系结构, 对于编程人员而言, 插拔及修改存储引擎都是极其方便的. 这些引擎包括处理事务安全表的引擎和处理非事务安全表的引擎:

  • MyISAM 管理非事务表. 提供高速存储和检索, 以及全文搜索能力. 主要用于 Web, 数据仓库等应用环境. 是 5.5 之前的默认存储引擎 (可以通过配置修改).
  • InnoDB 提供事务安全表 (ACID 兼容). 支持 commit, rollback, crash-recovery.
  • BDB 提供事务安全表.
  • Memory 将所有数据存储在 RAM 中以提高存取速度, 其正式称谓为 HEAP 引擎.
  • Merge 管理非事务表. 允许 DBA 或开发者将一组逻辑相关的 MyISAM 表视为一张单独的表.
  • Archive 存储及索引大批量存档数据.
  • Federated 联合多台物理数据库服务器, 组合成一个逻辑数据库.
  • NDBCLUSTER (NDB) 集群数据库存储引擎.
  • CSV 把数据以逗号分隔的格式存储在文本文件中. 用于应用程序间导入/导出 CSV 格式数据.
  • Blackhole 接受但不存储数据, 并且检索总是返回一个空集. 可用于分布式数据库设计 (数据自动复制, 不在本地存储).
  • Example "存根 (stub)" 引擎, 不做什么事. 可以用它创建表, 但不能用于存储或检索数据. 用于向开发者展示如何编写 MySQL 存储引擎.
Feature MyISAM Memory InnoDB Archive NDB
Storage limits 256TB RAM 64TB None 384EB
Transactions No No Yes No Yes
Locking granularity Table Table Row Row Row
MVCC No No Yes No No
Geospatial data type support Yes No Yes Yes Yes
Geospatial indexing support Yes No No No No
B-tree indexes Yes Yes Yes No Yes
Hash indexes No Yes No No Yes
Full-text search indexes Yes No No No No
Clustered indexes No No Yes No No
Data caches No N/A Yes No Yes
Index caches Yes N/A Yes No Yes
Compressed data Yes No Yes Yes No
Encrypted data Yes Yes Yes Yes Yes
Cluster database support No No No No Yes
Replication support Yes Yes Yes Yes Yes
Foreign key support No No Yes No No
Backup / point-in-time recovery Yes Yes Yes Yes Yes
Query cache support Yes Yes Yes Yes Yes
Update statistics for data dictionary Yes Yes Yes Yes Yes

一般应用程序中常用的存储引擎主要是 MyISAM 与 InnoDB 两种.

MyISAM

MyISAM 是 MySQL 5.5 之前 (不含 5.5) 的默认存储引擎, MyISAM 的最大缺陷在于不支持事务处理 (transaction).

每个 MyISAM 表在磁盘上存储成三个文件 (如 /var/lib/mysql/db 下). 文件名即为表名, 扩展名指出文件类型. 表格式文件的扩展名为 .frm; 数据文件的扩展名为.MYD (MYData); 索引文件的扩展名是.MYI (MYIndex).

InnoDB

从 MySQL 5.5 开始, InnoDB 成为 MySQL 的默认存储引擎.

InnoDB 为 MySQL 提供了具有提交, 回滚和崩溃恢复能力的事务安全 (ACID兼容) 存储引擎. InnoDB 设计目的是为处理巨大数据量时发挥最大性能. 它的CPU效率可能是任何其它基于磁盘的关系数据库引擎所不能匹敌的.

InnoDB 存储引擎被完全与 MySQL 服务器整合, InnoDB 存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池. InnoDB 将其表 & 索引存储在一个表空间中, 表空间可以包含数个文件 (或原始磁盘分区), 这与 MyISAM 表中每个表被存在分离的文件中有所不同. InnoDB 表可以是任何尺寸, 即使在文件尺寸被限制为 2GB 的操作系统上.

InnoDB 被用在众多需要高性能的大型数据库站点上.

常用命令

创建表时指定存储引擎

mysql> CREATE TABLE t (i INT) ENGINE = MYISAM;

修改表的存储引擎

mysql> ALTER TABLE t ENGINE = MYISAM;

显示存储引擎状态信息

mysql> SHOW ENGINES;

+------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine     | Support | Comment                                                        | Transactions | XA   | Savepoints |
+------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB     | YES     | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE  | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| CSV        | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| MEMORY     | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| FEDERATED  | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| ARCHIVE    | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| MyISAM     | DEFAULT | Default engine as of MySQL 3.23 with great performance         | NO           | NO   | NO         |
+------------+---------+----------------------------------------------------------------+--------------+------+------------+
8 rows in set (0.00 sec)

显示当前默认存储引擎

mysql> SHOW VARIABLES LIKE '%storage_engine%';
+----------------+--------+
| Variable_name  | Value  |
+----------------+--------+
| storage_engine | MyISAM |
+----------------+--------+
1 row in set (0.00 sec)

显示当前默认存储引擎

$ mysqlshow -u<user> -p<password> --status <database>
mysql> SHOW TABLE STATUS FROM trac;

数据库备份与恢复

MyISAM 表保存为文件方式, 很容易备份. 为保持备份一致性, 对相关表执行 LOCK TABLES 操作进行读锁定 (复制数据库目录中的文件时, 允许其它客户继续查询表), 然后对表执行 FLUSH TABLES. 你只需要读锁定; 这样当你复制数据库目录中的文件时, 允许其它客户继续查询表. 需要 FLUSH TABLES 语句来确保开始备份前将所有激活的索引页写入硬盘.

只要服务器不再进行更新, 还可以只复制所有表文件(.frm、.MYD和*.MYI文件). mysqlhotcopy 脚本使用该方法. (但请注意如果数据库包含 InnoDB 表, 这些方法不工作. InnoDB 不将表的内容保存到数据库目录中, mysqlhotcopy 只适合 MyISAM 表).

如果你在服务器上进行备份, 并且表均为MyISAM表, 应考虑使用mysqlhotcopy, 因为可以更快地进行备份和恢复:

$ mysqlhotcopy db_name [/path/to/new_directory]
$ mysqlhotcopy db_name_1 ... db_name_n /path/to/new_directory
$ mysqlhotcopy db_name./regex/

对于 InnoDB 表, 可以进行在线备份, 不需要对表进行锁定:

$ mysqldump [options] db_name [tables]
$ mysqldump [options] ---database DB1 [DB2 DB3...]
$ mysqldump [options] --all--database

参考

PS

搜集相关资料的过程中, 我在反思: 与维基百科相比, 百度百科相差太多, 单从编辑者的素质来看就是良莠不齐, 绝大多数编辑者停留在复制, 粘贴层面上, 而且是不负责任的复制, 粘贴. 这与百度功利的经验值贡献系统有关. 恶的制度滋生恶的行为, 导致恶的结果.

Author: Fox (yulefox at gmail.com)
Date: 2011-01-04

引言

2010年读了一些书, 下面的数据都来自豆瓣, 有些书没有读完但短期内没有计划再读也标记为 读过, 而另一些书搁置了很久但希望继续读下去则标记为 在读.

读过

  • Unix 技术手册(第四版 中文版)

    • 作者: Arnold Robbins / 东南大学出版社 / 2009年11月1日 / 98

    • 标签: 操作系统 工程

      虽然有 man, 有 info, 有这么一本放在手边的工具书, 还不错吧.

  • UNIX环境高级编程-(第2版)

    • 作者: W.Richard Stevens / 尤晋元 / 人民邮电出版社 / 2006-05-01 / 99.0

    • 标签: 编程 操作系统 经典

      令人景仰的经典之作, 一直在读, 没有读完. 虽然当工具书在用, 但真的是很好的教科书.

  • GNU Autoconf, Automake, and Libtool

    • 作者: Gary V. Vaughn / Sams / 2000-10-16 / USD 40.00

    • 标签: GNU 编程 工程

      让我看到了继续学习 autotools 的希望, 很多内容是读autotools相关manuals无法达到的. 至少我磨蹭了这么久, 这本书让我对 autotools 的掌握达到了实际使用的需要, 所以我要感谢它. 介于工具书和教材之前吧.

  • 精通正则表达式:第3版

    • 作者: (美)Jeffrey E.F.Friedl / 余晟 / 电子工业出版社 / 2007 / 75

    • 标签: 正则表达式 编程

      网上评价很高, 个人感觉, 正则表达式只是个工具而已, 并不值得花费太多时间学习, 而应该是 使用.

  • PYTHON技术手册(影印版·第二版)

    • 作者: 马特利 / 东南大学出版社 / 2006-11-01 / 78.0

    • 标签: 编程 Python 网络

      经典之作. 虽然不算是入门书籍, 但对于有一定编程经验的人来讲并不复杂. 读了前面几章, 剩下的当工具书或者闲来翻阅了.

  • 《Python核心编程》(中文第二版)

    • 作者: [美]Wesley J. Chun(陳仲才) / CPUG / 人民邮电出版社 / 2008-06 / 89.0

    • 标签: 编程 Python 网络

      不好.

  • 学习Python(第二版,影印版)

    • 作者: Mark Lutz / 东南大学出版社 / 2005年6月 / 68

    • 标签: 编程 Python 网络

      不错的入门书, 但感觉不如官方文档查起来方便.

  • Dive Into Python

    • 作者: Mark Pilgrim / CreateSpace / 2009-03-20 / USD 34.95

    • 标签: 编程 python

      很多人对这本书评价比较低, 甚至有人诅咒到 非死不可, 我觉得从我当初接触 Python 的角度看, 还好吧.

  • PHP与MySQL程序设计(第3版)/图灵程序设计丛书

    • 作者: W. Jason Gilmore / 朱涛江等 / 人民邮电出版社 / 2009.6 / 89

    • 标签: PHP Web开发

      刚开始想了解 Web 开发的时候买的, 因为后来对后台的兴趣转向python. 就没有怎么看, 但感觉体例还不错.

  • SQL 必知必会(第3版)

    • 作者: 福塔 / 钟鸣 / 人民邮电出版社 / 2007-07-01 / 29.0

    • 标签: sql 数据库

      很适合作入门的教程读, 简单易读. 当然, 也仅适合入门读.

  • MySQL必知必会

    • 作者: (英)福塔 著,刘晓霞,钟鸣 译 / 刘晓霞 / 人民邮电出版社 / 2009-01-01 / 39

    • 标签: 数据库 mySql

      对于第一次接触 mysql 甚至第一次接触 sql 的人来说都是浅显易懂的, 和 SQL必知必会 从内容到质量都差不多, 因为是同一个作者.

  • MySQL Crash Course

    • 作者: Ben Forta / Sams / 2005-12-22 / USD 29.99

    • 标签: 数据库

      读的是英文电子版, 因为讲的内容结合实践非常易懂, 读起来并不吃力. 觉得读起来很舒服, 又从书店买了一本中文版, 权当收藏和支持作者.

  • 高性能网站建设指南

    • 作者: Steve Souders / 刘彦博 / 电子工业出版社 / 2008年 / 35

    • 标签: 网络 Web

      经典前端, 还没有全部读完, 总觉得我的前端经验还不够, 读起来虽然不费劲, 但效果并不明显.

  • 高性能网站建设进阶指南

    • 作者: Steve Souders / 口碑网前端团队 / 电子工业出版社 / 2010年4月 / 49.8

    • 标签: web开发

      经典前端改进的指南, 所有做前端的人应该都读过吧.

  • 实战Nginx:取代Apache的高性能Web服务器

    • 作者: 张宴 / 电子工业出版社 / 2010年2月 / 55

    • 标签: nginx Web开发

      并不是一本很实用的书, 感觉和作者从事领域有一定关系, 作者既没有写成一本手册,也没有写成一本教材.

  • HTML5

    • 作者: Mark Pilgrim / O’Reilly Media / 2010-8-17 / USD 29.99

    • 标签: HTML5

      并没有仔细阅读, 一方面因为 html5 尚未大行其道, 另一方面, 因为有 html4 的基础, 读来性价比不高, 就是一本工具书.

  • Ajax基础教程

    • 作者: (美)阿斯利森,(美)舒塔 著,金灵 等译 / 金灵 / 人民邮电出版社 / 2006-02-01 / 35

    • 标签: 网络 web

      可以一读, 但 ajax 显然需要更多的是实践.

  • 精通JavaScript

    • 作者: John Resig / 江疆 / 人民邮电出版社 / 2008-4-1 / 49

    • 标签: JavaScript web

      个人认为, js 有精通一说, 但显然不是读出来的, 我对 精通 再一次失望了.

  • JavaScript 权威指南

    • 作者: David Flanagan / 张铭泽 / 机械工业出版社 / 2003-01-01 / 99

    • 标签: web 编程 JavaScript

      权威 也让我失望了. 可能是因为我对 js 的兴趣在减少, 现在来评价, 或许有失公允.

  • jQuery基础教程(第2版)

    • 作者: (美)查弗,(美)斯威德伯格 著,李松峰,卢玉平 译 / 李松峰 / 人民邮电出版社 / 2009-11-01 / 49

    • 标签: Web 编程

      同样是很一般的一本书. 或许, jquery就是一个普通的工具, 压根儿就不需要什么教程.

  • jQuery实战

    • 作者: Bear Bibeault / 陈宁 / 人民邮电出版社 / 2009.1 / 49

    • 标签: jquery Web

      很一般的一本书, 虽然jquery领域好的教材太少, 这本算是差强人意. 总体感觉,不比读官方文档好.

  • JavaScript基础教程(第7版) (原版销售150 000册)

    • 作者: Tom Negrino / 陈剑瓯 / 人民邮电出版社 / 2009-04 / 49

    • 标签: JavaScript 前端 web 编程

      在书店买的这本书, 当时对 js 的知识几乎为空白, 以为这本书很适合, 看的过程中发现这本书讲的很一般, 对于有编程基础的人来说几无可取之处.

  • CSS禅意花园

    • 作者: [美] Dave Shea / 陈黎夫 / 人民邮电出版社 / 2007-6 / 49.00元

    • 标签: 网络 Web 设计

      因为这本书评价实在太高, 便想认真读一读. 或许是我对 CSS 的需求比较低, 总觉得没有太多特别之处.

  • C++网络编程 卷1

    • 作者: (美)Douglas C.Schmidt,Stephen D.Huston 著,於春景 译 / 於春景 / 华中科技大学出版社 / 2003-12-01 / 35

    • 标签: 网络 编程

      看过几次, 每次都没有读完.

  • C++网络编程,卷2,基于ACE和框架的系统化复用

    • 作者: (美)施米特,(美)休斯顿 著,马维达 译 / 马维达 / 电子工业出版社 / 2004-01-01 / 38

    • 标签: 编程 网络

      这本书手上只有vol.1, vol.2 是电子档. 有人说, 对于 ACE, 学之者生, 用之者死, 我觉得说的很经典.

  • ACE程序员指南:网络与系统编程的实用设计模式

    • 作者: (美)约翰逊 等编著,马维达 译 / 马维达 / 中国电力出版社 / 2004-11-01 / 48

    • 标签: 网络 编程

      不只是网络编程, 像内存管理, 线程管理甚至主讲的设计模式等都值得一读.

  • Version Control with Subversion

    • 作者: C. Michael Pilato / O’Reilly Media, Inc. / 22 June, 2004 / $34.95

    • 标签: 编程 管理 工程

      因为项目代码采用 SVN 管理, 所以翻了翻, 目前对 SVN 的掌握是基本够用, 但在管理上还存在一些不足, 慢慢来吧.

  • Pro Git

    • 作者: Scott Chacon / Apress / 2009-08-27 / USD 34.99

    • 标签: git 版本控制

      当初考察 SVN 和使用 git 的时候简单翻阅过, 同样是工具, 掌握起来并不复杂.

  • 培养部下的100条铁则

    • 作者: (日)畠山芳雄 著,涂珊 译 / 涂珊 / 东方出版社 / 2006-10-01 / 32.0

    • 标签: 管理

      我一向对于成功学, 管理学并无兴趣, 公司有这本书, 随便翻了一下, 很普通的一本,依然没有兴趣. 有些道理, 明白了也是白明白.

  • 潜规则

    • 作者: 吴思 / 云南人民出版社 / 2001-1 / 16.00元

    • 标签: 历史 吴思

      一本不错的历史, 哲学, 社会科普.

在读

  • 深入理解计算机系统(英文版) – - 国外计算机科学教材系列

    • 作者: (美)Randal E.Bryant, David R.O’Hallaron / 电子工业出版社 / 2006-07-01 / 89.0

    • 标签: 操作系统 编程

      书是好书, 影印的太差.

  • C程序设计语言(英文版第2版)/经典原版书库

    • 作者: Brian W. Kernighan / 机械工业 / 2006-8-1 / 35

    • 标签: 经典 编程

      重温经典.

  • 算法导论

    • 作者: [美] Thomas H.Cormen / 潘金贵 等 / 机械工业出版社 / 2006-9 / 85.00元

    • 标签: 理论 算法 编程

      中英文一起看.

  • LINUX系统编程

    • 作者: Robert Love / O’Reilly Taiwan公司 / 东南大学出版社 / 2009年7月1日 / 56

    • 标签: Linux 编程

      适合对Linux有一定基础, 编码经验丰富的人.

  • 张道真实用英语语法(最新版)

    • 作者: 张道真 / 外语教学与研究出版社 / 2002-5-1 / 37.9

    • 标签: 英语

      老先生的治学态度令人景仰, 我也打算认真的再学习一遍语法, 因为从高中毕业之后,几乎再没有系统学习语法知识, 现在已经忘却的差不多了. 结合这十年对英语的间断学习, 相信会有新的收获.

  • UNIX网络编程 卷2:进程间通信(英文版 第2版)

    • 作者: (美)史蒂文斯 著 / 人民邮电出版社 / 2009-11-01 / 89
    • 标签: 编程 UNIX 网络
  • Beginning Game Development with Python and Pygame

    • 作者: Will McGugan / Apress / August 20, 2007 / $26.39
    • 标签: pygame python 编程
  • 如彗星划过夜空

    • 作者: 林达 / 生活·读书·新知三联书店 / 2006-3 / 21.00元
    • 标签: 人文 历史
  • 扫起落叶好过冬

    • 作者: 林达 / 生活·读书·新知三联书店 / 2006-10 / 38.50元

    • 标签: 人文 社会 历史

      领略另一种文化.

  • 人件(第2版)

    • 作者: Tom DeMarco Timothy Lister / UMLChina翻译组 / 清华大学出版社 / 2003-06-01 / 29.8
    • 标签: 工程 编程
  • TCP/IP 详解(卷1:协议)

    • 作者: W.Richard Stevens / 范建华等 / 机械工业出版社 / 1901-01-01 / 45.0
    • 标签: 网络 经典
  • UNIX网络编程

    • 作者: 史蒂文斯 / 杨继张 / 清华大学出版社 / 2006-01-01 / 98.0
    • 标签: 操作系统 编程 网络 经典
  • 人月神话

    • 作者: (美)布鲁克斯 著,汪颖 译 / 邢玉龙 / 清华大学出版社 / 2007-09-01 / 48
    • 标签: 经典 工程
  • 代码大全(第2版)

    • 作者: [美] 史蒂夫·迈克康奈尔 / 金戈 / 电子工业出版社 / 2006-3 / 98.00元
    • 标签: 经典 编程 工程
  • UNIX编程艺术

    • 作者: Eric S. Raymond / 姜宏、何源、蔡晓骏 / 电子工业出版社 / 2006-02-01 / 59.0
    • 标签: 操作系统 经典 编程

想读

  • 集体智慧编程

    • 作者: TOBY SEGARAN / 莫映 / 电子工业出版社 / 2009年1月 / 59.8

    • 标签: 数据挖掘

      本想确定一下哪些是自己最想读的书, 却犯了难: 没找到哪一本技术书是自己最想读的, 反倒是想补一下社科类. 加上时间有些晚, 只好作罢.

  • SQL沉思录

    • 作者: (美)塞科 著,马树奇 等译 / 马树奇 / 人民邮电出版社 / 2009-11-01 / 49
    • 标签: sql 数据库
  • 深入理解MySQL

    • 作者: (美)贝尔 著,杨涛 等译 / 杨涛 / 人民邮电出版社 / 2010-01-01 / 65
    • 标签: mysql 数据库 游戏开发
  • 软件随想录

    • 作者: Joel Spolsky / 阮一峰 / 人民邮电出版社 / 2009 / 49
    • 标签: 编程 管理
  • 版本控制之道

    • 作者: (美)梅森(Mike Mason) / 陶文 / 电子工业出版社 / 2007-03-01 / 32
    • 标签: 软件工程
  • Ubuntu Linux指南:管理篇/图灵程序设计丛书

    • 作者: Mark G. Sobell / 杨明军 / 人民邮电出版社 / 2009 / 59.00
    • 标签: Ubuntu
  • Python UNIX 和Linux 系统管理指南

    • 作者: (美)基弗特 等著,杨明华 等译 / 杨明华 / 机械工业出版社 / 2009-09-01 / 65
    • 标签: Python
  • Shell脚本学习指南

    • 作者: (美)比博 / Taiwan公司 / 机械工业出版社 / 2009-4-1 / 79.0
    • 标签: Shell
  • 搜索引擎:信息检索实践

    • 作者: (美)克罗夫特 等著,刘挺 等译 / 刘挺 / 机械工业出版社 / 2010-06-01 / 56
    • 标签: 搜索引擎
  • 编写高质量代码

    • 作者: 曹刘阳 / 机械工业出版社 / 2010年7月 / 49
    • 标签: web开发
  • TCP/IP详解 卷1:协议(英文版)

    • 作者: W.Richard Stevens / 人民邮电出版社 / 2010-04-01 / 79
    • 标签: 网络
  • TCPIP详解(卷2)/计算机科学丛书

    • 作者: (美)W.Richard Stevens 著 / 陆雪莹 / 机械工业出版社 / 2004-01-01 / 78
    • 标签: 经典 网络
  • TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议——计算机科…

    • 作者: [美]史蒂文斯(Stevens.W.R.) 著;胡谷雨等 译 / 胡谷雨 昊礼发 / 机械工业出版社 / 2000-09-01 / 35.0
    • 标签: 网络 经典
  • 代码整洁之道

    • 作者: (美)马丁 著,韩磊 译 / 韩磊 / 人民邮电出版社 / 2010-01-01 / 59
    • 标签: 软件工程
  • 菊与刀

    • 作者: (美)鲁思・本尼迪克特 / 吕万和 / 商务印书馆 / 1990-6 / 11.00
    • 标签: 文化 人文 日本
  • 血酬定律

    • 作者: 吴思 / 中国工人出版社 / 2003-08 / 23.8
    • 标签: 历史 吴思
  • 往事并不如烟

    • 作者: 章诒和 / 人民文学出版社 / 2004-1 / 35.00元
    • 标签: 章诒和 历史
  • 伶人往事

    • 作者: 章诒和 / 湖南文艺出版社 / 2006-10 / 38.00元
    • 标签: 章诒和 戏曲 历史
  • sed与awk(第二版)

    • 作者: (美)多尔蒂 / 张旭东 杨作梅 田丽华 / 机械工业出版社 / 2003-06-01 / 55.0
    • 标签: Linux 编程
  • 毛姆读书随笔

    • 作者: 毛姆 / 刘文荣 / 上海三联书店 / 1999-12 / 20.00
    • 标签: 随笔 文学
  • 项目管理艺术

    • 作者: (美)伯克温(Berkun,S.) / 东南大学出版社 / 2007-9-1 / 45.0
  • 测试驱动开发(中文版)

    • 作者: Kent Beck / 崔凯 / 中国电力出版社 / 2004-03-01 / 28.0
    • 标签: 测试 工程
  • 万历十五年

    • 作者: [美] 黄仁宇 / 生活·读书·新知三联书店 / 1997-5 / 12.80元
    • 标签: 历史 文学
  • 带一本书去巴黎

    • 作者: 林达 / 生活·读书·新知三联书店 / 2002-5 / 35.00元
    • 标签: 社会 文学 人文 历史
  • The Definitive Guide to Django

    • 作者: Adrian Holovaty / Apress / 2007-12-06 / USD 44.99
    • 标签: django python 网络 编程
  • 美国宪政历程

    • 作者: 任东来 / 中国法制出版社 / 2005-06-01 / 35.0
    • 标签: 历史 宪政
  • 在边缘看世界

    • 作者: 林达 / 云南人民出版社 / 2001-7 / 20.00元
    • 标签: 人文 历史 社会
  • 我也有一个梦想

    • 作者: 林达 / 三联书店 / 2004-08 / 23.00
    • 标签: 文学 人文 社会 历史
  • 一路走来一路读

    • 作者: 林达 / 湖南文艺出版社 / 2004-04-01 / 31.9
    • 标签: 文学 人文 社会 历史
  • 西班牙旅行笔记

    • 作者: 林达 / 生活·读书·新知三联书店 / 2007-1 / 49.00元
    • 标签: 文学 社会 人文 历史
  • 像自由一样美丽

    • 作者: 林达 / 生活·读书·新知三联书店 / 2007-9 / 38.00元
    • 标签: 人文 历史 社会
  • 历史深处的忧虑

    • 作者: 林达 / 生活·读书·新知三联书店 / 1997-5-1 / 18.00
    • 标签: 人文 历史
  • Erlang 程序设计

    • 作者: Armstrong / 赵东炜 / 人民邮电出版社 / 2008 / 79
    • 标签: 编程 游戏开发
  • 游戏开发中的人工智能

    • 作者: David M.Bourg / O’Reilly Taiwan公司 / 东南大学出版社 / 2006 / 48
    • 标签: 编程 游戏开发
  • 游戏

    • 作者: (德)曼・艾根 / 惠昌常 / 上海教育出版社 / 2005-1-1 / 30
  • LaTeX Companion, The (2nd Edition)

    • 作者: Frank Mittelbach / Addison-Wesley Professional / 22 April, 2004 / $59.99
  • Algorithms

    • 作者: Sanjoy Dasgupta / McGraw-Hill Science/Engineering/Math / 2006-09-13 / 41.55
  • C++ Templates中文版

    • 作者: David Vandevoorde / 陈伟柱 / 人民邮电出版社 / 2004-01-01 / 62
  • 软件架构设计

    • 作者: 温昱 / 电子工业 / 2007-05-01 / 45

还有一些在读和想读的没有列进来, 时间太晚了, 做一个标记罢了. 希望 2011 年比 2010 年做的更好.

Author: Fox (yulefox at gmail.com)
Date: 2011-01-01

引言

散列表应用比较广泛, 用于类字典式查询, 一些动态语言 (如 python) 实现了类似的动态结构. 在合理假设 (散列函数选取恰当, 散列表尺寸合理) 的情况下, 散列表元素查找的期望时间为O(1).

我们所熟悉的静态数组, 其元素查找的期望时间即为O(1), 散列表正是静态数组概念的推广 [1].

术语

在描述散列表的实现算法时通常会涉及下列术语:

  • chaining (链接法)

    链接法是解决碰撞最简单, 最直观的一个方案, 将具有同一散列值 (碰撞) 的元素添加到同一链表中. 查找时, 如果发生碰撞, 将在链表中进行顺序查找.

  • collision (碰撞)

    不同关键字映射到同一个槽上的情况称为发生碰撞. 解决碰撞主要有两种方案: 链接法 (chaining) 和 开放寻址法 (open addressing).

  • direct-address table (直接寻址表)

    直接寻址表就是普通的静态数组, 其下标就是元素的关键字 (key). 在不考虑数组大小和关键字冲突的情况下, 对该数组进行元素的插入, 删除及查找操作均只需O(1)的时间. 但在实际应用中, 数组的大小是受限的, 而关键字的全域通常比较大, 想在直接寻址表中为每一个关键字建立映射会比较困难.

  • division method (除法散列法)

    散列函数为取模运算. 通过对关键字 k 进行模 m 运算 (k mod m), 将关键字 k 映射到 m 个槽中. 需要注意的是, 不同的模 m 对散列效果有很大影响.

    理论上讲, m 为质数时, 散列效果较好. 在实际应用中, 由于 m 通常对应槽的数量, 在需要动态扩充槽的数量时, 寻找下一个质数 m 不太方便. 但一般会避开 2 的整次幂, 因为这会造成 k mod m 的结果是 k 的低位.

  • double hasing (双重散列)

    开放寻址的一种较好的解决方案. 使用了两个散列函数 h3(k)h2(k) 分别确定初始探查位置和探查偏移, 从而使不同关键字产生不同的探查序列. 为了能够遍历整个散列表, 要求 h2(k) 与散列表的大小 m 互质.

  • linear probing (线性探查)

    开放寻址的一种解决方案. 当表未满时, 向前/后探查, 直到找到空槽并插入, 实现较为简单, 容易导致一次群集 (primary clustering).

  • load factor (负载因子)

    在一个具有 m 个槽位的散列表中已然存储了 n 个元素, 则负载因子为 n/m, 用以表示散列表的负载情况.

  • multiplicaiotn method (乘法散列法)

    乘法散列法比除法散列法稍显复杂. 其实现分两步:

    • 将关键字 k 映射到 [0, 1) 区间的 k' ((k * A) mod 1);
    • k' 映射到 m 个槽中 (INT(m * k')).

    在计算机程序实现中, 基于上面的理论, 在实现上略有差异, 假定计算机字长为 w, m 取值为 2 的整次幂 2^p, 先以 k * s (s = A * 2^w) 取其乘积低 32 位中的高 p 位为散列值.

    乘法散列的效果虽然与 m 无关, 但与 A 有关. Knuth [2] 认为 A = (sqrt(5) - 1) / 2 是一个比较理想的值.

  • open addressing (开放寻址法)

    开放寻址是解决碰撞的另一方案, 摒弃链表, 使用某种探查规则, 从当前表中探查空的槽位并插入. 当发生碰撞时, 按照探查规则确定的探查序列依次探查. 其探查规则主要包括: 线性探查, 二次探查, 双重散列, 开方探查.

  • perfect hashing (完全散列)

    perfect 已经将完全散列的含义表达的很明确, 完全散列是指最坏情况下的查找时间为O(1). 完全散列适用于关键字集合是静态的情况, 举例来讲, C 语言的关键字集合是静态的. 这时我们可以有针对的制定散列方案, 实现完全散列.

  • primary clustering (一次群集)

    采用线性探查时, 随着负载因子的增加, 连续被占用的槽不断增加, 平均查找时间也随之增加, 群集现象必然加剧.

  • quadratic probing (二次探查)

    开放寻址的一种解决方案. 采用一个辅助散列函数确定初始探查位置, 并产生探查序列, 不同初始探查位置使用不同的探查序列, 从而避免一次群集. 但相同初始探查位置的关键字使用相同的探查序列, 从而导致二次群集.

  • secondary clustering (二次群集)

    采用二次探查时, 相同初始探查位置的关键字使用相同的探查序列, 当此类情况增多导致的群集现象称为二次群集.

  • slot (槽)

    槽为散列表中的一个关键字对应的元素.

  • simple uniform hashing (简单一致散列)

    简单一致散列 是一个假设: 假定任何元素等可能地散列到 m 个槽中, 且与其它元素已被散列到什么位置独立无关.

    一个好的散列函数应满足 简单一致散列 假设, 计算元素的散列值是比较重要的一步, 寻找好的散列函数并不是一件简单的事情, 还是交给数学家们吧 [2].

  • universal hashing (全域散列)

    全域散列是提供一族散列函数, 这些散列函数均可将给定的关键字 k 映射到 m 个槽中. 在使用时, 从中随机选取一个散列函数. 以达到较好的平均性态.

log4c 的散列表实现分析

log4c 是一系列基于 log4j 的日志库中以 C 语言实现的一个, 其中提供了一个通用的 (generic) 散列表实现.

该实现使用了除法散列法.

其实现思想与 STL 的容器/迭代器有异曲同工之妙, 通过迭代器实现对散列表的访问与操作. 为了突出重点, 说明问题, 我对代码进行了一定的删减 (故无法正常运行且存在内存泄露问题), 并增加了部分注释.

/* 下面是基本的数据结构定义 */

/* 散列表操作: 散列函数及关键字比较函数 */
struct __sd_hash_ops {
    unsigned int (*hash)      (const void*);
    int          (*compare)   (const void*, const void*);
};
typedef struct __sd_hash_ops sd_hash_ops_t;

/* 迭代器 */
struct __sd_hash_iter {
    void*                  key;
    void*                  data;
    struct __sd_hash*      hash;    /* 所在的散列表 */
    unsigned int           __hkey;  /* 映射前的关键字 */
    struct __sd_hash_iter* __next;
    struct __sd_hash_iter* __prev;
};
typedef struct __sd_hash_iter sd_hash_iter_t;

/* 散列表结构 */
struct __sd_hash {
    size_t               nelem;     /* 元素总数 */
    size_t               size;      /* 槽数 */
    sd_hash_iter_t**     tab;       /* 槽链表 */
};
typedef struct __sd_hash sd_hash_t;

#define SD_HASH_FULLTAB     2       /* rehash when table gets this x full */
#define SD_HASH_GROWTAB 4   /* grow table by this factor */
#define SD_HASH_DEFAULT_SIZE 10 /* self explenatory */

/* 映射后的关键字, 除法 */
#define hindex(h, n)        ((h)%(n))

/* 创建槽数为 a_size 的散列表 */
sd_hash_t* sd_hash_new(size_t a_size, const sd_hash_ops_t* a_ops)
{

    sd_hash_t*          hash;
    sd_hash_iter_t**    tab;

    /* 初始化 */
    hash    = sd_calloc(1, sizeof(*hash));
    tab     = sd_calloc(a_size, sizeof(*tab));

    hash->nelem = 0;
    hash->size  = a_size;
    hash->tab   = tab;
    hash->ops   = a_ops != 0 ? a_ops : &default_ops;

    return hash;
}

/* 对碰撞太多的散列表重新散列, 提高查找效率 */
void rehash(sd_hash_t* a_this)
{
    size_t              i;
    int h, size;
    sd_hash_iter_t**    tab;
    sd_hash_iter_t*     p;
    sd_hash_iter_t*     q;

    /* 扩容 */
    size    = SD_HASH_GROWTAB * a_this->size;
    tab     = sd_calloc(size, sizeof(*tab));

    /* 将表中元素重新散列到新表中 */
    for (i = 0; i < a_this->size; i++) {
        for (p = a_this->tab[i]; p; p = q) {
            q           = p->__next;

            /* 计算新的槽孔位置 */
            h           = hindex(p->__hkey,
                                 size);

            /* 插到链表头部 */
            p->__next   = tab[h];
            tab[h]      = p;
            if (p->__next != 0) p->__next->__prev = p;
            p->__prev   = 0;
        }
    }
    a_this->tab     = tab;
    a_this->size    = size;
}

/* 查找关键字, 如果不存在, 则插入到表中 */
sd_hash_iter_t* sd_hash_lookadd(sd_hash_t* a_this, const void* a_key)
{
    int h;
    sd_hash_iter_t* p;

    if ((p = sd_hash_lookup(a_this, a_key)) != 0)   return p;
    if ((p = sd_calloc(1, sizeof(*p))) == 0)        return 0;

    /* 将新的元素插入到表中 */
    p->hash     = a_this;
    p->__hkey   = a_this->ops->hash(a_key);

    /* 对碰撞太多的散列表重新散列, 提高查找效率 */
    if (a_this->nelem > SD_HASH_FULLTAB * a_this->size) rehash(a_this);

    /* 计算槽孔位置 */
    h           = hindex(p->__hkey,
                         a_this->size);

    /* 插到链表头部 */
    p->__next   = a_this->tab[h];
    a_this->tab[h]  = p;
    if (p->__next != 0) p->__next->__prev = p;

    a_this->nelem++;

    return p;
}

/* 将字符串映射为整数 */
unsigned int sd_hash_hash_string(const char* a_string)
{
    register unsigned int h;

    for (h = 0; *a_string != '\0'; a_string++)
        h = *a_string + 31 * h;

    return h;
}

参考

[1] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. 算法导论.
[2] (1, 2) Donald E. Knuth. 排序和查找, 计算机编程艺术 第3卷.

Author: Fox (yulefox at gmail.com)
Site: http://www.yulefox.com
Date: Dec. 09, 2010

0. 引言

对于很多个人或团队开发者而言, 往往缺乏的不是开发能力, 而是无法确定一个高度一致的编码规范, 甚至对于简单如目录结构这样的问题也莫衷一是. 近日读 <GNU AUTOCONF, AUTOMAKE, AND LIBTOOL> 一书时, 解开了长久埋在自己心中的一些疑惑.

1. 项目目录结构

  • 项目顶级目录中只放置 configure, aclocal.m4, README, COPYING等配置文件, 不放置代码文件;
  • 将工具生成的中间配置文件 (如 install-sh) 置于一个名为 config 的子目录中, 保持代码目录清洁;
  • 所有库文件存放在名为 lib 的子目录中;
  • 所有代码文件存放在名为 src 的子目录中;
  • 将项目划分成若干模块, 并将每一个模块代码置于 src 的一个子目录中;
  • 所有文档文件存放在名为 doc 的子目录中;
  • 所有测试代码存放在名为 test 的子目录中;

如此一来, 一个项目的目录结构看上去应该是这样:

config/    configure.in  lib/     Makefile.am  src/
configure  doc/          COPYING  README       test/

2. 头文件条件编译宏

头文件条件编译是一个很平淡的话题, 这是要讲的是关于该宏名的命名规则.

对于使用 #include "header.h" 这种形式的包含, header.h 一般仅在项目内部包含使用 (不会提供给项目外使用), 宏名使用全大写的 MOD_HEADER_H, 其中MOD 为模块名, HEADER 为头文件名.

对于使用 #include <header.h> 这种形式的包含, header.h 一般为系统安装库文件, 如果 header.h 为项目内部头文件, 而且可能提供项目外部使用 (如公共模块功能库), 宏名使用全大写的 PAC_HEADER_H, 其中 PAC 为软件包名, HEADER 为头文件名. 也就是说, 该头文件将会被安装在系统中, 其位置可能为/usr/local/include/pac/header.h.

有人喜欢将一些宏, 变量, 接口名前加 下划线 以示强调. 对此, 该书作者认为带下划线的标识符只供编译器保留使用.

3. 函数定义

作为编程风格约定, 作者建议所有函数返回值 独占一行, 以便查询. 这对于GNU中使用 vim, Emacs 等编辑器的开发者是非常有必要的.

4. 错误管理

错误管理模块是项目开始的一个不错的选择, 统一的错误管理模块使得整个项目的日志记录, 错误调试更加容易.

5. 内存管理

对内存管理接口进行封装, 做一些友好的日志记录, 错误提示.

上一篇文章中我们已经完成了SVN服务器的布署,本文将在此基础上完成Trac布署及其与SVN的集成。

Trac由Edgewall公司开发维护,使用Python开发,开源,用于软件开发项目管理,提供SVN等版本控制工具的Web接口,与Wiki、bug跟踪集成。

Trac官网提供了完备文档,并有丰富的插件库

这篇文章详细讲述了Ubuntu 10.04下Trac 0.12的安装过程(不同之处在于其使用Bazaar作为版本控制工具),本文讲述布署基于MySQL的Trac,并完成与SVN的集成。

1. 安装Trac

$ sudo apt-get install apache2 libapache2-mod-wsgi python-setuptools \
 python-genshi mysql-server python-mysqldb libapache2-mod-auth-mysql
$ sudo easy_install Trac==0.12

2. 配置数据库

$ mysql -u root -p
mysql> CREATE DATABASE trac DEFAULT CHARSET utf8 COLLATE utf8_bin;
mysql> exit

3. 配置Trac项目

$ sudo mkdir /home/www/trac
$ sudo trac-admin /home/www/trac initenv

项目名称 [My Project]> <project_name>
数据库连接字符串 [sqlite:db/trac.db]> mysql://root:<password>@localhost/trac

如果您想试用一下这个新的项目环境,请尝试运行Trac独立Web服务器 `tracd`:

  tracd --port 8000 /home/www/trac

恭喜您!

$ sudo trac-admin /home/www/trac permission add admin TRAC_ADMIN

4. 修改数据库表引擎

$ mysql -u root -p

mysql> USE trac;
mysql> ALTER TABLE `attachment` ENGINE = InnoDB;
ALTER TABLE `auth_cookie` ENGINE = InnoDB;
ALTER TABLE `cache` ENGINE = InnoDB;
ALTER TABLE `component` ENGINE = InnoDB;
ALTER TABLE `enum` ENGINE = InnoDB;
ALTER TABLE `milestone` ENGINE = InnoDB;
ALTER TABLE `node_change` ENGINE = InnoDB;
ALTER TABLE `permission` ENGINE = InnoDB;
ALTER TABLE `report` ENGINE = InnoDB;
ALTER TABLE `repository` ENGINE = InnoDB;
ALTER TABLE `revision` ENGINE = InnoDB;
ALTER TABLE `session` ENGINE = InnoDB;
ALTER TABLE `session_attribute` ENGINE = InnoDB;
ALTER TABLE `system` ENGINE = InnoDB;
ALTER TABLE `ticket` ENGINE = InnoDB;
ALTER TABLE `ticket_change` ENGINE = InnoDB;
ALTER TABLE `ticket_custom` ENGINE = InnoDB;
ALTER TABLE `version` ENGINE = InnoDB;
ALTER TABLE `wiki` ENGINE = InnoDB;
mysql> SELECT table_name, engine FROM information_schema.tables
WHERE table_schema=DATABASE();
mysql> exit

5. 布署Apache

$ sudo trac-admin /home/www/trac/ deploy /home/www/trac/deploy
$ sudo chown -R www-data /home/www/trac
$ sudo vim /etc/apache2/sites-available/svn

        # 往文件中加入以下内容, 使用与SVN相同的用户验证
        WSGIScriptAlias /trac /home/www/trac/deploy/cgi-bin/trac.wsgi
        <Directory /home/www/trac/deploy/cgi-bin>
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        <Location /trac/login>
                AuthType Basic
                AuthName "Trac Login"

                AuthBasicAuthoritative off
                AuthUserFile /dev/null

                Auth_MySQL on
                Auth_MySQL_Authoritative on
                Auth_MySQL_Host localhost
                Auth_MySQL_DB svn
                Auth_MySQL_User root
                Auth_MySQL_Password <password>
                Auth_MySQL_Password_Table user
                Auth_MySQL_Empty_Passwords off
                Auth_MySQL_Encrypted_Passwords on
                Auth_MySQL_Username_Field name
                Auth_MySQL_Password_Field passwd
                Auth_MySQL_Encryption_Types PHP_MD5

                Require valid-user
        </Location>

$ sudo a2enmod auth_mysql
$ sudo a2ensite svn
$ sudo /etc/init.d/apache2 restart

6. 完成Trac与SVN的整合

将SVN配置到Trac的配置文件trac.ini中。找到repository_dir配置项:

$ sudo vim /home/www/trac/conf/trac.ini

repository_dir = /home/www/svn

访问http://www.svn.com/trac,大功告成!

由于不同的Linux版本服务器布署方案不尽相同,本文不保证对Ubuntu之外的环境有效,事实上,由于Apache服务模块实现和使用不同,本文的方案仅适用于Ubuntu,如果在其它环境下,建议参考其它文章。

本文使用vim作为配置文件编辑器,对vim不熟悉的读者可以使用nano或者其它。

1. 安装Apache + MySQL

我所使用的服务器版本为(使用$ uname -a查看):Linux 2.6.32-24-server x86_64 GNU/Linux,在安装系统时已经安装了Apache、MySQL。或者使用下述命令进行安装:

$ sudo apt-get install apache2 mysql-server

如果在启动Apache时遇到端口冲突,可能是已安装的其它Web服务器(如NGINX)占用80端口引起的,可以修改相应配置文件,换用其它端口,如8080。如果出现下面的提示:

apache2: Could not reliably determine the server's fully qualified domain
         name, using 127.0.1.1 for ServerName

需要往httpd.conf(默认为空)文件中添加一行:

$ sudo vim /etc/apache2/httpd.conf

ServerName localhost

2. 安装subversion

安装subversion及对应Apache的服务器模块和MySQL用户认证管理模块,如果使用基本认证,则不必安装libapache2-mod-auth-mysql,考虑到用户信息是很多服务的一个基本要素,使用数据库进行管理是必要的。

$ sudo apt-get install subversion libapache2-svn libapache2-mod-auth-mysql
$ sudo a2enmod auth_mysql

3. 创建subversion项目

创建svn项目(选择一个合适的目录),修改用户以便可以通过HTTP访问:

$ sudo svnadmin create --fs-type fsfs /home/www/svn
$ sudo chown -R www-data /home/www/svn

4. 创建用户验证数据库

$ mysql -u root -p

mysql> CREATE DATEBASE svn;
mysql> USE svn;
mysql> CREATE TABLE user (
name char(30) NOT NULL COMMENT '用户名',
passwd char(32) NOT NULL COMMENT '密码',
PRIMARY KEY (name) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> INSERT INTO user VALUE ('admin', MD5('admin'));

此处需要注意,密码域一定要足够宽,本文将使用MD5密码(32字节),如果使用SHA1,则需要40字节

5. 配置虚拟主机

为提供HTTP访问svn,并提供MySQL用户验证(Ubuntu与Linux其它版本在配置上是有区别的),需要配置虚拟主机。本文提供局域网布署方案,使用指定的主机名www.svn.com(对应IP地址为192.168.1.2):

$ sudo vim /etc/hosts

192.168.1.2      www.svn.com

编辑虚拟主机的配置文件(可以基于Apache提供的默认配置进行修改):

$ sudo vim /etc/apache2/sites-available/svn

<VirtualHost *:80>
        ServerName www.svn.com
        ServerAdmin admin@svn.com
        DocumentRoot /home/www
        ErrorLog /home/www/log/svn_error.log
        LogLevel warn
        CustomLog /home/www/log/svn_access.log combined

        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>

        <Directory /home/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        <Location /svn/>
                DAV svn
                SVNPath /home/www/svn

                AuthType Basic
                AuthName "Subversion Repository"

                AuthBasicAuthoritative off
                AuthUserFile /dev/null

                Auth_MySQL on
                Auth_MySQL_Authoritative on
                Auth_MySQL_Host localhost
                Auth_MySQL_DB svn
                Auth_MySQL_User root
                Auth_MySQL_Password <password>
                Auth_MySQL_Password_Table user
                Auth_MySQL_Empty_Passwords off
                Auth_MySQL_Encrypted_Passwords on
                Auth_MySQL_Username_Field name
                Auth_MySQL_Password_Field passwd
                Auth_MySQL_Encryption_Types PHP_MD5

                Require valid-user
        </Location>
</VirtualHost>

6. 启用站点

$ sudo a2ensite svn
$ sudo /etc/init.d/apache2 reload

在浏览器中打开http://www.svn.com/svn,提示输入用户名及密码后,可以看到SVN目录树,大功告成!


更多关于libapache2-svnlibapache2-mod-auth-mysql的文档在/usr/share/doc中,包括权限管理等内容都能够从中获取。

我一直希望能够推进研发工作流程的规范化,也在不断尝试用Web完成若干工作,并计划在时机成熟之后推广到其它部门。不想这次另一个项目的程序和我们的策划不约而同地换服务器,都希望使用SVN代替AB,于是用Ubuntu架了两台服务器,但如何能够让策划自己管理这台服务器在我预想之外,只好把文档写的更加繁冗,尽量让没有使用过Linux的用户也能读懂。

WebQQ

Web前端开发听上去并不是一份牛逼的工作,但如果你的Web前端开发搞的很牛逼,那么你一定是做到了Steve Souders提到的那14条,个人认为如果前端开发有什么所谓的金科玉律,应该就是指他们了。Yahoo!后来还据此专门为Firebug开发了一个插件:YSlow

闭门造车不是个好主意,Web QQ 2.0的前端我认为做的很有创意,所以我准备停下来,认真分析一下其实现。在开始分析之前,先来了解一些基础知识。

基础知识

那些不得不说的工具

Firebug

“工欲善其事,必先利其器”,我所使用的分析工具简单而强大,上文已经提到过:Firebug + YSlow,显然,你还需要一个FireFox(推荐使用FX 3.6,4.0的组件比较少吧?)。

一次页面访问的执行顺序是怎样的?

当用户通过浏览器访问指定页面(url)时,浏览器将其包装为一个http请求,该请求除包含待访问页面地址及若干参数(可选)外,还包含了用户的浏览器及操作系统信息,语言、编码信息,缓存信息及其它一些浏览器配置信息等。

这是请求http://web2.qq.com/时的头信息(部分):

GET / HTTP/1.1
Host: web2.qq.com
User-Agent: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
If-Modified-Since: Wed, 29 Sep 2010 10:50:21 GMT

这是对上述请求的响应的头信息:

HTTP/1.1 304 Not Modified
Server: nginx
Date: Thu, 30 Sep 2010 03:18:22 GMT
Last-Modified: Wed, 29 Sep 2010 10:50:21 GMT
Connection: close
Expires: Thu, 07 Oct 2010 03:18:22 GMT
Cache-Control: max-age=604800

具体的响应内容,就是我们能够看到的页面了。但由于一次页面访问可能导致多次发送http请求(事实上,由于图片、脚本、样式表的存在,这几乎是必然的),页面上的内容并不会一次全部加载完成,而是陆续加载。

关于http请求的更多内容见这里

一次http请求的执行顺序是怎样的?

从浏览器发出http请求,到收到服务器响应,整个过程通常可能持续几毫秒到几秒不等的时间,其顺序如下:

  1. 域名解析:时间一般比较短(n ms),取决于所访问站点隶属的DNS服务器及网络环境。
  2. 建立连接:时间一般稍长(n*10^1 ms),取决于所访问站点对应服务器及网络环境。
  3. 发送请求:时间一般很短(接近于0 ms),取决于本地浏览器。
  4. 等待响应:时间一般稍长(n*10^1 – n*10^3 ms),取决于所访问站点对应服务器的处理效率及网络环境。
  5. 接收数据:时间一般稍长(n*10^1 – n*10^3 ms),取决于请求内容的多少及网络环境。

下面将根据http请求发送顺序具体分析webqq 2.0的http请求。

http请求分析

分析之前,先看一张图,这是http请求序列中的一小部分(由于Web QQ 2.0资源更新比较频繁,内容不尽相同,但影响不大)。后面还会有更加详细的分解:

Web QQ 2.0资源加载序列(局部)

我们注意到图中有颜色为蓝色和红色的两条竖线,蓝色线表示页面的所有DOM(文档对象模型)加载完成,为DomContentLoaded事件的触发点,jQuery$(document).ready()将开始执行,IE不支持该事件;红色线为文档load事件的触发点,意味着所有内容加载完毕(含图片资源等),window.onload()将会被执行。

1. 加载页面

首先看一下具体响应页面内容(这也是上图中的第一个http请求)。为了方便,我直接将分析内容以注释形式夹在其中(位于对应内容上方):

<!-- 这是html5的文档标识,web qq 2.0明显已经为html5的到来做好了准备 -->
<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <!-- 针对IE8,使用IE7进行渲染 -->
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <meta name="keywords" content="QQ、QQ网页桌面、WebQQ 2.0、web QQ、网页聊天、网页QQ、QQ被封、QQ空间、腾讯微博、QQ邮箱、腾讯新闻、QQlive、QQ地图、QQ校友、QQ团购" />
    <meta name="description" content="WebQQ2.0以个人化门户为理念,致力于满足用户聊QQ,看资讯、写微博、玩游戏、听音乐、拼团购、收发邮件及网络存储等各种网络生活需求,打造一站式个人网络生活平台。" />
    <meta name="copyright" content= "腾讯公司" />
    <!-- 主要是针对移动终端等,禁止用户缩放 -->
    <meta name="viewport" content="minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0, width=device-width, user-scalable=no">
    <title>WebQQ 2.0</title>
    <!-- 设置各种环境下的图标 -->
    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
    <link rel="bookmark" href="/favicon.ico" type="image/x-icon" />
    <link rel="apple-touch-icon" href="/webqq.png"/>
    <!-- 加载jet库 -->
    <script type="text/javascript" src="./js/jet.all.js?t=20101014002"></script>
    <!-- 加载样式表 -->
    <link type="text/css" rel="stylesheet" href="http://hp.qq.com/webqqpic/style/jet.all.css?t=20101014002" />
    <link type="text/css" rel="stylesheet" href="http://hp.qq.com/webqqpic/style/qqweb.main.css?t=20101014002" />
    <link id="qqwebSkin" type="text/css" rel="stylesheet" href="about:blank" />
    <script type="text/javascript">
        Jet().$package(function(J){
            // 加载主题样式
            var themeId = "theme_wood1";
            J.out("themeId:  "+themeId)
            J.http.loadCss("http://hp.qq.com/webqqpic/style/" + themeId + "/qqweb.theme.css?t=20101014002",{node:J.dom.id("qqwebSkin")});
        });
    </script>
</head>
<body>
    <!-- 需要开启JavaScript -->
    <noscript>
        <div class="noscript">
            你好,需要浏览器开启JavaScript功能来帮助你使用WebQQ2.0。<br/>
            请设置浏览器开启 JavaScript功能,然后重试。
        </div>
    </noscript>
    <!-- 针对ios触屏用户,添加触屏响应 -->
    <iframe id="touchpad" src="./touchpad.html"></iframe>
    <div id="desktop" class="EQQ_Container">
        <div id="topBar">
            <div class="topBar_bg">
                <a title="20101014002" id="logo" href="http://im.qq.com/webqq" target="_blank"></a>
                <div id="qqBar" class="qqBar">
                </div>
            </div>
        </div>
        <div id="mainPanel">
            <div id="sideBarReplacement" class="sideBarReplacement">
            </div>
            <div id="toggleBar" class="toggleBar toggleBar_show" title="打开侧边栏"></div>
        </div>
        <div id="toolBar">
            <div class="toolBar_bg">
                <div id="statusBar">
                    <div id="settingCenterButton" class="settingCenterButton"></div>
                </div>
                <div id="taskBar"></div>
            </div>
        </div>
    </div>
    <script type="text/javascript" src="http://pingjs.qq.com/tcss.ping.js"></script>
    <script type='text/javascript'>
        Jet().$package(function(J){
            if(J.platform.iPad) document.querySelector('#touchpad').style.display = 'block';
        });
        if(typeof(pgvMain) == 'function'){
            // 与页面访问及缓存控制相关,腾讯平台共用的一个统计功能,暂且放下
            pgvMain();
        }
    </script>
    <script type="text/javascript" src="./js/qqweb.all.js?t=20101014002"></script>
</body>
</html>

如果在没有缓存的情况下,下载该页面(大约1.4K)可能耗费n ms的时间,资源的加载时间与其大小成正比(同时受当时网络环境影响)。

通过Firebug查看该页面效果是这样的:

初始响应内容

2. 加载资源

正如我们在前面的序列图中看到的,浏览器在处理返回的页面过程(从上到下顺序执行,遇到错误将中止)中,该页面所使用(直接使用)的脚本及样式表会依次加载。加载过程是异步的,后续资源不需要等待前面的资源加载完成,但如果遇到内联或外联脚本,则需等待脚本执行后才能继续。因此,如果可能,将脚本置于页面底部是明智的选择:

  • Line 20: http://web2.qq.com/js/jet.all.js?t=20101014002
  • Line 22: http://hp.qq.com/webqqpic/style/jet.all.css?t=20101014002
  • Line 23: http://hp.qq.com/webqqpic/style/qqweb.main.css?t=20101014002
  • Line 66: http://pingjs.qq.com/tcss.ping.js
  • Line 78: http://web2.qq.com/js/qqweb.all.js?t=20101014002

3. 执行页面

接下来便是执行脚本以及加载各DOM所需的资源并完成元素绘制。

  • Line 25:内联脚本功能为加载当前使用的主题样式,并将所加载的样式表嵌入到Line 24对应的结点中。
  • Line 43:针对触屏用户添加触屏响应,将其置入一个iframe中。
  • Line 47:样式填充——加载logo(背景图片元素,由qqweb.main.css给出)。
  • Line 75:内联脚本功能为处理缓存(cookie)并发送统计信息。

通过上面的http请求顺序,我们也能够看出:页面执行是从上到下的,DOM元素(如Line 47的logo)必须等到前面的脚本(Line 25)执行完毕才能开始绘制,这也印证了我们上文提到的尽量将脚本放在页面底部的必要性。当然,如果脚本本身不需要立即执行(如这里的jet库),放在页面顶部反而是必须的,后续脚本的执行依赖它们,需要等待其加载完成才能继续执行,前面序列图的时间线已经很好的证明了这一点。

Web QQ的实现逻辑全部位于上文分析的页面底部的javascript文件qqweb.all.js中,其实现基于JET

欲知后事,且听下回……