存档:2008年九月

流逝得岁月

九月 29, 2008 | 心情杂记 | RSS 2.0

    换找个博客版面已经好久了,那时候还在大三,还远离着我得大四,但现在已经是物是人非事事休了,青春都散场了,留下的是那些美好得回忆,大学四年,虽有遗憾,但也有好多好多骄傲得地方,譬如年年得一等奖学金,软件设计师,在学校网站做得那些网站,好多次得替考挣得钱。。。。。。。这些让我更加怀念我得大学生活,特别是我和女朋友同甘共苦得那些日子,虽然已经过去了,但他留给我得是一生得珍藏,还有大学里认识得好多有个性的朋友,从他们哪里我学到了很多,学会了坦然,学会了提高心境,学会了阔达,从他们哪里我得到了好多。步入了社会我们就真正得担起了社会得角色,相信自己会走得更远。
      让曾经鄙视过伤过你得人有一点后悔。让曾经帮助过你得人,值得骄傲自豪。永远记得那几个字。
     勇气,自信,热情 。

没有评论 »

十一放假了

九月 28, 2008 | linux, 心情杂记 | RSS 2.0

假期开始了三天了,天天的浑浑噩噩,不知道了些什么。还好能经常看些书,浏览点技术。学点东西。
勇气,信心,热情
今天玩了玩ubuntu下的fusion,截了两张desktop图上来:用的命令是 sleep 5 ; gnome-screenshot

没有评论 »

函数与指针(转)

九月 24, 2008 | c/c++, linux | RSS 2.0

若有错误之处,还请指正.

1. 首先,在C语言中函数是一种function-to-pointer的方式,即对于一个函数,会将其自动转换成指针的类型.如:

#include<stdio.h>

void fun()
{
}

int main()
{
        printf(”%p      %p      %p”, &fun, fun, *fun);
        return 0;
}

这三个值的结果是一样的. 其实对于最后的那个*fun, 即使前面加上很多个*号, 其结果也不变, 即**fun, ***fun的结果都是一样的. 对于这个问题, 因为之前讲过函数是一种
function-to-pointer方式, 其会自动转换成指针的类型, &fun是该函数的地址, 为指针类型, fun是一个函数,会转换成其指针类型, 而对于*fun, 由于fun已经变成了指针类型, 指向这个函数, 所以*fun就是取这个地址的函数,而又根据function-to-pointer, 该函数也转变成了一个指针, 所以以此类推, 这三个值的结果是相同的.

2. 如何调用一个地址上的函数
  如果知道了一个函数所在的地址, 可以将其强制转化成某一种类型的函数指针, 然后再根据这个指针去调用这个地址的函数. 如:

#include<stdio.h>

void f(int i)
{
        printf(”i = %d”, i);
}

int main()
{
        unsigned long add;
        add = (unsigned long)f;
        ((void (*)(int))add)(10);
        (*(void (*)(int))add)(20);
        return 0;
}

使用(void (*)(int))的方式可以将一个地址转换成一个带int参数且没有返回值的函数的指针类型, 然后再去调用,由于第1点中讲的function-to-pointer, 所以最后两条语句中加与不加那个*号效果都是一样的. 在嵌入式方面经常用到这种方式.

3. 函数指针数组的用法.
有时候需要定义一个数组, 其内容为一系列的函数指针, 然后对其进行调用, 如:

#include<stdio.h>
int max(int v1, int v2)
{
        return (v1 > v2 ? v1 : v2);
}

int min(int v1, int v2)
{
        return (v1 < v2 ? v1 : v2);
}

int sum(int v1, int v2)
{
        return (v1 + v2);
}

int main()
{
        int (*p[3])(int, int);
        p[0] = max;
        p[1] = min;
        p[2] = sum;

        printf(”p[0] = %d”, (p[0])(3, 5));
        printf(”p[1] = %d”, (p[1])(4, 6));
        printf(”p[2] = %d”, (p[2])(1, 2));
        return 0;
}

虽然感觉这种方法有点累赘, 但是也算是一种使用的方式, 所以介绍一下.

4.返回一个指向数组的指针的方式
可以让函数返回一个指向数组的一个指针, 如:

#include<stdio.h>
#include<stdlib.h>
int (*p())[10]
{
        int (*m)[10];
        int i;
        m = (int (*)[10])malloc(10 * sizeof(int));
        if (m == NULL)
        {
                printf(”malloc error”);
                exit(1);
        }
        for (i = 0; i < 10; i++)
                *(*m+i) = i+1;
        return m;
}

int main()
{
        int (*a)[10];
        int i;
        a = p();
        for (i = 0; i < 10; i++)
                printf(”%d “, *(*a+i));
        printf(”done”);
        return 0;
}

这种方式中,int (*a)[10]是一个指向一维数组的一个指针, 而p()也是返回一个指向一维数组的一个指针.

5.返回一个函数指针的指针
对这个问题, signal()函数是最好的例子,

void (*signal (int signo, void (*func)(int)))(int);

很多朋友刚开始看这个函数定义的时候是不太懂, 其实可以一步一步地慢慢看, 我以前是这样分析的, 希望能对大家有用.
int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.所以说对于

void (*signal(int signo, void (*fun)(int)))(int);

就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数), 而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.
signal函数返回的其实是指向以前的信号处理程序的指针, 所以举一个例子来说明返回指向函数的指针的用法,如:

#include<signal.h>
#include<stdlib.h>
#include<stdio.h>

void sig_fun2(int signo)
{
        printf(”in sig_fun2:%d”, signo);
}

void sig_fun1(int signo)
{
        printf(”in sig_fun1:%d”, signo);
}

int main()
{
        unsigned long i;
        if (signal(SIGUSR1, sig_fun1) == SIG_ERR)
        {
                printf(”signal fun1 error”);
                exit(1);
        }

        (signal(SIGUSR1, sig_fun2))(30);

        printf(”done”);
        return 0;
}

6. 使用函数指针作为参数的情况
在函数的参数中, 可能会带有一个函数指针, 这在signal()函数中是出现了的, 另外再写个例子如:

#include<stdio.h>

int max(int v1, int v2)
{
        return (v1 > v2 ? v1 : v2);
}

