问题描述:
这是在网上找到的一道百度的面试题:
搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。
问题解析:
【分析】:要统计最热门查询,首先就是要统计每个Query出现的次数,然后根据统计结果,找出Top 10。所以我们可以基于这个思路分两步来设计该算法。下面分别给出这两步的算法:
第一步:Query统计
算法一:直接排序法
首先我们能想到的算法就是排序了,首先对这个日志里面的所有Query都进行排序,然后再遍历排好序的Query,统计每个Query出现的次数了。但是题目中有明确要求,那就是内存不能超过1G,一千万条记录,每条记录是225Byte,很显然要占据2.55G内存,这个条件就不满足要求了。
让我们回忆一下数据结构课程上的内容,当数据量比较大而且内存无法装下的时候,我们可以采用外排序的方法来进行排序,这里笔者采用归并排序,是因为归并排序有一个比较好的时间复杂度O(NlgN)。
排完序之后我们再对已经有序的Query文件进行遍历,统计每个Query出现的次数,再次写入文件中。
综合分析一下,排序的时间复杂度是O(NlgN),而遍历的时间复杂度是O(N),因此该算法的总体时间复杂度就是O(NlgN)。
算法二:Hash Table法
在上个方法中,我们采用了排序的办法来统计每个Query出现的次数,时间复杂度是NlgN,那么能不能有更好的方法来存储,而时间复杂度更低呢?
题目中说明了,虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把他们都放进内存中去,而现在只是需要一个合适的数据结构,在这里,Hash Table绝对是我们优先的选择,因为Hash Table的查询速度非常的快,几乎是O(1)的时间复杂度。
那么,我们的算法就有了:维护一个Key为Query字串,Value为该Query出现次数的HashTable,每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内完成了对该海量数据的处理。
本方法相比算法一:在时间复杂度上提高了一个数量级,但不仅仅是时间复杂度上的优化,该方法只需要IO数据文件一次,而算法一的IO次数较多的,因此该算法比算法一在工程上有更好的可操作性。
第二步:找出Top 10
算法一:排序
我想对于排序算法大家都已经不陌生了,这里不在赘述,我们要注意的是排序算法的时间复杂度是NlgN,在本题目中,三百万条记录,用1G内存是可以存下的。
算法二:部分排序
题目要求是求出Top 10,因此我们没有必要对所有的Query都进行排序,我们只需要维护一个10个大小的数组,初始化放入10Query,按照每个Query的统计次数由大到小排序,然后遍历这300万条记录,每读一条记录就和数组最后一个Query对比,如果小于这个Query,那么继续遍历,否则,将数组中最后一条数据淘汰,加入当前的Query。最后当所有的数据都遍历完毕之后,那么这个数组中的10个Query便是我们要找的Top10了。
不难分析出,这样的算法的时间复杂度是N*K, 其中K是指top多少。
算法三:堆
在算法二中,我们已经将时间复杂度由NlogN优化到NK,不得不说这是一个比较大的改进了,可是有没有更好的办法呢?
分析一下,在算法二中,每次比较完成之后,需要的操作复杂度都是K,因为要把元素插入到一个线性表之中,而且采用的是顺序比较。这里我们注意一下,该数组是有序的,一次我们每次查找的时候可以采用二分的方法查找,这样操作的复杂度就降到了logK,可是,随之而来的问题就是数据移动,因为移动数据次数增多了。不过,这个算法还是比算法二有了改进。
基于以上的分析,我们想想,有没有一种既能快速查找,又能快速移动元素的数据结构呢?回答是肯定的,那就是堆。
借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此到这里,我们的算法可以改进为这样,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。。。
那么这样,这个算法发时间复杂度就降到了NlogK,和算法而相比,又有了比较大的改进。
结语:
至此,我们的算法就完全结束了,经过步骤一和步骤二的最优结合,我们最终的时间复杂度是O(N) + O(N')logK。如果各位有什么好的算法,欢迎跟帖讨论。
转自http://blog.redfox66.com/redfox66/blog/post/2010/09/23/top-k-algoriyhm-analysis.aspx
在C#中,
值类型:整型、布尔型、字符型、实数型、结构型、枚举型。
引用类型:类、对象、字符串、数组、接口、委托。
区别:
1、值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高。
2、引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例。
3、值类型继承自ValueType(注意:而System.ValueType又继承自System.Object);而引用类型继承自System.Object。
4、值类型变量包含其实例数据,每个变量保存了其本身的数据拷贝(副本),因此在默认情况下,值类型的参数传递不会影响参数本身;而引用类型变量保存了其数据的引用地址,因此以引用方式进行参数传递时会影响到参数本身,因为两个变量会引用了内存中的同一块地址。
5、值类型有两种表示:装箱与拆箱;引用类型只有装箱一种形式。我会在下节以专门的篇幅来深入讨论这个话题。
6、典型的值类型为:struct,enum以及大量的内置值类型;而能称为类的都可以说是引用类型。
7、值类型的内存不由GC(垃圾回收,Gabage Collection)控制,作用域结束时,值类型会自行释放,减少了托管堆的压力,因此具有性能上的优势。例如,通常struct比class更高效;而引用类型的内存回收,由GC来完成,微软甚至建议用户最好不要自行释放内存。
8、值类型是密封的(sealed),因此值类型不能作为其他任何类型的基类,但是可以单继承或者多继承接口;而引用类型一般都有继承性。
9、值类型不具有多态性;而引用类型有多态性。
10、值类型变量不可为null值,值类型都会自行初始化为0值;而引用类型变量默认情况下,创建为null值,表示没有指向任何托管堆的引用地址。对值为null的引用类型的任何操作,都会抛出NullReferenceException异常。
11、值类型有两种状态:装箱和未装箱,运行库提供了所有值类型的已装箱形式;而引用类型通常只有一种形式:装箱。
ORACLE的分区(Partitioning Option)是一种处理超大型表的技术。分区是一种“分而治之”的技术,通过将大表和索引分成可以管理的小块,从而避免了对每个表作为一个大的、单独的对象进行管理,为大量数据提供了可伸缩的性能。分区通过将操作分配给更小的存储单元,减少了需要进行管理操作的时间,并通过增强的并行处理提高了性能,通过屏蔽故障数据的分区,还增加了可用性。
ORACLE的分区表的划分方法包括:按字段值进行划分的范围分区;按字段的HASH函数值进行的划分HASH分区;先按范围划分,再按HASH划分的复合分区;在ORACLE9i中又增强了按字段值列表进行划分的列表(Listing)分区方法。
管理员可以指定每个分区的存储属性,分区在宿主文件系统中的放置情况,这样便增加了对超大型数据库的控制粒度(granularity)。分区可以被单独地删除、卸出或装入、备份、恢复,因此减少了需要进行管理操作的时间。
还可以为表分区创建单独的索引分区,从而减少了需要进行索引维护操作的时间。此外,还提供了种类繁多的局部和全局的索引技术。分区操作也可以被并行执行。
分区技术还提高了数据的可用性。当部分数据由于故障或其它原因不可用时,其它分区内的数据可用不收影响继续使用。
分区对应用是透明的,可以通过标准的SQL语句对分区表进行操作。Oracle 的优化器在访问数据时会分析数据的分区情况,在进行查询时,那些不包含任何查询数据的分区将被忽略,从而大大提高系统的性能。
分区原则
1.表分区的指南
a、表的大小
对于大表进行分区,将有益于大表操作的性能和大表的数据维护。通常当表的大小超过1.5GB-2GB,或对于OLTP系统,表的记录超过1000万,都应考虑对表进行分区。
b、数据访问特性
基于表的大部分查询应用,只访问表中少量的数据。对于这样表进行分区,可充分利用分区排除无关数据查询的特性。
c、数据维护
某些表的数据维护,经常按时间段删除成批的数据,例如按月删除历史数据。对于这样的表需要考虑进行分区,以满足维护的需要。因为删除(Delete)大量的数据,对系统开销很大,有时甚至是不可接受的。
d、只读数据
如果一个表中大部分数据都是只读数据,通过对表进行分区,可将只读数据存储在只读表空间中,对于数据库的备份是非常有益的。
e、并行数据操作(Parallel DML)
对于经常执行并行操作(如Parallel Insert,Parallel Update等)的表应考虑进行分区。
f、表的可用性
当对表的部分数据可用性要求很高时,应考虑进行表分区。
2.选择分区字段(Partition Key)
当确定分区字段时,有两个主要因素特别需要考虑:
a、增强表的管理和维护性
通过Partition Key,可以使数据维护基于某个分区进行,如Drop或Truncate一个或多个分区。通过Paratition Key可控制只读的数据存储在相应的分区中,且这些分区存储在只读的表空间里,这将提高数据备份的性能。这类Partition Key通常与时间相关。
b、提高访问表的性能
通过Partition Key,可使查询的数据定位在一个或少量的分区中;这需要考虑最常用的查询条件。注意在考虑提高查询效率这个因素的同时,还应兼顾数据维护管理的因素,尽可能地避免相互间地冲突。
版权说明:文章转自http://www.javaeye.com/topic/174265
分区功能能够将表、索引或索引组织表进一步细分为段,这些数据库对象的段叫做分区。每个分区有自己的名称,还可以选择自己的存储特性。从数据库管理员的角度来看,一个分区后的对象具有多个段,这些段既可进行集体管理,也可单独管理,这就使数据库管理员在管理分区后的对象时有相当大的灵活性。但是,从应用程序的角度来看,分区后的表与非分区表完全相同,使用 SQL DML 命令访问分区后的表时,无需任何修改。
比较能理解的是以下几个几种表分区:
1 范围分区
每个分区都由一个分区键值范围指定create table RangeTable(
id int primary key,
name varchar(10),
grade int
)
partition by rang(grade)
(
partition part1 values less then(1000) tablespace Part1_tb,
partition part2 values less then(MAXVALUE) tablespace Part2_tb
);
2 列表分区
create table ListTable(
id int primary key,
name varchar(20),
area varchar(10)
)
partition by list(area)
(
partition part1 values('guangdong','beijing') tablespace Part1_tb,
partition part2 values('shanghai','nanjing') tablespace Part2_tb
);
3 散列分区
create table HashTable(
id int primary key,
name varchar(20),
grade int
)
partition by hash(grade)
partitions 10
store in(Part1_tb,Part2_tb,Part3_tb)
partition by rang(grade)(
partition part1 tablespace Part1_tb,
partition part2 tablespace Part2_tb
);
4 索引分区
create index IndexTable_index
on IndexTable(name)
local
(
partition part1 tablespace Part1_tb,
partition part2 tablespace Part2_tb
)--local 告诉oracle表 IndexTable的每一个分区建立一个独立的索引
create index IndexTable_index
on IndexTable(name)
global;
--global为全局索引 全局索引可以包含多个分区的值 局部索引比全局索引容易管理,而全局索引比较快
注意:不能为散列分区 或者 子分区创建全局索引。
在ORACLE里如果遇到特别大的表,可以使用分区的表来改变其应用程序的性能。
以system身份登陆数据库,查看 v$option视图,如果其中Partition为TRUE,则支持分区功能;否则不支持。Partition有基于范围、哈希、综和三种类型。我们用的比较多的是按范围分区的表。
在ORACLE里如果遇到特别大的表,可以使用分区的表来改变其应用程序的性能。
以system身份登陆数据库,查看 v$option视图,如果其中Partition为TRUE,则支持分区功能;否则不支持。Partition有基于范围、哈希、综和三种类型。我们用的比较多的是按范围分区的表。
我们以一个2001年开始使用的留言版做例子讲述分区表的创建和使用:
1 、以system 身份创建独立的表空间(大小可以根据数据量的多少而定)
create tablespace g_2000q4 datafile '/home/oradata/oradata/test/g_2000q4.dbf' size 50M default storage (initial 100k next 100k minextents 1 maxextents unlimited pctincrease 1);
create tablespace g_2001q1 datafile '/home/oradata/oradata/test/g_2001q1.dbf' size 50M default storage (initial 100k next 100k minextents 1 maxextents unlimited pctincrease 1);
create tablespace g_2001q2 datafile '/home/oradata/oradata/test/g_2001q2.dbf' size 50M default storage (initial 100k next 100k minextents 1 maxextents unlimited pctincrease 1);
2 、用EXPORT工具把旧数据备份在guestbook.dmp中
把原来的guestbook表改名
alter table guestbook rename to guestbookold;
以guestbook 身份创建分区的表
create table guestbook(
id number(16) primary key,
username varchar2(64),
sex varchar2(2),
email varchar2(256),
expression varchar2(128),
content varchar2(4000),
time date,
ip varchar2(64)
)
partition by range (time)
(partition g_2000q4 values less than (to_date('2001-01-01','yyyy-mm-dd'))
tablespace g_2000q4
storage(initial 100k next 100k minextents 1 maxextents unlimited pctincrease 0),
partition g_2001q1 values less than (to_date('2001-04-01','yyyy-mm-dd'))
tablespace g_2001q1
storage(initial 100k next 100k minextents 1 maxextents unlimited pctincrease 0),
partition g_2001q2 values less than (to_date('2001-07-01','yyyy-mm-dd'))
tablespace g_2001q2
storage(initial 100k next 100k minextents 1 maxextents unlimited pctincrease 0)
);
(说明:分区的名称可以和表空间的名称不一致。这里是每个季度做一个分区,当然也可以每个月做一个分区)
3、IMPORT导入数据,参数ignore=y
4、分区表的扩容:
到了2001 年下半年,建立新的表空间:
create tablespace g_2001q3 datafile '/home/oradata/oradata/test/g_2001q3.dbf' size 50m default storage (initial 100k next 100k minextents 1 maxextents unlimited pctincrease 1);
为表添加新分区和表空间:
alter table guestbook add partition g_2001q3
values less than (to_date('2001-10-01','yyyy-mm-dd')
tablespace g_2001q3
storage(initial 100k next 100k minextents 1 maxextents unlimited pctincrease 0);
5、删除不必要的分区
将2000年的数据备份(备份方法见 6、EXPORT 分区),将2000年的分区删除。
alter table guestbook drop partion g_2000q4;
删除物理文件
%rm /home/oradata/oradata/test/g_2000q4.dbf
6、EXPORT 分区:
% exp guestbook/guestbook_password tables=guestbook:g_2000q4 rows=Y file=g_2000q4.dmp
7、IMPORT分区:
例如在2001 年,用户要查看2000 年的数据,先创建表空间
create tablespace g_2000q4 datafile '/home/oradata/oradata/test/g_2000q4.dbf' size 50m default storage (initial 100k next 100k minextents 1 maxextents unlimited pctincrease 1);
为表添加新分区和表空间:
alter table guestbook add partition g_2000q4
values less than (to_date('2001-01-01','yyyy-mm-dd')
tablespace g_2001q3
storage(initial 100k next 100k minextents 1 maxextents unlimited pctincrease 0);
导入数据
%imp guestbook/guestbook_password file=g_2000q4.dmp tables=(guestbook:g_2000q4) ignore=y
(说明:如果不指明导入的分区,imp会自动按分区定义的范围装载数据)
版权说明:文章转自http://yinchunjian.javaeye.com/blog/680063
数据库大表的优化:采用蔟表(clustered tables)及蔟索引(Clustered Index)
蔟表和蔟索引是oracle所提供的一种技术,其基本思想是将几张具有相同数据项、并且经常性一起使用的表通过共享数据块(data block)的模式存放在一起。各表间的共同字段作为蔟键值(cluster key),数据库在访问数据时,首先找到蔟键值,以此同时获得若干张表的相关数据。蔟表所能带来的好处是可以减少I/O和减少存储空间,其中我更看重前者。采用表分区(partition)
表分区技术是在超大型数据库(VLDB)中将大表及其索引通过分区(patition)的形式分割为若干较小、可管理的小块,并且每一分区可进一步划分为更小的子分区(sub partition)。而这种分区对于应用来说是透明的。通过对表进行分区,可以获得以下的好处:
1)减少数据损坏的可能性。
2)各分区可以独立备份和恢复,增强了数据库的可管理性。
3)可以控制分区在硬盘上的分布,以均衡IO,改善了数据库的性能。
蔟表与表分区技术的侧重点各有不同,前者侧重于改进关联表间查询的效率,而表分区侧重于大表的可管理性及局部查询的性能。而这两项对于我的系统来说都是极为重要。由于本人技术限制,目前尚不确定两者是否可以同时实现,有那位在这方面有经验的给点指导将不胜感激。
在两者无法同时实现的情况下,应依照需实现的功能有所取舍。综合两种模式的优缺点,我认为采用表分区技术较为适用于我们的应用。
Oracle的表分区有以下几种类型:
1)范围分区:将表按某一字段或若干个字段的取值范围分区。
2)hash分区:将表按某一字段的值均匀地分布到若干个指定的分区。
3)复合分区:结合了前面两种分区类型的优点,首先通过值范围将表进行分区,然后以hash模式将数据进一步均匀分配至物理存储位置。
综合考虑各项因素,以第三种类型最为优越。(本人实在技术有限仅采用了第1种范围分区,因为比较简单,便于管理)
优化的具体步骤:
1.确定需要优化分区的表:
经过对系统数据库表结构和字段,应用程序的分析,现在确定那些大表需要进行分区:
如帐户交易明细表acct_detail.
2.确定表分区的方法和分区键:
分区类型:采用范围分区。
分 区 键:
按trans_date(交易时间)字段进行范围分区.
3.确定分区键的分区范围,及打算分多少分区:
如:帐户交易明细表acct_detail.
根据字段(trans_date)分成一下分区:
1).分区1:09/01/2003
2).分区2:10/01/2003
3).分区3:11/01/2003
4).分区4:12/01/2003
5).分区5:01/01/2004
6).分区6:02/01/2004
该表明显需要在以后增加分区。
4.建立分区表空间和分区索引空间
1).建立表的各个分区的表空间:
1.分区1:crm_detail_200309
CREATE TABLESPACE crm_detail_200309 DATAFILE
‘/u1/oradata/orcl/crm_detail_20030901.dbf’
SIZE 2000M EXTENT MANAGEMENT LOCAL UNIFORM size 16M;
其它月份以后同以上(我在此采用oracle的表空间本地管理的方法)。
2). 建立分区索引表空间
1.分区1:index_detail_200309
CREATE TABLESPACE index_detail_200309 DATAFILE
‘/u3/oradata/orcl/index_detail_20030901.dbf’
SIZE 2000M EXTENT MANAGEMENT LOCAL UNIFORM size 16M;
5.建立基于分区的表:
create table table name
(
........
enable row movment --此语句是能修改行分区键值,也就是如不添加该 句不能修改记录的分区键值,不能使记录分区迁移
PARTITION BY RANGE (TRANS_DATE)
(
PARTITION crm_detail_200309 VALUES LESS THAN
(TO_DATE (‘09/01/2003’,’mm/dd/yyyy’
TABLESPACE crm_detail_200309,
其他分区.....
;
6.建立基于分区的索引:
create index index_name on table_name (分区键+…)
global --这里是全局分区索引,也可以建本地索引
PARTITION BY RANGE (TRANS_DATE)
(
PARTITION index_detail_200309 VALUES LESS THAN
(TO_DATE ('09/01/2003','mm/dd/yyyy' )
TABLESPACE index_detail_200309,
其他索引分区...
;
对表的分区就这样完成了,第一次主要确定表分区的分区策约是最重要的,可我觉得对表分区难在以后对表分区的管理上面,因为随着数据量的增加,表分区必然存在删除,扩容,增加等。在这些过程中还牵涉到全局等索引,因为对分区表进行ddl操作为破坏全局索引,故全局索引必须在ddl后要重 rebuild.
版权说明:文章转至http://www.javaeye.com/topic/174264

