关于作者

姓名:

性别:男

出生日期:1900-01-01

地区:

联系电话:

QQ:5201706婚否:保密
用户名:pialion
笔名:钢琴狮
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



link

访问统计:
文章个数:103
评论个数:19
留言条数:8




Powered by BlogDriver 2.1

片断

 

欢迎你的到来!
向右 向左
累积片断,知其然,寻其所以然.

文章

本博客已迁移  (作者置顶)

本博客停止更新,继续关注,请访问:

http://viadolorosa.bokee.com

- 作者: 钢琴狮 2009年05月4日, 星期一 21:22  回复(0) |  引用(0) 加入博采

片断随想  (作者置顶)

     有时候会想,世界上无处不在的片断,纸是书的片断,叶子是树的片断,双手是身体的片断,生命是历史的片断......太多太多
      生活中常常追求一些大而全的完美,殊不知,在这个浩淼的宇宙,什么就仅仅该是什么;常常觉得很可笑,却从来不曾停止这种追求.

-- 感悟,随笔,仅此而已
2005-08-25 pm

- 作者: 钢琴狮 2005年08月25日, 星期四 12:47  回复(1) |  引用(0) 加入博采

因情制宜,建立“适当”的索引

一、因情制宜,建立适当的索引

  建立适当的索引是实现查询优化的首要前提。

  索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用适当的索引的效果就越来越明显。注意,在这句话中,我们用了适当这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。

  (一)深入浅出理解索引结构

  实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:

  其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查字,就会很自然地翻开字典的前几页,因为的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查字,那您也会将您的字典翻到最后部分,因为的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。

  我们把这种正文内容本身就是一种按照一定规则排列的目录称为聚集索引

  如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据偏旁部首查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合部首目录检字表而查到的字的排序并不是真正的正文的排序方法,比如您查字,我们可以看到在查部首之后的检字表中的页码是672页,检字表中的上面是字,但页码却是63页,的下面是字,页面是390页。很显然,这些字并不是真正的分别位于字的上下方,现在您看到的连续的驰、张、弩三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。

  我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为非聚集索引

  通过以上例子,我们可以理解到什么是聚集索引非聚集索引

  进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。

(二)何时使用聚集索引或非聚集索引

  下面的表总结了何时使用聚集索引或非聚集索引(很重要)。

  动作描述
   使用聚集索引
   使用非聚集索引
 
  列经常被分组排序
   应
   应
 
  返回某范围内的数据
   应
   不应
 
  一个或极少不同值
   不应
   不应
 
  小数目的不同值
   应
   不应
 
  大数目的不同值
   不应
   应
 
  频繁更新的列
   不应
   应
 
  外键列
   应
   应
 
  主键列
   应
   应
 
  频繁修改索引列
   不应
   应
 

  事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询200411日至2004101日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

(三)结合实际,谈索引使用的误区

  理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。

  1、主键就是聚集索引

  这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。

  通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。

  显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。

  从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合大数目的不同值情况下不应建立聚合索引规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

  在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是日期还有用户本身的用户名

  通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过日期这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。

  在这里之所以提到理论上三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在日期/span>这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):

  (1)仅在主键上建立聚集索引,并且不划分时间段:

Select gid,fariqi,neibuyonghu,title from tgongwen

  用时:128470毫秒(即:128秒)

  (2)在主键上建立聚集索引,在fariq上建立非聚集索引:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

  用时:53763毫秒(54秒)

  (3)将聚合索引建立在日期列(fariqi)上:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

  用时:2423毫秒(2秒)

  虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第12/span>种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。

  得出以上速度的方法是:在各个select语句前加:declare @d datetime

set @d=getdate()