int min(int v1, int v2)
{
        return (v1 < v2 ? v1 : v2);
}

int sum(int v1, int v2)
{
        return (v1 + v2);
}

int fun(int a, int b, int (*call)(int, int))
{
        return (call(a, b));
}

int main()
{
        printf(”max=%d”, fun(1, 2, max));
        printf(”min=%d”, fun(3, 4, min));
        printf(”sum=%d”, fun(5, 6, sum));
        return 0;
}

其实在很多排序函数中就是使用的这个参数为函数指针的方式来进行调用的.

草草地总结了一下, 希望能有一些用

没有评论 »

shell

九月 23, 2008 | linux | RSS 2.0

该部分讲述的是shell编程方面的知识,以Bourne Shell为主,因为它是使用最广泛的,因而移植性最好,虽然功能不如现代许多其他版本的shell强大。

篇幅不大,只是一个简明手册,不具备系统性。

 

1.文件名生成通配符

在shell中,有两种类型的通配符:文件名生成通配符和正则表达式通配符。两者是不同的,注意区分。

字符*

星号*匹配文件名中的任何字符串。

字符?

?匹配文件名中的任何单个字符。

[…]和[!…]

使用[…]匹配方括号中的任何字符。可以使用一个横杠来连接两个字母或数字,表示一个范围。

使用[!…]表示非的意思。

 

2.正则表达式通配符

(1).基本元字符集
元字符 含义
^ 只匹配行首,在[]中表示否定
$ 只匹配行尾
* 一个单字符后紧跟*,匹配0个或多个此字符
[] 匹配[]内字符,可以是一个单字符,也可以是字符序列,可以使用-表示范围
用来屏蔽一个元字符的特殊含义
. 匹配任意单字符
pattern{n} 用来匹配前面pattern出现次数,n为次数
pattern{n, } 含义同上,但次数最少为n
pattern{n, m} 含义同上,但次数在n与m之间
(2).屏蔽的特殊字符

$ . ‘ “ * [ ] ^ | + ?

(3).例子
^$ 匹配空行
[A-Za-z] 匹配所有字母
[A-Za-z]* 匹配所有单词
[^A-Za-z] 匹配任一非字母型字符
A{2, } 匹配AAB、AAAB、…
[0-9]{2}-[0-9]{2}-[0-9]{4} 匹配dd-mm-yyyy

 

3.find命令

(1).find命令的形式:
find pathname -optino [ -pirnt -exec -ok ]

pahtname:查找的目录路径;

-print:将匹配的文件输出到标准输出;

-exec:对匹配的文件执行给出的shell命令,命令形式为‘command {} ;’,注意空格;

-ok:与-exec类似,在执行前会让用户确认。

(2).-name

按照文件名查找文件。

例:

查找$HOME目录及子目录下所有.txt文件:

find ~ -name "*.txt" -print

查找当前目录及子目录下以一个大写字母开头的文件:

find . -name "[A-Z]*" -print

查找/etc及子目录下以host开头的文件:

find  /etc -name "host*" -print

查找$HOME目录及子目录下文件:

find ~ -name "*" -print或find . -print

查找当前目录及子目录下以两个小写字母打头,后跟两个数字,最后是.txt后缀的文件:

find . -name "[a-z][a-z][0-9][0-9].txt" -print
(3).-perm

按照权限查找。

例:

查找当前目录及子目录下权限为755的文件:

find . -perm 755 -print

查找当前目录及子目录下所有用户都可读、写、执行的文件(使用八进制数字前要加-):

find . -perm -007 -print
(4).-prune

忽略某个目录,如果同时使用了-depth,则-prune被忽略。

例:

find /apps -name "/apps/bin" -prune -o -print
(5).-user和-nouser

-user:按照文件属主查找;

-nouser:查找文件属主帐户已经被删除的文件。

例:

查找属主为duan的文件:

find ~ -user duan -print

查找文件属主帐户已经被删除的文件

find /home  -nouser -print
(6).-group和-nogroup

-group:按照文件所属的组查找;

-nogroup:查找属于不存在的组的文件。

例:

查找属于用户组informix的文件

find /apps -group informix -print

查找不存在组的文件:

find / -nogroup -print
(7).-mtime

按更改时间查找,减号-限定更改时间距今n日内的文件,加号+限定更改时间距今n日外的文件。

例:

查找更改时间在5日内的文件:

find . -mtime -5 -print

查找更改时间在3日前的文件:

find . -mtime +3 -print
(8).-newer

查找更改时间新的文件,可以使用!逻辑非。

例:

查找比haha.txt新的文件:

find . -newer haha.txt

查找比haha.txt新但比find.txt旧的文件:

find . -newer haha.txt ! -newer find.txt -print
(9).-type

按类型查找。类型有:

  • b,块设备文件;
  • d:目录;
  • c:字符设备文件;
  • p:管道文件;
  • l:符号链接文件;
  • f:普通文件。

例:

查找目录文件:

find /etc -type d -print

查找非目录文件:

find . ! -type d -print

查找符号联接文件:

find /etc -type l -print
(10).-size

按照文件大小查找。单位是块,也可以是字节(后跟c)。

例:

查找字节大于1M的文件:

find . -size +1000000c -print

查找字节为100的文件:

find . -size 100c -print

查找大小大于10块的文件:

find . -size +10 -print

查找字节小于10的文件:

find . -size -10c -print
(11).-depth

先匹配所有文件,再在子目录中查找。

例:

先匹配当前目录中所有.txt文件,再在子目录中找:

find . -name "*.txt" -depth -print
(12).-mount

只在当前文件系统中查找,不进入其他文件系统。

例:

find . -name "*.txt" -mount -print
(13).-cpio

使用cpio命令将文件备份到磁带上。

例:

find etc home apps -depth -print -cpio /dev/rmt0
(14).-exec和-ok

对匹配的文件执行某操作。

例:

find logs -type f -mtime +5 -exec rm {} ;find . -name "*.log" -mtime +5 -ok rm {} ;find /etc -name "passwd*" -exec grep "rounder" {} ;
(15).和xargs结合使用
find . -name "*.txt -print |xargx filefind . -name "*.txt" -print | xagrs echo  >/tmp/tmpfilefind . -perm -7 -print |xargs chmod o-wfind . -type f -print |xargs grep "device"find . -name * -type f -print |xargs grep "DBO"

 

4.shell输入和输出

(1).echo命令

显示文本行或变量,缺省会自动换行。

特殊字符:

  • c:不换行;

  • f:进纸;

  • t:跳格;

  • :换行。

有些系统使用echo的-n选项来控制不换行。

特殊字符使用来转义,如双引号。

(2).read命令

read命令从标准输入读入一行信息,并将其赋给变量。如果只有一个变量,则整行内容都赋给此变量,若有多个变量,则把输入内容按空格分开分别赋给变量,多余的输入内容全部赋给最后一个变量。

read形式为:

read var1 var2 …
(3).cat命令

cat命令显示文件内容,使用选项-v可以显示控制字符。

如:

cat myfilecat -v myfile
(4).tee命令

tee命令把输出的一个副本送到标准输出,另一个副本拷贝到相应的文件中。

如:

who | tee who.out;who | tee -a who.out;-a表示追加到文件末尾。
(5).文件重定向

文件描述符0为标准输入,文件描述符1为标准输出,文件描述符2为标准错误。

常用重定向命令:

command >filename:把标准输出重定向到一个新文件中;command >>filename:把标准输出重定向到一个文件(附加);command 1>filename:把标准输出重定向到一个新文件中;command >filename 2>&1:把标准输出和标准出错重定向到一个文件中;command 2>filename:把标准错误重定向到一个新文件中;command 2>>filename:把标准错误重定向到一个文件(附加);command >>filename 2>&1:把标准输出和标准出错重定向到一个文件中(附加);command < file >file2:以file为标准输入,以file2为标准输出;command < filename:以filename为标准输入;command << delimiter:从标准输入读入,直到遇到delimiter分界符;command <&m:把文件描述符m作为标准输入;command >&m:把标准输出重定向到文件描述符m中;command <&-:关闭标准输入。
(6).exec命令

exec命令替换当前shell,它践踏了你当前的shell。

一个例外是exec对文件描述符操作时,它不覆盖当前shell。

使用exec操作文件描述符,例如:

exec 4<&0 0<stock.txt;

把描述符4重定向到标准输入,把标准输入重定向到stock.txt。

exec 0<&4;

把描述符0重定向到描述符4,即恢复标准输入。

 

5.命令执行顺序

(1).使用&&

一般形式为:

命令1&& 命令2

含义:&&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行。

如:

cp justice.doc justice.bak && echo "cp was OK"
(2).使用||

一般形式为:

命令1|| 命令2

含义:如果左边的命令(命令1)未执行成功,那么就执行||右边的命令(命令2)。

如:

cp wopper.txt oops.txt || echo "cp failed"
(3).使用()和{}组合命令

在当前shell执行一组命令,形式为:

(命令1;命令2;…)

在子shell中执行一组命令,形式为:

{命令1;命令2;…}

如:

comet month_end || (echo "hello" | mail dave; exit)

 

6.stty命令

stty用于设置终端特性。

(1).查询现有终端设置
stty -a
(2).设置tty选项
stty name character

如:

stty erase ^H

在vi中输入控制字符:先按ctrl-v,再按该控制字符。

(3).保存stty现有设置
stty -g

以可读形式保存stty现有设置,便于以后恢复。

如:

SAVETTY=`stty -g`...stty $SAVETTY

 

7.shell变量

(1).设置变量
  1. Variable_name = value,设置实际值到变量中;
  2. Variable_name + value,如果设置了变量,则使用value值,但不改变量的值;
  3. Variable_name :? value,如果未设置变量,显示用户错误信息value;
  4. Variable_name ? value,如果未设置变量,显示系统错误信息;
  5. Variable_name := value,如果设置了变量,则使用它,如果未设置变量,设置其值;
  6. Variable_name :- value,同上,但取值并不设置到变量中。

如:

echo "The file is ${FILES:?}"echo "The file is ${FILES:?' sorry cannot locate the files'}"

COLOUR=blueecho "The sky is ${COLOUR:-grey} today"
(2).显示变量

在变量名前加$,可以用{}将变量名括起来。如:

echo ${CREAT_PICTURE}
(3).清除变量

使用unset命令清除变量。

(4).显示所有本地shell变量

使用set命令。

(5).设置只读变量
variable_name=valuereadonly variable_name
(6).查看所有只读变量

使用readonly命令。

(7).位置变量参数

传给脚本的参数可以使用如下变量进行访问,只有前9个可以直接访问,使用shift可以改变此限制:

$0、$1、$2、…、$9

其中$0为脚本名字,$1到$9为第一到第九个参数。

(8).特定变量参数

$#:传递到脚本的参数个数;

$*:以一个单字符串显示所有向脚本传递的参数;

$$:脚本运行的当前进程ID号;

$!:后台运行的最后一个进程的进程ID号;

$@:与$#类似;

$-:显示shell使用的当前选项,与set命令相同;

$?:显示最后命令的退出状态,0表示没有错误,其他任何值表明有错误。

 

8.环境变量

(1).设置环境变量
variable_name=value; export variable_name或variable_name=valueexport variable_name
(2).查看所有环境变量

使用env命令。

(3).环境变量列表
CDPATH;EXINIT;HOME;IFS;LOGNAME;MAIL;MAILCHECK;MAILPATH;PATH;PS1;PS2;SHELL;TERMINFO;TERM;TZ;EDITOR;PWD;PAGER;MANPATH;LPDEST/PRINTER。

 

9.引用

shell引用类型有:””双引号;’’单引号;`反引号;反斜线。

(1).双引号

双引号可引用除字符$、`、外的任意字符或字符串,这几个特殊字符是美元符号、反引号和反斜线,对shell来说,它们有特殊意义。

(2).单引号

类似于双引号,但shell会忽略任何引用值。

(3).反引号

反引号用于设置系统命令的输出到变量,shell将反引号的内容作为一个系统命令,并执行其内容。

(4).反斜线

反斜线屏蔽下一个字符的特殊含义。