并在select语句后加:

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())

  2、只要建立索引就能显著提高查询速度

  事实上,我们可以发现上面的例子中,第23条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。

  从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:既不能绝大多数都相同,又不能只有极少数相同的规则。由此看来,我们建立适当的聚合索引对于我们提高查询速度是非常重要的。

  3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度

  上面已经谈到:在进行数据查询时都离不开字段的是日期还有用户本身的用户名。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。

  很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)

  (1select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'

  查询速度:2513毫秒

  (2select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='办公室'

  查询速度:2516毫秒

  (3select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室'

  查询速度:60280毫秒

  从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句12的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成索引覆盖,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。

(四)其他书上没有的索引使用经验总结

  1、用聚合索引比用不是聚合索引的主键速度快

  下面是实例语句:(都是提取25万条数据)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

  使用时间:3326毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000

  使用时间:4470毫秒

  这里,用聚合索引比用不是聚合索引的主键速度快了近1/4

  2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi

  用时:12936

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid

  用时:18843

  这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。

  3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'

  用时:6343毫秒(提取100万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'

  用时:3170毫秒(提取50万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

  用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'

  用时:3280毫秒

  4 、日期列不会因为有分秒的输入而减慢查询速度

  下面的例子中,共有100万条数据,200411日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi

  用时:6390毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi

  用时:6453毫秒

  (五)其他注意事项

  水可载舟,亦可覆舟,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。

  所以说,我们要建立一个适当的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。

  当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。

- 作者: 钢琴狮 2007年05月8日, 星期二 22:24  回复(0) |  引用(0) 加入博采

[转]MSDE1433端口未监听问题

前天在公司的一台测试机器上装了一个MSDE,操作系统为windows xp + sp2, 装完以后就匆匆忙忙赶去打球了,结果昨天有人跟我说从别的机器去连接这台机器的数据库无法连接.

我去看了一下netstat,果然监听的端口中没有1433...

上Google查资料的时候在网页快照中看到MS的一个MVP给了个解决的办法,share在这里一下^_^

1.首先运行cliconfg,看enable的协议中是否有TCP/IP和Name Pipes这两项,如果没有,那么添加;

2.如果已经有这两项是启用的,那么运行命令svrnetcn这个命令,看在Enable的协议中是否添加了TCP/IP,(我装的那台机器出的原因就在这里~);

3.如果这个也已经是在右边的enable窗口里,那么看一下TCP/IP的属性,端口是否为1433,注意,这个时候添加TCP/IP的时候不要把下面的那个checkbox选起来;

4.所有的这一切做完后,在service manager中重启sql server.

之后就一切正常啦:P

- 作者: 钢琴狮 2007年04月5日, 星期四 16:10  回复(0) |  引用(0) 加入博采

转:如何在SqlServer与oracel中进行分页的讨论!

使用sql,和oracle数据库进行分页可以有以下三种方法!

下面让我们看一看如果我们要在数据库中取第1000条到第1010条的数据这两种方法是怎么实现的.
1.        使用临时表的方法. (在系统中主要是直接写Sql语句来做)
a)          按所需的排序方式排好序
b)         创建临时表
c)          从数据库里取出第0条 到 第1010条的数据
d)         把这些数据放入临时表中
e)          把临时表再按与 a) 相反的排序方式排好序
f)          然后只需把临时表中的前10条显时出来
g)          销毁临时表

2.        使用 object 的方法
a)          按所需的排序方式排好序
b)         从数据库里取出第0条 到 第1010条的数据
c)          倒着从这1010条数据中取10条 放入一个 object中
d)         把这个 object里的记录 完全倒置一下
e)          把 object里的数据显示出来


显然 第二种 方法优于第一种方法 它减少了系统创建, 销毁临时表所需耗费的资源, 但是它们都有一个共同的弱点. 那就是 它们都要从数据库里取出第0条 到 第1010条的数据  这样就造成了 查询出的记录数很少,但网络传输数据量很大!

因此比较好的分页做法应该是:
每次翻页的时候只从数据库里检索页面大小的块区的数据。这样虽然每次翻页都需要查询数据库,但查询出的记录数很少,网络传输数据量不大,如果使用连接池更可以略过最耗时的建立数据库连接过程。而在数据库端有各种成熟的优化技术用于提高查询速度,比在应用服务器层做缓存有效多了。

对于SqlServer 数据库 如要到得第1000-1010条记录:
Select top 10  * from (
Select top 10  * from (
     Select top 1010 * from docdetail order by lastmodidate asc ,Id asc
) temptbl1 order by lastmodidate desc ,Id desc
) temptbl2 order by lastmodidate asc,Id asc

对于oracle 数据库 如要到得第1000-1010条记录 由于oracle中的rownum是在查询之后排序之前赋值的.所以其相应的写法应为:
 select * from (
         select my_table.*, rownum as temptbl_rownum from (
                   Select * from docdetail order by lastmodidate asc,Id asc
      ) temptbl where rownum <1010
 ) where  temptbl_rownum >=1000

当以上的Sql语句执行完成以后, 网络传输数据量就从以前的1010条减少到 10条