具有特殊含义的字符:& * + ^ $ ` “ | ?。

如:

echo $$      显示当前进程ID号;echo $$      显示$$;expr 12 * 12  计算12乘以12;echo “This is a copyright 251 sign”  显示八进制字符;

 

10.条件测试

(1).语法

使用test命令,形式如下:

test condition或[ condition ] (在括号和条件之间要有空格)

测试结果为0表示成功,其他为失败。

(2).逻辑操作符

-a 逻辑与,操作符两边都为真,结果为真,否则为假;

-o 逻辑或,操作符一边为真,结果为真,否则为假;

! 逻辑否,条件为假,结果为真。

(3).测试文件状态
-d   目录-f   正规文件-L   符号连接-r   可读-s   文件长度大于0、非空-w   可写-u   文件有suid位设置-x   可执行

如:

test -w score.txt[ -d appbin ][ -w b1.txt -a -w b2.txt ]
(4).字符串测试

字符串测试有5种格式:

test "string"test string_operator "string"test "string" string_operator "string"[ string_operator string ][ string string_operator string ]

其中string_operator可为:

= 两个字符串相等;

!= 两个字符串不等;

-z 空串;

-n 非空串。

如:

[ -z $EDITOR ][ $EDITOR = "vi" ][ "$TAPE1" = "$TAPE2" ][ "$TAPE1" != "$TAPE2" ]
(5).数值测试

格式如下:

"number"  number_operator "number"   or[ "number" number_operator "number" ]

其中numbe_operator可为:

-eq 数值相等;

-ne 数值不等;

-gt 第一个数大于第二个数;

-lt 第一个数小于第二个数;

-le 第一个数小于等于第二个数;

-ge 第一个数大于等于第二个数。

如:

[ "$NUMBER" -eq "300" ][ "$NUMBER" -gt "300" ][ "$NUMBER" -gt "$SOURE_COUNT" ][ "99" -le "993" ]
(6).expr用法

expr命令一般用于数值计算,但也可用于字符串。格式如下:

expr argument operator argument

如:

expr 10 + 10expr 30 / 3expr 30 / 3 / 2expr 30 * 3   乘法要用反斜线LOOP=0LOOP=`expr $LOOP + 1`

可以用expr测试一个数,如果试图计算非整数,将返回错误:

$VALUE=helloexpr $VALUE + 10 >/dev/null 2>&1echo $?  (失败)

expr还能比较字符串和进行模式匹配。expr的返回值成功为1,与系统的最后退出命令结果刚好相反,不要混淆。

 

11.控制流结构

(1).退出状态

退出shell使用如下命令:

exit n

其中n为退出状态,0表示成功,1表示失败。

(2).if then else语句

格式为:

if condition1then        (if和then不在同一行)  ....elif condition2then  ....else  ....fi或:if condition; then    (if和then在一行时要用分号)  ....fi

例如:

if [ “10” -le “12” ]then  …fiif grep david ha.txt >/dev/null 2>&1then  …fi
(3).交互模式

测试脚本是交互模式还是非交互模式,使用test的-t选项,若测试成功,则为交互方式。如:

if [ -t ]; then  echo “we are interactive with a terminal”fi
(4).空语句

if语句各部分都不能为空,可以使用空命令解决这个问题。

空语句形式为一个冒号:。

if [ -z $DIR ]then  echo “DIR is empty”else  :    #do nothingfi
(5).case语句

case语句格式:

case 值 in模式1)  命令1  …  ;;模式2)  命令2  …  ;;esac

模式:

模式可以使用元字符:* ? [ ... ]。

模式中可以使用“|”作为或命令。

示例:

case $TERMINAL invt100|vt102)  TERM=vt100  ;;vt200)  TERM=vt200  ;;*)  echo “not valid”  exit 1  ;;esac
(6).for循环

for语句格式:

for 变量名 in 列表do  命令1  命令2 …done

列表:

省略in列表时,for接收命令行位置参数作为参数,等同于:

for param in “$@”或for param in “$*”

示例:

for loop in 1 2 3 4 5for loop in “orange red blue grey”for loop in `ls`
(7).until循环

until循环执行一系列命令直至条件为真停止。

until格式:

until 条件  命令1  …done

条件测试发生在循环末尾,因此循环至少执行一次。

(8).while循环

while循环格式:

while 命令do  命令1  命令2 ..done

永真循环:

while :do  …done
(9).使用break和continue

break允许跳出循环或case语句。如果在一个嵌套循环里,可以指定跳出的循环个数。如在两层循环里,用break 2刚好跳出整个循环。

continue跳过当前循环步,继续下一循环。

 

12.shell函数

(1).函数定义格式
function 函数名(){   命令1   …}
(2).函数参数

函数里使用参数就象在一般脚本中使用特殊变量$1、$2等一样。

(3).函数返回

函数结束可以用return返回函数执行结果:

return   从函数返回,用最后状态命令决定返回值;return 0  无错误返回;return 1  有错误返回。
(4).函数返回值测试

在脚本调用函数语句的后面使用最后状态命令测试函数的返回值。如:

check_is_directory $FILENAMEif [ “$?” = “0” ]then  …fi

更好的方法是在if中测试。如:

if check_is_directory $FILENAMEthen  …fi
(5).函数文件

文件的首行为#!/bin/sh。

装入函数文件格式:

. /pathname/filename

在shell中装入后,可以用set命令查看,用unset删除。

在脚本中装入后,可以在脚本中使用定义的函数。

 

13.脚本参数

(1).处理方式

脚本处理参数有三种方式:

  1. 使用特定变量$1、$2、…、$9,$#统计参数个数。此法的缺点是只能处理9个参数;
  2. 使用shift;
  3. 使用getopts。
(2).shift

shift的功能是每次将参数位置向左偏移一位,通过循环可以处理所有的参数。

如:

while [ $# -ne 0 ]do  echo $1  shiftdone
(3).获得命令行输入的最后一个参数

有两种方法:

  1. 使用命令:eval echo $$#;
  2. 使用命令:shift `expr $#-2`。
(4).getopts

使用getopts可以处理复杂的命令行参数。

getopts的一般格式为:

getopts option_string variable
				

没有评论 »

mysql优化

九月 23, 2008 | mysql | RSS 2.0

引言:在以前,我总是习惯用 INT UNSIGNED 来存储一个转换成Unix时间戳的时间值,认为这样做从索引,比较等角度来讲,都会比较高效。现在我们来对比下 TIMESTAMP 和 INT UNSIGNED 以及 DATETIME 这3种类型到底谁更好。

 

1. 准备

创建一个测试表:

mysql> CREATE TABLE `t` (`d1` int(10) unsigned NOT NULL default '0',`d2` timestamp NOT NULL default CURRENT_TIMESTAMP,`d3` datetime NOT NULL,KEY `d2` (`d2`),KEY `d1` (`d1`),KEY `d3` (`d3`));

 

然后创建一个存储过程填充数据:

mysql> DELIMITER //CREATE PROCEDURE INS_T()BEGINSET @i=1;WHILE 0<1DOSET @i=@i+1;INSERT INTO i VALUES (1199116800+@i, FROM_UNIXTIME(1199116800+@i), FROM_UNIXTIME(1199116800+@i));END WHILE;END;//DELIMITER ;

 

时间戳 1199116800 表示 2008-01-01 这个时间点。然后运行存储过程,大概填充几十万条记录后,中止执行,因为上面的存储过程是个死循环,所以需要人工中止。
来看看到底有多少条记录了,以及索引情况:

mysql> select count(*) from t;+----------+| count(*) |+----------+|   924707 |+----------+mysql> analyze table t;+--------+---------+----------+-----------------------------+| Table  | Op      | Msg_type | Msg_text                    |+--------+---------+----------+-----------------------------+| test.t | analyze | status   | Table is already up to date |+--------+---------+----------+-----------------------------+mysql> show index from t;+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+| t     |          1 | d2       |            1 | d2          | A         |      924707 |     NULL | NULL   |      | BTREE      |         || t     |          1 | d1       |            1 | d1          | A         |      924707 |     NULL | NULL   |      | BTREE      |         || t     |          1 | d3       |            1 | d3          | A         |      924707 |     NULL | NULL   |      | BTREE      |         |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

 

2. 对比

2.1 只检索一条记录

mysql> explain select * from t where d1 = 1199579155;+----+-------------+-------+------+---------------+------+---------+-------+------+-------+| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+|  1 | SIMPLE      | t     | ref  | d1            | d1   | 4       | const |    1 |       |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+mysql> explain select * from t where d2 = '2008-01-06 08:25:55';+----+-------------+-------+------+---------------+------+---------+-------+------+-------+| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+|  1 | SIMPLE      | t     | ref  | d2            | d2   | 4       | const |    1 |       |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+mysql> explain select * from t where d3 = '2008-01-06 08:25:55';+----+-------------+-------+------+---------------+------+---------+-------+------+-------+| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+|  1 | SIMPLE      | t     | ref  | d3            | d3   | 8       | const |    1 |       |+----+-------------+-------+------+---------------+------+---------+-------+------+-------+

 

2.2 范围检索

mysql> explain select * from t where d1 >= 1199894400;+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra       |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+|  1 | SIMPLE      | t     | range | d1            | d1   | 4       | NULL | 121961 | Using where |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+mysql> explain select * from t where d2 >= from_unixtime(1199894400);+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra       |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+|  1 | SIMPLE      | t     | range | d2            | d2   | 4       | NULL | 121961 | Using where |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+mysql> explain select * from t where d3 >= from_unixtime(1199894400);+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra       |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+|  1 | SIMPLE      | t     | range | d3            | d3   | 8       | NULL | 120625 | Using where |+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+

没有评论 »

记个空间

九月 23, 2008 | 心情杂记 | RSS 2.0

http://hi.baidu.com/_green_hand_/blog/item/05ac3239879a9922b9998f32.html
http://hi.baidu.com/ufo008ahw
http://blog.csdn.net/whyangwanfu
http://hi.baidu.com/5l2_

没有评论 »

heap,stack

九月 22, 2008 | c/c++, 数据结构算法 | RSS 2.0

一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 – 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = “abc”; 栈
char *p2; 栈
char *p3 = “123456″; 1234560在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, “123456″); 1234560放在常量区,编译器可能会将它与p3所指向的”123456″优化成一个地方。
}  
二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活
2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较