通过以上分页方式的改变,对我们系统的性能有很大的提升

我有个客户使用的是oracel数据库 其中文档数目为 12万条 ,当我们对这张表时行搜索的时候 使用第一种方法进行分页时,页面显示的时间约为10秒左右,而使用第三种方法而现在页面显示时间只需要2-3秒左右.
当然,可能还有更好的分页方法,总觉得随着数据库里的数据的不断增加,系统运行的速度将会变慢,我在这里贴出这篇文章,只是想和大家讨论一下,还有没有更好的方法, 希望大家不吝回复! 一起讨论!

:D (完)

[点击此处收藏本文]
发表于 2005年04月12日 9:35 AM


随便说说 发表于2005-04-14 2:42 PM
1.
select my_table.*, rownum as temtbl_rownum from (
Select * from docdetail order by lastmodidate asc,Id asc
) temptbl where rownum <1010 and rownum >=1000

2.
select * from docdetail rownum <1010
minus
select * from docdetail rownum <1000

- 作者: 钢琴狮 2007年04月5日, 星期四 14:59  回复(0) |  引用(0) 加入博采

转:身份證复印件的正确用法,谨防盗用!!!

各位:

下面的事情一定要提起注意.我一个朋友老公是警察,

也听他说过,他们接到1女士报案,说是别墅被卖掉了.

后来一查发现,她把身份证复印件交给过中介,他们就是

用复印件伪造了正本把人家的房子给骗了,中介找不到了.

身份证附件件给别人的时候,自己一定要在上面签字.

 

有时难免要将身份证复印件交予他人如购车,保险。。。等等,以下是司法部上班的朋友转寄的。。。。参考参考!! 身份证复印件正确签注写法!正确写法如下:

 身份证复印件签注写法:

 身份证复印件记得要签注写法是分三行

 仅提供XX银行--------------

 申请XX基金扣帐-----------

 他用无效-----------------------

  用蓝色原子笔,部分笔画与身份证的字交叉或接触,每一行后面一定要划上横线,以免被偷加其它文字。                                                  

 无论是信用卡,基金,手机。。。。。。申请书,只要须附身份证复印件的,一律照办,go-vern-ment的表格也一样,另外,申请书尚未填写的空格,如:附卡 申请,加买保险,加买第二支基金,申请手机号等,这些空下的字段都必须画叉叉,以免被不法业者补填。

 这个真的是很重要…如果你有修过相关法律课程或票据课程你就知道,所以…