char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include <stdio.h>;
void main()
{
char a = 1;
char c[] = “1234567890″;
char *p =”1234567890″;
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。
?     

2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因

没有评论 »

学习

九月 21, 2008 | linux, 心情杂记, 读书 | RSS 2.0

#include<stdio.h>
#include<malloc.h>
#include<string.h>

#define MAXSIZE 20
#define LEN sizeof(list)

typedef struct record
{
    char number[MAXSIZE];
    char name[MAXSIZE];
    int score;
}student;

typedef struct node
{
    student stu;
    struct node *next;
}list;

int count=0;

list *Creat();

void Display(list *rear);

void main()
{
    list *rear;
    rear=Creat();
    Display(rear);
    getchar();
}

list *Creat()
{
    list *head,*rear,*p;
    head=(list*)malloc(LEN);
    head->next = head;
    p=(list*)malloc(LEN);

    while(1)
    {   char ch;
        printf(”请输入学号: “);
        scanf(”%s”,p->stu.number);  
        printf(”请输入姓名: “);
        scanf(”%s”,p->stu.name);
        printf(”请输入学生的成绩: “);
        scanf(”%d”,&p->stu.score);
        ++count;
        p->next = head->next;
        head->next=p;

        printf(”继续输入学生的信息吗?(y是/n否): “);

        while((ch = getchar() ) != ”) ;
        scanf(”%c”,&ch);
        if(ch==’y')
        {
            p=(list*)malloc(LEN);
            continue;
        }
        if(ch==’n')
        {
            printf(”共有%d名学生的信息!”,count);
            break;
        }
    }

    return head;
}

void Display(list *r)
{
    list *head;
    head=r->next;
    printf(”t显示学生信息学号t姓名t成绩”);
    while(head!=r) {
        printf(”%s %s %d”,head->stu.number,head->stu.name,head->stu.score);
        head=head->next;
    }
}

没有评论 »

师哥的程序

九月 12, 2008 | c/c++ | RSS 2.0

**************************************************
* /data/c/C语言时钟源程序
* 南阳理工学院计算机系01612班大鹏课程设计作品
* 运行环境:TC2.0
* Email:ypsky@mail2.nyist.net
*************************************************
#include”graphics.h”
#define PI 3.1416
#include”math.h”
#include”dos.h”
main()
{
int x0=320,y0=240,r0=150;
void init_sceen();
void sec();
init_sceen(x0,y0,r0);
sec();
closegraph();
}
void init_sceen(int x0,int y0,int r0)/********************************************/
{
int i,x,y,graphdriver,graphmode;
char s[10];
float alpha,a0=90;

graphdriver=DETECT;
initgraph(&graphdriver,&graphmode,”");
setbkcolor(3);
setcolor(2);
circle(x0,y0,r0);
circle(x0,y0,r0 30);
setfillstyle(SOLID_FILL,10);
floodfill(x0-r0-10,y0,2);
/*please input the time*/
for(i=12;i>=1;i–)
{
alpha=(a0 30*(11-i)*PI/180);
x=x0 cos(alpha)*r0-16;
y=y0-sin(alpha)*r0;
sprintf(s,”-”,i);
setcolor(4);
settextstyle(0,0,2);
outtextxy(x,y,s);
}
/*input second*/
for(i=60;i>=1;i–)
{
alpha=(a0 6*(60-i)*PI/180);
x=x0 cos(alpha)*(r0-20);
y=y0-sin(alpha)*(r0-20);
setcolor(14);
if(i%5==0)
circle(x,y,5);
else circle(x,y,2);
floodfill(x,y,14);
}
setlinestyle(0,0,3);
}
void sec(void) /*******************************************************/
{
int x,y,i,j,k,xj,yj,xk,yk,xi,yi,x0=320,y0=240,r0=150;
union REGS r;

unsigned char *shijie=”";
unsigned char *daa=”";
suct time tim;
suct date dat;
float alphai,alphak,alphaj,a0=90;
xi=x0;yi=y0;xj=x0;yj=y0;xk=x0;yk=y0;
do
{
/*intput the time*/
x=38;y=12;
gettime(&tim);
sprintf(shijie,”d:d:d”,tim.ti_hour,tim.ti_min,tim.ti_sec);
setfillstyle(SOLID_FILL,0);
bar(245,190,375,210);
setcolor(15);
outtextxy(245,190,shijie);
/*input the date*/ /*****************************************************/
geate(&dat);
sprintf(daa,”d–d–d”,dat.da_year,dat.da_mon,dat.da_day);
/*setfillstyle(SOLID_FILL,3);*/
bar(225,290,395,310);
setcolor(RED);
outtextxy(225,290,daa);
x=190;y=430;
setcolor(RED);
outtextxy(x-26,y,”Designed by GuoLiuTa0″);
setcolor(LIGHTRED);
outtextxy(x 76,y0 20,”NBA GAME”);
setlinestyle(0,0,3);
k=tim.ti_hour;
j=tim.ti_min;
i=tim.ti_sec;
alphak=(a0 30*(12-k)-j*5/60.*6)*PI/180;
alphaj=(a0-6*j)*PI/180;
/*write second hand*/
alphai=(a0 6*(60-i))*PI/180;
x=x0 cos(alphai)*(r0-32);
y=y0-sin(alphai)*(r0-32);
setcolor(BLACK);
line(x0,y0,xi,yi);
setcolor(YELLOW);
line(x0,y0,x,y);
xi=x;
yi=y;
/*write minute hand*/
x=x0 cos(alphaj)*(r0-60);
y=y0-sin(alphaj)*(r0-60);
setcolor(BLACK);
line(x0,y0,xj,yj);
setcolor(BLUE);
line(x0,y0,x,y);
xj=x;
yj=y;
/*write hour hand*/
x=x0 cos(alphak)*(r0-99);
y=y0-sin(alphak)*(r0-99);
setcolor(BLACK);
line(x0,y0,xk,yk);
setcolor(RED);
line(x0,y0,x,y);
xk=x;
yk=y;
delay(10000);
}
while(!kbhit());
}

没有评论 »

纯真IP数据库格式详解

九月 12, 2008 | 数据结构算法, 软件工程/编程技巧/设计模式 | RSS 2.0

纯真IP数据库格式详解

摘要
网络上的IP数据库以纯真版的最为流行,LumaQQ也采用了纯真版IP数据库做为IP查询功能的基础。不过关于其格式的文档却非常之少,后来终于在网上找到了一份文档,得以了解其内幕,不过那份文档寥寥数语,也是颇为耐心才读明白。在这里我重写一份,以此做为LumaQQ开发者文档的一部分,我想还是必要的。本文详细介绍了纯真IP数据库的格式,并且给出了一些Demo以供参考。Luma, 清华大学
修改日期: 2005/01/14

Note: 在此感谢纯真IP数据库作者金狐和那唯一一份文档的作者。

修改历史:
2005-01-14 修改了原来一些表达不清和错误的地方


自从有了IP数据库这种东西,QQ外挂的显示IP功能也随之而生,本人见识颇窄,是否还有其他应用不得而知,不过,IP数据库确实是个不错的东西。如今网络上最流行的IP数据库我想应该是纯真版的(说错了也不要扁我),迄今为止其IP记录条数已经接近30000,对于有些IP甚至能精确到楼层,不亦快哉。2004年4、5月间,正逢LumaQQ破土动工,为了加上这个人人都喜欢,但是好像人人都不知道为什么喜欢的显IP功能,我也采用了纯真版IP数据库,它的优点是记录多,查询速度快,它只用一个文件QQWry.dat就包含了所有记录,方便嵌入到其他程序中,也方便升级。

基本结构

QQWry.dat文件在结构上分为3块:文件头,记录区,索引区。一般我们要查找IP时,先在索引区查找记录偏移,然后再到记录区读出信息。由于记录区的记录是不定长的,所以直接在记录区中搜索是不可能的。由于记录数比较多,如果我们遍历索引区也会是有点慢的,一般来说,我们可以用二分查找法搜索索引区,其速度比遍历索引区快若干数量级。图1是QQWry.dat的文件结构图。

图1. QQWry.dat文件结构

要注意的是,QQWry.dat里面全部采用了little-endian字节序

一. 了解文件头

QQWry.dat的文件头只有8个字节,其结构非常简单,首四个字节是第一条索引的绝对偏移,后四个字节是最后一条索引的绝对偏移。

二. 了解记录区

每条IP记录都由国家和地区名组成,国家地区在这里并不是太确切,因为可能会查出来“清华大学计算机系”之类的,这里清华大学就成了国家名了,所以这个国家地区名和IP数据库制作的时候有关系。所以记录的格式有点像QName,有一个全局部分和局部部分组成,我们这里还是沿用国家名和地区名的说法。

于是我们想象着一条记录的格式应该是:[IP地址][国家名][地区名],当然,这个没有什么问题,但是这只是最简单的情况。很显然,国家名和地区名可能会有很多的重复,如果每条记录都保存一个完整的名称拷贝是非常不理想的,所以我们就需要重定向以节省空间。所以为了得到一个国家名或者地区名,我们就有了两个可能:第一就是直接的字符串表示的国家名,第二就是一个4字节的结构,第一个字节表明了重定向的模式,后面3个字节是国家名或者地区名的实际偏移位置。对于国家名来说,情况还可能更复杂些,因为这样的重定向最多可能有两次。

那么什么是重定向模式?根据上面所说,一条记录的格式是[IP地址][国家记录][地区记录],如果国家记录是重定向的话,那么地区记录是有可能没有的,于是就有了两种情况,我管他叫做模式1和模式2。我们对这些格式的情况举图说明:

图2. IP记录的最简单形式

图2表示了最简单的IP记录格式,我想没有什么可以解释的

图3. 重定向模式1

图3演示了重定向模式1的情况。我们看到在模式1的情况下,地区记录也跟着国家记录走了,在IP地址之后只剩下了国家记录的4字节,后面3个字节构成了一个指针,指向了实际的国家名,然后又跟着地址名。模式1的标识字节是0×01。

图4. 重定向模式2

图4演示了重定向模式2的情况。我们看到了在模式2的情况下(其标识字节是0×02),地区记录没有跟着国家记录走,因此在国家记录之后4个字节之后还是有地区记录。我想你已经明白了模式1和模式2的区别,即:模式1的国家记录后面不会再有地区记录,模式2的国家记录后会有地区记录。下面我们来看一下更复杂的情况。

图5. 混和情况1

图5演示了当国家记录为模式1的时候可能出现的更复杂情况,在这种情况下,重定向指向的位置仍然是个重定向,不过第二次重定向为模式2。大家不用担心,没有模式3了,这个重定向也最多只有两次,并且如果发生了第二次重定向,则其一定为模式2,而且这种情况只会发生在国家记录上,对于地区记录,模式1和模式2是一样的,地区记录也不会发生2次重定向。不过,这个图还可以更复杂,如图7:

图6. 混和情况2

图6是模式1下最复杂的混和情况,不过我想应该也很好理解,只不过地区记录也来重定向而已,有一点我要提醒你,如果重定向的地址是0,则表示未知的地区名。

所以我们总结如下:一条IP记录由[IP地址][国家记录][地区记录]组成,对于国家记录,可以有三种表示方式:字符串形式,重定向模式1和重定向模式2。对于地区记录,可以有两种表示方式:字符串形式和重定向,另外有一条规则:重定向模式1的国家记录后不能跟地区记录。按照这个总结,在这些方式中合理组合,就构成了IP记录的所有可能情况。

设计的理由

在我们继续去了解索引区的结构之前,我们先来了解一下为何记录区的结构要如此设计。我想你可能想到了答案:字符串重用。没错,在这种结构下,对于一个国家名和地区名,我只需要保存其一次就可以了。我们举例说明,为了表示方便,我们用小写字母代表IP记录,C表示国家名,A表示地区名:

  1. 有两条记录a(C1, A1), b(C2, A2),如果C1 = C2, A1 = A2,那么我们就可以使用图3显示的结构来实现重用
  2. 有三条记录a(C1, A1), b(C2, A2), c(C3, A3),如果C1 = C2, A2 = A3,现在我们想存储记录b,那么我们可以用图6的结构来实现重用
  3. 有两条记录a(C1, A1), b(C2, A2),如果C1 = C2,现在我们想存储记录b,那么我们可以采用模式2表示C2,用字符串表示A2

你可以举出更多的情况,你也会发现在这种结构下,不同的字符串只需要存储一次。

了解索引区

在”了解文件头”部分,我们说明了文件头实际上是两个指针,分别指向了第一条索引和最后一条索引的绝对偏移。如图8所示:

图8. 文件头指向索引区图示

实在是很简单,不是吗?从文件头你就可以定位到索引区,然后你就可以开始搜索IP了!每条索引长度为7个字节,前4个字节是起始IP地址,后三个字节就指向了IP记录。这里有些概念需要说明一下,什么是起始IP,那么有没有结束IP? 假设有这么一条记录:166.111.0.0 -166.111.255.255,那么166.111.0.0就是起始IP,166.111.255.255就是结束IP,结束IP就是IP记录中的那头4个字节,这下你应该就清楚了吧。于是乎,每条索引配合一条记录,构成了一个IP范围,如果你要查找166.111.138.138所在的位置,你就会发现166.111.138.138落在了166.111.0.0 – 166.111.255.255这个范围内,那么你就可以顺着这条索引去读取国家和地区名了。那么我们给出一个最详细的图解吧:

图9. 文件详细结构

现在一切都清楚了是不是?也许还有一点你不清楚,QQWry.dat的版本信息存在哪里呢?答案是:最后一条IP记录实际上就是版本信息,最后一条记录显示出来就是这样:255.255.255.0 255.255.255.255 纯真网络2004年6月25日IP数据。OK,到现在你应该全部清楚了。

Demo

下一步:我给出一个读取IP记录的程序片断,此片断摘录自LumaQQ源文件edu.tsinghua.lumaqq.IPSeeker.java,如果你有兴趣,可以下载源代码详细看看。

 /**  * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构  * @param offset 国家记录的起始偏移  * @return IPLocation对象  */ private IPLocation getIPLocation(long offset) {  try {   // 跳过4字节ip   ipFile.seek(offset + 4);   // 读取第一个字节判断是否标志字节   byte b = ipFile.readByte();   if(b == REDIRECT_MODE_1) {    // 读取国家偏移    long countryOffset = readLong3();    // 跳转至偏移处    ipFile.seek(countryOffset);    // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向    b = ipFile.readByte();    if(b == REDIRECT_MODE_2) {     loc.country = readString(readLong3());     ipFile.seek(countryOffset + 4);    } else     loc.country = readString(countryOffset);    // 读取地区标志    loc.area = readArea(ipFile.getFilePointer());   } else if(b == REDIRECT_MODE_2) {    loc.country = readString(readLong3());    loc.area = readArea(offset + 8);   } else {    loc.country = readString(ipFile.getFilePointer() - 1);    loc.area = readArea(ipFile.getFilePointer());   }   return loc;  } catch (IOException e) {   return null;  } } 

 /**  * 从offset偏移开始解析后面的字节,读出一个地区名  * @param offset 地区记录的起始偏移  * @return 地区名字符串  * @throws IOException 地区名字符串  */ private String readArea(long offset) throws IOException {  ipFile.seek(offset);  byte b = ipFile.readByte();  if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {   long areaOffset = readLong3(offset + 1);   if(areaOffset == 0)    return LumaQQ.getString("unknown.area");   else    return readString(areaOffset);  } else   return readString(offset); }

 /**  * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法  * 用了这么一个函数来做转换  * @param offset 整数的起始偏移  * @return 读取的long值,返回-1表示读取文件失败  */ private long readLong3(long offset) {  long ret = 0;  try {   ipFile.seek(offset);   ipFile.readFully(b3);   ret |= (b3[0] & 0xFF);   ret |= ((b3[1] << 8) & 0xFF00);   ret |= ((b3[2] << 16) & 0xFF0000);   return ret;  } catch (IOException e) {   return -1;  } } 

 /**  * 从当前位置读取3个字节转换成long  * @return 读取的long值,返回-1表示读取文件失败  */ private long readLong3() {  long ret = 0;  try {   ipFile.readFully(b3);   ret |= (b3[0] & 0xFF);   ret |= ((b3[1] << 8) & 0xFF00);   ret |= ((b3[2] << 16) & 0xFF0000);   return ret;  } catch (IOException e) {   return -1;  } }

 /**  * 从offset偏移处读取一个以0结束的字符串  * @param offset 字符串起始偏移  * @return 读取的字符串,出错返回空字符串  */ private String readString(long offset) {  try {   ipFile.seek(offset);   int i;   for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte());   if(i != 0)        return Utils.getString(buf, 0, i, "GBK");  } catch (IOException e) {         log.error(e.getMessage());  }  return ""; }

代码并不复杂,getIPLocation是主要方法,它检查国家记录格式,并针对字符串形式,模式1,模式2采用不同的代码,readArea则相对简单,因为只有字符串和重定向两种情况需要处理。

总结

纯真IP数据库的结构使得查找IP简单迅速,不过你想要编辑它却是比较麻烦的,我想应该需要专门的工具来生成QQWry.dat文件,由于其文件格式的限制,你要直接添加IP记录就不容易了。不过,能查到IP已经很开心了,希望纯真记录越来越多~。

#include <sio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <net/if_arp.h>

#define MAXINTERFACES 16

main (argc, argv)
register int argc;
register char *argv[];
{
register int fd, inface, retn = 0;
suct ifreq buf[MAXINTERFACES];
suct arpreq arp;
suct ifconf ifc;

if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) >= 0) {
ifc.ifc_len = sizeof buf;
ifc.ifc_buf = (caddr_t) buf;
if (!ioctl (fd, SIOCGIFCONF, (char *) &ifc)) {
inface = ifc.ifc_len / sizeof (suct ifreq);
printf(”interface num is inface=%d”,inface);
while (inface– > 0)
{
printf (”net device %s”, buf[inface].ifr_name);

/*Jugde whether the net card status is promisc*/
if (!(ioctl (fd, SIOCGIFFLAGS, (char *) &buf[inface]))) {
if (buf[inface].ifr_flags & IFF_PROMISC) {
puts (”the interface is PROMISC”);
retn ;
}
} else {
char s[256];

sprintf (s, “cpm: ioctl device %s”, buf[inface].ifr_name);
perror (s);
}

/*Jugde whether the net card status is up*/
if (buf[inface].ifr_flags & IFF_UP) {
puts(”the interface status is UP”);
}
else {
puts(”the interface status is DOWN”);
}

/*Get IP of the net card */
if (!(ioctl (fd, SIOCGIFADDR, (char *) &buf[inface])))
{
puts (”IP address is:”);
puts(inet_ntoa(((suct sockaddr_in*)(&buf[inface].ifr_addr))->sin_addr));
puts(”");
//puts (buf[inface].ifr_addr.sa_data);
}
else {
char s[256];

sprintf (s, “cpm: ioctl device %s”, buf[inface].ifr_name);
perror (s);
}

/*Get HW ADDRESS of the net card */
if (!(ioctl (fd, SIOCGIFHWADDR, (char *) &buf[inface])))
{
puts (”HW address is:”);

printf(”x:x:x:x:x:x”,
(unsigned char)buf[inface].ifr_hwaddr.sa_data[0],
(unsigned char)buf[inface].ifr_hwaddr.sa_data[1],
(unsigned char)buf[inface].ifr_hwaddr.sa_data[2],
(unsigned char)buf[inface].ifr_hwaddr.sa_data[3],
(unsigned char)buf[inface].ifr_hwaddr.sa_data[4],
(unsigned char)buf[inface].ifr_hwaddr.sa_data[5]);

puts(”");
puts(”");
}

else {
char s[256];

sprintf (s, “cpm: ioctl device %s”, buf[inface].ifr_name);
perror (s);
}
}
} else
perror (”cpm: ioctl”);

} else
perror (”cpm: socket”);

close (fd);
return retn;
}

没有评论 »