大家小心点,身份证复印件记得要签名呦!

 兹提供生活上便利的小方法保障自已的权利,你拿身份证复印件去办信用卡,记得在复印件上写:[此身份证复印件,仅为申请XX银行XX信用卡用,不得转为其它用途]。﹙XXX签名﹚,再提醒你,上述文字一定要签在身份证的范围内,但不要遮住IDNo(身份证字号)及姓名,请尽您所能的传阅出去,因为事关许多人的权益,也  是为防止不法之徒另作他用。

 在别的地方看到的。觉得很有道理。或许很多人懂了。但是我相信还有些没注意到这个细节的人吧```... 

from:http://my.6sq.net/54079/viewspace_16758.html

- 作者: 钢琴狮 2007年03月27日, 星期二 09:31  回复(0) |  引用(0) 加入博采

15位身份证号码转18位算法

//15位身份证号码转18位算法                                      
class CIDCardNumber
{
// 最后的18位身份证号码
TCHAR szIdCard18 [MAX_PATH];
// 经过初次变换后的15位身份证号码
char ai[18];
public:
/*
功能:15位号码转换为18位
参数:chCodeNumber原15位身份证号码
*/
char * IDCard15To18 (const char * const chCodeNumber )
{
// 初次变换,把出生年由2位变为4位
// 最后一位a代表未知 。
ai[ 0] = chCodeNumber [0 ] ;
ai[ 1] = chCodeNumber [1 ] ;
ai[ 2] = chCodeNumber [2 ] ;
ai[ 3] = chCodeNumber [3 ] ;
ai[ 4] = chCodeNumber [4 ] ;
ai[ 5] = chCodeNumber [5 ] ;
ai[ 6] = '1' ;
ai[ 7] = '9' ;
ai[ 8] = chCodeNumber [6 ] ;
ai[ 9] = chCodeNumber [7 ] ;
ai[10] = chCodeNumber [8 ] ;
ai[11] = chCodeNumber [9 ] ;
ai[12] = chCodeNumber [10] ;
ai[13] = chCodeNumber [11] ;
ai[14] = chCodeNumber [12] ;
ai[15] = chCodeNumber [13] ;
ai[16] = chCodeNumber [14] ;
ai[17] = 'a' ;

// 以下计算最后一位a

// 加权系数
const short shModulus[18] =
{ 7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1} ;

int sum = 0 ;
// 初次变换后的身份证号码和加权系数对应位积的和
for ( int i =0 ; i < 17 ; i ++ )
{
TCHAR szTemp[MAX_PATH];
_stprintf ( szTemp , TEXT ( "%c" ) , ai ) ;
sum += ( atoi ( szTemp ) * shModulus );
}

// 查表,对应sum的值,查下表
const char chTab[11] =
{'1','0','X','9','8','7','6','5','4','3','2'};

// 把查表所得的值赋给ai的未知数a
ai [17] = chTab [ sum % 11 ] ;

_stprintf ( szIdCard18 , TEXT ( "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" ) ,
ai[0] , ai[1 ], ai[2] , ai[3] , ai[4] , ai[5] , ai[6] , ai[7] ,
ai[8] , ai[9] , ai[10] , ai[11] , ai[12] , ai[13] , ai[14] ,
ai[15] , ai[16] ,ai[17] ) ;

// 计算完毕,返回18位身份证号码
return szIdCard18 ;
}
};

- 作者: 钢琴狮 2007年03月27日, 星期二 09:00  回复(0) |  引用(0) 加入博采

转:什么叫哈希表(Hash Table)
      google搜索到的头条:散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
      我觉得这个解释太含糊,想要整明白哈希表,那就得明白哈希表到底有什么样的优势。
      数据结构中,有个时间算法复杂度O(n)的概念来衡量某种算法在时间效率上的优劣。哈希表的理想算法复杂度为O(1),也就是说利用哈希表查找某个值,系统所使用的时间在理想情况下为定值,这就是它的优势。那么哈希表是如何做到这一点的呢?
      我们定义一个很大的有序数组,想要得到位于该数组第n个位置的值,它的算法复杂度为O(1)。哈希表利用哈希函数将需要存储的内容的关键值转换为这个有序数组中的某个值,在被存储内容和有序数组之间建立了映射关系。这样,下次我们对这个值进行查找时只要使用同一个哈希函数对关键值进行转换,找到这个数组值就可以了。
      如果还没有明白是怎么回事的话,那我们来举个例子。假设我们要做个存储结构,需要存储下来三国中的人物,以及他们的详细信息。我们用他们的名字来作为存储的关键值,例如:刘备,曹操,孙权,关羽,张飞……等等。这个时候我们如果想用一般的方法来查找这些英雄豪杰,需要遍历整个存储空间,如果这些英雄豪杰一共有n个,那么这时候的时间算法复杂度为O(n)。显然如果n值很大,每次想要找到某个英雄就需要比较长的时间。
      此时我们先定义一个大的有序结构数组HashValue[m],用来存放各位英雄豪杰的信息。然后编写一个哈希函数ChangeToHashValue(name),函数的具体内容就不细说了,反正这个函数会将这些做为关键值的名字转换为HashValue[m]中的某个下标值x。然后可以将英雄的信息放进HashValue[x]中去。这样,可以将所有英雄的信息存储起来。当查询的时候再使用哈希函数ChangeToHashValue(name)得到这个下标值,这样就很容易得到了这个英雄的信息。例如:ChangeToHashValue(刘备)为10,那么就将刘备存储到HashValue[10]里面。当查询的时候再次使用ChangeToHashValue(刘备)得到10,这个时候我们就可以很容易找到刘备的所有信息。在实际应用中如果我们想把所有的英雄豪杰都存储进系统时,需要定义m>n。就是数组的大小要大于需要存储的信息量,所以说哈希表是一个以空间换取时间的数据结构。
      这个时候问题来了,出现了这种情况ChangeToHashValue(关羽)和ChangeToHashValue(张飞)得到的值是一样的,都是250,我们岂不是在存储过程中会遇到麻烦,怎么安排他们二位的地方呢(总不能让二位打一架,谁赢了谁呆在那吧),这就需要一个解决冲突的方法。当遇到这种情况时我们可以这样处理,先存储好了关羽,当张飞进入系统时会发现关羽已经是250了,那咱就加一位,251得了,这不就解决了。我们查找张飞的时候也是,一看250不是张飞,那就加个1,就找到了。这时还存在一个问题。直接用ChangeToHashValue(赵云)为251,张飞已经早早占了他的地方,那就再加1存到252呗。呵呵,这时我们会发现,当哈希函数冲突发生的机率很高时,可能会有一群英雄豪杰在250这个值后面扎堆排队。要命的是查找的时候,时间算法复杂度早已不是O(1)了(所以我们说理想情况下哈希表的时间算法复杂度为O(1))。
      这就是说哈希函数的编写是哈希表的一个关键问题,会涉及到一个存储值在哈希表中的统计分布。如果哈希函数已经定义好了,冲突的解决就成为了改变系统性能的关键因素。其实还有很多种方法来解决冲突情况下的存储和查找问题,不一定非要线性向后排队,如果有好的哈希表冲突的解决方法也能很大程度上提高系统的效率。
      好了,写到这里,哈希表的概念应该搞清楚了吧。今天咱这也是现学现卖,其实我还没有使用过这个数据结构。有不对的地方还请高手指出来,耽误了我自己不怕,免得误导了别人。
from:

- 作者: 钢琴狮 2007年03月2日, 星期五 16:35  回复(2) |  引用(0) 加入博采

将日期转换成农历

Option Explicit
Dim WeekName(7), MonthAdd(11), NongliData(99), TianGan(9), DiZhi(11), ShuXiang(11), DayName(30), MonName(12)
Dim curTime, curYear, curMonth, curDay, curWeekday
Dim GongliStr, WeekdayStr, NongliStr, NongliDayStr
Dim i, m, n, k, isEnd, bit, TheDate
Dim year, month, day, grid, grid_date

'grid参数形式: yyyy-mm-dd
'函数返回农历日期字符串
Function calendar(grid)
  grid_date = grid
  year = Mid(grid_date, 1, 4)
  month = Mid(grid_date, 6, 2)
  day = Mid(grid_date, 9, 2)
  '星期名
  WeekName(0) = " * "
  WeekName(1) = "星期日"
  WeekName(2) = "星期一"
  WeekName(3) = "星期二"
  WeekName(4) = "星期三"
  WeekName(5) = "星期四"
  WeekName(6) = "星期五"
  WeekName(7) = "星期六"
  '天干名称
  TianGan(0) = "甲"
  TianGan(1) = "乙"
  TianGan(2) = "丙"
  TianGan(3) = "丁"
  TianGan(4) = "戊"
  TianGan(5) = "己"
  TianGan(6) = "庚"
  TianGan(7) = "辛"
  TianGan(8) = "壬"
  TianGan(9) = "癸"
  '地支名称
  DiZhi(0) = "子"
  DiZhi(1) = "丑"
  DiZhi(2) = "寅"
  DiZhi(3) = "卯"
  DiZhi(4) = "辰"
  DiZhi(5) = "巳"
  DiZhi(6) = "午"
  DiZhi(7) = "未"
  DiZhi(8) = "申"
  DiZhi(9) = "酉"
  DiZhi(10) = "戌"
  DiZhi(11) = &quo;亥"
  '属相名称
  ShuXiang(0) = "鼠"
  ShuXiang(1) = "牛"
  ShuXiang(2) = "虎"
  ShuXiang(3) = "兔"
  ShuXiang(4) = "龙"
  ShuXiang(5) = "蛇"
  ShuXiang(6) = "马"
  ShuXiang(7) = "羊"
  ShuXiang(8) = "猴"
  ShuXiang(9) = "鸡"
  ShuXiang(10) = "狗"
  ShuXiang(11) = "猪"
  '农历日期名
  DayName(0) = "*"
  DayName(1) = "初一"
  DayName(2) = "初二"
  DayName(3) = "初三"
  DayName(4) = "初四"
  DayName(5) = "初五"
  DayName(6) = "初六"
  DayName(7) = "初七"
  DayName(8) = "初八"
  DayName(9) = "初九"
  DayName(10) = "初十"
  DayName(11) = "十一"
  DayName(12) = "十二"
  DayName(13) = "十三"
  DayName(14) = "十四"
  DayName(15) = "十五"
  DayName(16) = "十六"
  DayName(17) = "十七"
  DayName(18) = "十八"
  DayName(19) = "十九"
  DayName(20) = "二十"
  DayName(21) = "二十一"
  DayName(22) = "二十二"
  DayName(23) = "二十三"
  DayName(24) = "二十四"
  DayName(25) = "二十五"
  DayName(26) = "二十六"
  DayName(27) = "二十七"
  DayName(28) = "二十八"
  DayName(29) = "二十九"
  DayName(30) = "三十"
  '农历月份名
  MonName(0) = "*"
  MonName(1) = "正"
  MonName(2) = "二"
  MonName(3) = "三"
  MonName(4) = "四"
  MonName(5) = "五"
  MonName(6) = "六"
  MonName(7) = "七"
  MonName(8) = "八"
  MonName(9) = "九"
  MonName(10) = "十"
  MonName(11) = "十一"
  MonName(12) = "腊"
  '公历每月前面的天数
  MonthAdd(0) = 0
  MonthAdd(1) = 31
  MonthAdd(2) = 59
  MonthAdd(3) = 90
  MonthAdd(4) = 120
  MonthAdd(5) = 151
  MonthAdd(6) = 181
  MonthAdd(7) = 212
  MonthAdd(8) = 243
  MonthAdd(9) = 273
  MonthAdd(10) = 304
  MonthAdd(11) = 334
  '农历数据
  NongliData(0) = 2635
  NongliData(1) = 333387
  NongliData(2) = 1701
  NongliData(3) = 1748
  NongliData(4) = 267701
  NongliData(5) = 694
  NongliData(6) = 2391
  NongliData(7) = 133423
  NongliData(8) = 1175
  NongliData(9) = 396438
  NongliData(10) = 3402
  NongliData(11) = 3749
  NongliData(12) = 331177
  NongliData(13) = 1453
  NongliData(14) = 694
  NongliData(15) = 201326
  NongliData(16) = 2350
  NongliData(17) = 465197
  NongliData(18) = 3221
  NongliData(19) = 3402
  NongliData(20) = 400202
  NongliData(21) = 2901
  NongliData(22) = 1386
  NongliData(23) = 267611
  NongliData(24) = 605
  NongliData(25) = 2349
  NongliData(26) = 137515
  NongliData(27) = 2709
  NongliData(28) = 464533
  NongliData(29) = 1738
  NongliData(30) = 2901
  NongliData(31) = 330421
  NongliData(32) = 1242
  NongliData(33) = 2651
  NongliData(34) = 199255
  NongliData(35) = 1323
  NongliData(36) = 529706
  NongliData(37) = 3733
  NongliData(38) = 1706
  NongliData(39) = 398762
  NongliData(40) = 2741
  NongliData(41) = 1206
  NongliData(42) = 267438
  NongliData(43) = 2647
  NongliData(44) = 1318
  NongliData(45) = 204070
  NongliData(46) = 3477
  NongliData(47) = 461653
  NongliData(48) = 1386
  NongliData(49) = 2413
  NongliData(50) = 330077
  NongliData(51) = 1197
  NongliData(52) = 2637
  NongliData(53) = 268877
  NongliData(54) = 3365
  NongliData(55) = 531109
  NongliData(56) = 2900
  NongliData(57) = 2922
  NongliData(58) = 398042
  NongliData(59) = 2395
  NongliData(60) = 1179
  NongliData(61) = 267415
  NongliData(62) = 2635
  NongliData(63) = 661067
  NongliData(64) = 1701
  NongliData(65) = 1748
  NongliData(66) = 398772
  NongliData(67) = 2742
  NongliData(68) = 2391
  NongliData(69) = 330031
  NongliData(70) = 1175
  NongliData(71) = 1611
  NongliData(72) = 200010
  NongliData(73) = 3749
  NongliData(74) = 527717
  NongliData(75) = 1452
  NongliData(76) = 2742
  NongliData(77) = 332397
  NongliData(78) = 2350
  NongliData(79) = 3222
  NongliData(80) = 268949
  NongliData(81) = 3402
  NongliData(82) = 3493
  NongliData(83) = 133973
  NongliData(84) = 1386
  NongliData(85) = 464219
  NongliData(86) = 605
  NongliData(87) = 2349
  NongliData(88) = 334123
  NongliData(89) = 2709
  NongliData(90) = 2890
  NongliData(91) = 267946
  NongliData(92) = 2773
  NongliData(93) = 592565
  NongliData(94) = 1210
  NongliData(95) = 2651
  NongliData(96) = 395863
  NongliData(97) = 1323
  NongliData(98) = 2707
  NongliData(99) = 265877
  '生成当前公历年、月、日 ==> GongliStr
  curYear = year
  curMonth = month
  curDay = day
  GongliStr = curYear & "年"
  If (curMonth < 10) Then
      GongliStr = GongliStr & "0" & curMonth & "月"
  Else
      GongliStr = GongliStr & curMonth & "月"
  End If
  If (curDay < 10) Then
      GongliStr = GongliStr & "0" & curDay & "日"
  Else
      GongliStr = GongliStr & curDay & "日"
  End If
  '生成当前公历星期 ==> WeekdayStr
  curWeekday = Weekday(curTime)
  WeekdayStr = WeekName(curWeekday)
  '计算到初始时间1921年2月8日的天数:1921-2-8(正月初一)
  TheDate = (curYear - 1921) * 365 + Int((curYear - 1921) / 4) + curDay + MonthAdd(curMonth - 1) - 38
  If ((curYear Mod 4) = 0 And curMonth > 2) Then
      TheDate = TheDate + 1
  End If
  '计算农历天干、地支、月、日
  isEnd = 0
  m = 0
  Do
      If (NongliData(m) < 4095) Then
          k = 11
      Else
          k = 12
      End If
      n = k
      Do
          If (n < 0) Then
              Exit Do
       &nbp;  End If
      '获取NongliData(m)的第n个二进制位的值
      bit = NongliData(m)
      For i = 1 To n Step 1
          bit = Int(bit / 2)
      Next
      bit = bit Mod 2
      If (TheDate <= 29 + bit) Then
          isEnd = 1
          Exit Do
      End If
      TheDate = TheDate - 29 - bit
      n = n - 1
    Loop
    If (isEnd = 1) Then
        Exit Do
    End If
    m = m + 1
  Loop
  curYear = 1921 + m
  curMonth = k - n + 1
  curDay = TheDate
  If (k = 12) Then
      If (curMonth = (Int(NongliData(m) / 65536) + 1)) Then
          curMonth = 1 - curMonth
      ElseIf (curMonth > (Int(NongliData(m) / 65536) + 1)) Then
          curMonth = curMonth - 1
      End If
  End If
  '生成农历天干、地支、属相 ==> NongliStr
  NongliStr = "农历" & TianGan(((curYear - 4) Mod 60) Mod 10) & DiZhi(((curYear - 4) Mod 60) Mod 12) & "年"
  NongliStr = NongliStr & "(" & ShuXiang(((curYear - 4) Mod 60) Mod 12) & ""
  '生成农历月、日 ==> NongliDayStr
  If (curMonth < 1) Then
      NongliDayStr = "闰" & MonName(-1 * curMonth)
  Else
      NongliDayStr = MonName(curMonth)
  End If
  NongliDayStr = NongliDayStr & "月"
  NongliDayStr = NongliDayStr & DayName(curDay)
  calendar = NongliStr & NongliDayStr & ")"
End Function

- 作者: 钢琴狮 2007年03月2日, 星期五 11:33  回复(0) |  引用(0) 加入博采

数字转大写的函数

EXCEL中隐藏了一个数字转大写的函数NUMBERSTRING,但它仅支持正整数,用中文版的朋友可以试试这个函数:

=NUMBERSTRING(VALUE,TYPE)

NumberString(1234567890,1) = 一十二亿三千四百五十六万七千八百九十

NumberString(1234567890,2) = 壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾

NumberString(1234567890,3) = 一二三四五六七八九○

利用参数2,我们就可以用来完成人民币小写转化为大写,设数据在A1格,公式如下(由baohulu收集):

=IF(ISERROR(FIND(".",A1)),NUMBERSTRING(INT(A1),2)&"元整",IF(ISERROR(NUMBERSTRING(MID(A1,FIND(".",A1)+2,1),2)),NUMBERSTRING(INT(A1),2)&"元"&NUMBERSTRING(MID(A1,FIND(".",A1)+1,1),2)&"角整",NUMBERSTRING(INT(A1),2)&"元"&NUMBERSTRING(MID(A1,FIND(".",A1)+1,1),2)&"角"&NUMBERSTRING(MID(A1,FIND(".",A1)+2,1),2)&"分"))

当公式整数部份最大为15位时正确,结果舍去分(小数点第二位)以下数据。

- 作者: 钢琴狮 2007年03月2日, 星期五 11:21  回复(0) |  引用(0) 加入博采