|
楼主 |
发表于 2009-4-27 13:28:53
|
显示全部楼层
2
1楼:【点】风格的存储结构
话说某天……和疯人聊QQ(透露个小秘密,我QQ才刚注册几天~)…………他在跟我讲他关于怎样拓展timer的数据含义的想法(在T中,只用很少的J部分)。如果把一个handle地址的意义比作0.25,那么timer就只有0.25,而location就有2.25(x,y,handle)。他在想,如果能让timer也向location一样拥有2.25的数据意义呢?
他是这样想的:地图初始化时,一个timer创建完后,就创建一个location。这样在获得timer时,直接把Handle+1,就可以得到2个数据了………………
当然,某些人,一看就说:用TimerDataSystem!!
也许你没发现(在T中,只用很少的J部分)这一部分。
(好吧这是题外话)
详细的对话内容我已经忘却了,如今这些珍贵的对话资料已经遗失在茫茫网络中了。但是我受他的启发,居然发现可以在魔兽中实现【链表】、【另一种更方便的局部数组传递】、【超越8192的伪数组】了……………………
后来我又想了很多,将这个东西发掘了一下,成果就发一下吧~~
不过~大家要记住是疯人启发我的,不要忘记他哦~~
我讲的顺序是(好假):
1.【超越8192的伪数组】
2.【链表】
3.【另一种更方便的局部数组传递】
1.【超越8192的伪数组】
我在想,一个timer只能配一个location,只能有2个有意义的数据,如果能多几个就好了。当时我第一想的是链表,但是,后来我又想到一种更好、更快捷、更省事的办法,而且直接脱离了数组,同样也没有了8192的限制。不过虽然脱离了8192的限制,但是效率降得可不是一星半点……
我的想法是,如果需要更多的数据,那么在初始化时直接增加location的数量就好了。比如一个timer创建后,配10个location,这样的话,就可以有20个数据了。而要获得这些location,只要将timer的handle再加上你要获得的location的位移下标,就可以得到对应的location。再GetLocationX/Y就可以得到真正的数据了。
但是,这虽然能突破8192的限制。却也有两个缺点:
1.效率绝对比全局变量低(这是必然的,GetLocation+GetLocationX/Y怎么看都不能比全局变量数组快)。
2.这必须要在初始化时运行。由于HandleTable的特点使得我们不能在初始化之后再次重新创建location和timer,因为这时你不能保证timer的handle,一定和location连在一起。
(PS:使用Location来存储handle_address应该没有问题,我试过用990000,990000来测试Location,很惊讶的没有崩溃,我继续用GetLocationZ来测试,只是返回0而已……)
代码:
[codes=jass]function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Loc takes integer i returns location
return i
return null
endfunction
function Init takes integer timer_number, integer loc_number returns nothing
local integer i = 0
local integer j = 0
loop
exitwhen i >= timer_number
call CreateTimer()
set j = 0
loop
exitwhen j >= loc_number
call Location( 0.0, 0.0 )
set j = j + 1
endloop
set i = i + 1
endloop
endfunction
//建议timer_number * loc_number不要太大,否则可能有危险。魔兽一个触发器运行的代码字符是有限制的,除非用等待……
function GetHAL takes integer handle_address, integer where returns location
return I2Loc( handle_address + where )
endfunction[/codes]
2.【链表】
是这样的……
好吧~疯人给我记录了~~(太神奇了~当然也有可能我不会看QQ通话记录的缘故…………): 血戮魔动冰16:02:33
1.timer绑定指定location。location包含一个location和一个数据,然后,再获得下一个location的location下一个location的location…………而每个location都包含一个数据………………
好吧………………
好吧~~链表~这又是个貌似很陌生的东东~于是解释一下(高人就不要怪我啦):
迄今为止,在魔兽中,是没有能实现链表的。只能用数组里的一些指针来做静态链表,但大多数还是基于数组本身的存储表示。数组优点是:
1.无需为元素间的逻辑位置关系增加额外的存储空间,省了很多内存
2.可以方便的用下标来获得对应的元素,无需做任何逻辑运算。
3.相对于魔兽内部的现有的存储结构而言,它是物理层面上最快的存储结构
但是其缺点是:
1.受限于最高限制8192
2.对于某些要求在struct中实现数组的人来说,必须使用2维数组来解决。而且由于struct内一种元素要求的数量。导致整个struct的数量上限下降到8192/最大的某种元素要求的数量
当然了,这还是因为魔兽自身的数组就是个Table,为空的元素自动不为其非配空间所致,要不然大家才不会这么不要命的用数组呢~~(一句话里居然有4个不……我的语文真差…………)
再来看链表:
链表,对元素使用链接的形式进行集合。
基本有几种之分:
单链表:每个结点都有一个指向它的下一结点的指针next,可以通过此结点的next来获得下一结点。尾结点next为空。
循环列表:与单链表相似,只不过尾结点next为头结点,这样其实就没有头尾之分了。
双向列表:与循环列表相似,只不过每个结点都增加了一个指向上一节点的指针before,头结点before指向尾结点。
它的优点是:
1.相对于数组,数组在传递时需要很多资源(魔兽的数组根本没法传递……),而链表,只要传递一个头结点的指针(循环和双向只要任意一个结点就可以了),就可以依次获得整个链表了。
也就是说,他的灵活性是比数组要高得多的。
缺点是:
1.相对于数组,他需要一个额外的数据域来存储下一个节点的地址,所以在存储的空间上比数组要多一点。
2.在获得指定结点时,需要不断地迭代地寻找指定的结点。在最坏的情况下,它的开销是O(n),n是整个链表的结点个数。
3.由于链表无法直接在魔兽里实现,所以效率要差一点点
那么我们来看一些关于链表的实际应用:
比如:我需要给timer绑定一组种类相同但值不同的数据,而这一组数据可能有1-150个。而且同一时间的timer会有几百个,数据是顺序存入/读取的。如果你想用struct+成员数组来实现,你会发现就算是挤满了8192有些情况就会超出。所以数组及其派生的所有如HashTable什么的绝对是第一时间排除(如果一个存储结构在实际的问题应用时会发生数据无效的情况,那么对于这个问题,这个存储结构就算是废了)。那么我们想,魔兽中,除了数组以外,就只有GameCache和我现在讲的链表了。那么此时怎么办呢?如果用GameCache的话,"Data"+1-150的话,先不说string的使用,光是效率就是非常低的。那链表呢?它能不能担任这个重任呢?
首先,我们确定他是顺序存入/读取的,那么如果是链表的存入/读取,那么链表的结构上的读取/存入的劣势就被消除了(这只是设定而已,假一点不要怪我)。然后,用这个肯定不会产生string。但是会使用handle,所以需要remove他们。这基本与Flush的开销基本一样。
最后,我们来分析一下GameCache和location(这是用location来测试,其他handle的存储的开销基本上就跟location的开销差不多)在物理方面的效率吧。
首先我们可以肯定,location是在内存层面进行的操作(要不是,我找玻璃渣理论去……),而GameCache是在外存的层面进行的操作,可以想见,除非location的存储/读入有专门的很费事的算法,否则,location的效率是绝对由优于GameCache。接下来分析location到底有没有费事的算法。
首先location的数据存储函数是Get/SetLocationX/Y,如果变成C++,那么绝对不会有什么麻烦的算法:
[codes=C++]void/double location::Get/SetLocationX/Y ( double the_x/y/ )
{
return x/y;
/
x/y = the_x/y;
}[/codes]
自然暴雪也不会比这更复杂些(没准会有地图坐标Z高度检测什么的)。而GameCache,就算他没有关于字符串的运算,光是它在外存(嗯……真正的物理地址应该在硬盘上)这一点硬伤上,location就绝对是优于GameCache的,但是事实呢?
为了测试location和GameCache以及全局变量(但是很遗憾的是我用newgen貌似有问题,无法支持globals,其实是我找不到没有中文路径的地方啦……其实是懒而已………………),我用newgen的japi中的StopWatch(据说比timer精确……)测试了一下,结果如下(方面并不全,只测试了一个location的存储/读取的情况,主要原因是location-linkedlist它的查找是个线性一次函数,不好测试,最多也就是个O(n)的开销)(全部是测试同一location/missionkey-key的数据,每次都重复100次)(我没有对代码测试过两次,所以这些数据是不太准确的,但它能说明一些很显而易见的问题):
取出数据方面:
循环本身开销:1.780
location:2.528 与循环本身开销的差:0.748
GameCache:3.939 与循环本身开销的差:2.159
location与GameCache效率比:3:1
存储数据方面:
循环本身开销:1.780
location:3.513 与循环本身开销的差:1.733
GameCache:4.754 与循环本身开销的差:2.974
location与GameCache效率比:1.7:1
通过这组测试数据,我们可以得知,空白location的效率,是绝对优于gamecache的。而且还没有了string过量使用的困扰。
(一个小小的不幸的事实被我言中了:location果然需要)
那既然我们对比了gamecache,那么我们能对比一下globals和location的效率吗?
很遗憾……不能……但是我可以明确的告诉你们……不管是存储还是取出数据方面,globals都是优于location。而且在存储数据方面……是绝对的优于location。
而且,由于点链表它的特殊结构,读取数据时,必须遍历此数据前面的所有结点,所以效率会随着结点的多少而不断递增。最大开销为O(n)。数组是直接通过index来直接在内存中找到数据地址,GameCache经过老狼测试,它的效率非常平稳,而location-linkedlist一个得到指定位置的函数都需要一个loop循环。
[jass]globals
location LinkedList_Find_Return = null
endglobals
constant function I2Loc takes integer i returns location
return i
return null
endfunction
function LinkedList_Find takes location head, integer index returns location
local integer i = 0
set LinkedList_Find_Return = head
//如果用local location temp的话,则返回时不能清空变量,所以我用了全局变量
loop
exitwhen i >= index
set LinkedList_Find_Return = I2Loc( R2I( GetLocationX( LinkedList_Find_Return ) ) )
//当index大于链表长度时,最后的location的x必定为0
//而I2Loc则得到null,GetLocationX( null )得到0
//如此循环直到i==index,此时location为null
//所以这样写是没有什么问题的
set i = i + 1
endloop
return LinkedList_Find_Return
endfunction[/jass]
所以基本上,当链表长度>10(我估计)时,效率就可能连GameCache都不如了。
你们会说:这个臭链表,搞那么复杂,怎么效率这么让人恶心,还不如用GameCache都比这好!
但是,链表的真正价值不在于此。
一般来说,GameCache和数组在存储方面是基本不能互相包含的(总觉得话说不明白……)。比如在GameCache里,你想存储一个数组,那怎么办呢?
[jass]call StoreXXXX( <gc>, <missionkey>, <key> + I2S( <x~y> ), <values> )
//……
GetStoredXXXX( <gc>, <missionkey>, <key> + I2S( <x~y> ) )[/jass]
不得不说,这样的效率是差到了极点的……
而存储一个index是可以的……
但是一个数组呢?
怎么把一个数组塞进GameCache里?
这在编辑方面就是无解的……
你能在数组里用GameCache吗?
[jass]globals
gamecache array GCs
endglobals
function GetGC takes integer index returns gamecache
if index > 256 then
return null
else
return GCs[index]
endif
endfunction[/jass]
这是不可能的,魔兽最多支持256个GameCache(如果我没记错的话),这基本上是使用不了的。
也就是说,数组存不了GameCache,GameCache存不了数组。
这两种存储结构不能合二为一,取长补短,这是我们WEer一直解决不了的难题。
但是链表作为一个存储结构,它能不能与数组、GameCache叠加、为这俩主流存储结构补短呢?
答案是可以的,而且非常有用~~
先说linkedlist+gamecache。
如上文所说,gamecache想要存储一个数组的数据,效率非常低。
linkedlist能不能帮他把效率提一提呢?
很简单,将数组的数据存进链表里,gamecache只存储一个头结点,读取时,按照结点顺序依次读取就可以了。
[jass]globals
gamecache GC
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Loc takes integer i returns location
return i
return null
endfunction
function Init takes nothing returns nothing
//init GC
endfunction
function Store takes nothing returns nothing
local integer array Set//一个数组的数据
local integer len//数组长度
local integer i = len
local location loc = null
loop
exitwhen i < 0
set loc = Location( H2I( loc ), Set )
set i = i - 1
endloop
call StoreInteger( GC, <mission key>, Set, H2I( loc ) )
call StoreInteger( GC, <mission key>, Len, len )
//......
endfunction
function Restore takes nothing returns nothing
local integer array Set
local integer len = GetStoredInteger( GC, <mission key>, "Len" )
local location loc = I2Loc( GetStoredInteger( GC, <mission key>, "Set" ) )
local integer i = 0
loop
exitwhen i >= len
set Set = R2I( GetLocationY( loc ) )
set loc = I2Loc( R2I( GetLocationX( loc ) ) )
set i = i + 1
endloop
//......
endfunction[/jass]
这样,GameCache的效率不高的特点就被掩盖了过去。他的效率会提升1.5-3倍。
而且,这样还避免了string的产生,可以说是全方面的优化。
基本上,没有增加什么代码的难度,一般会jass的WEer基本很快就能上手。
再来看location和array的组合。
介于array本身的限制,标准魔兽风格的array是不能被包含在其他存储结构里的。
所以我们只能将location包含在array里。
数组有4种用法,一种指针,一种Hash,一种struct,一种DataSystem(与GameCache结合)。
对于指针和struct,我们可以用location避免其内置数组占用过多index的情况。
比如我们的一个timer绑定一个8元素的数组。但是这让我们很不爽,为什么呢?
因为我们需要将每个绑定单元的间隔变为8。而由于魔兽数组的8192限制,我们就只能有1024个绑定单元了。
一下少了7000个绑定单元……这太坑人了……
那么我们只要用location改进一下就可以。
将数组变为链表。我们只用头结点就可以得到整个链表。这样,我们只要在单元里用一个location变量就可以代表数组了。
这样,理论上,所有绑定单元的数组都可以变为链表来存储。这样,我们的绑定单元的最大数量就可以不受内部数组的限制了。
[jass]globals
constant integer Min_Number = A000
integer maxN = 1
integer p = 0
integer array Set
location array SetInSet
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Loc takes integer i returns location
return i
return null
endfunction
function GetNumber takes location head returns integer
local integer index = p
if index == 0 then
set index = maxN
if index > 8191 then
return 0
endif
set maxN = index + 1
else
set p = Set[index]
endif
set SetInSet[index] = head
return index
endfunction
function FlushNumber takes integer index returns nothing
local location loc = SetInSet[index]
local location next = null
set Set[index] = p
set p = index
loop
set next = I2Loc( R2I( GetLocationX( loc ) ) )
exitwhen next == null
call RemoveLocation( loc )
set loc = next
endloop
set loc = null
set next = null
endfunction[/jass]
DataSystem因为用GameCache,所以就不说了(太方便了),对应的数组的可以看上面指针和struct的优化。
接下来我们说Hash。
现在我们使用Hash有大体两种处理溢出的方法。
第一种是传统的Hash。
分有:
1.+-1
2.+-n^2(n++)
3.ReHash()
第二种是传统Hash怎么也比不了的拉链Hash(强烈推荐!!!!!!可以看cccty1的《还是说数组存储系统》、《还是说数组存储系统续》都是非常好的Hash的贴,当然我在写此贴时,《续》还没看完,估计会有拉链Hash的详讲……但是我在家里没法上网,所以看不了……只好看他的拉链Hash以前的简略的帖子……幸好cccty1是个好人,还写点注释……)
传统的Hash在我的《数据结构:由浅入深谈Hash表 及 数组存储系统》中有提到……
但是我很可恶的略去了ReHash的介绍及效率。
你可以看回帖,有人指明《算法导论》提到ReHash是在+-1,+-n^2(n++),ReHash中平均搜索效率最高的方法。
好,题外话结束。
Hash表已经为我们提供了用数组很好的代替GameCache的算法。但是还是有一个硬伤那就是魔兽jass最大数组尺寸8192的限制。如果能突破这个限制,那么GameCache肯定会成为历史。但是事实不是这样的(要不然现在我们哪会用Hash啊!直接用指针就好了)。
那么我们现在可以利用location不受8192的影响这一特性,为Hash补短。
我们知道,现在拉链Hash是查找效率最高的Hash法。他的最大开销是O(n),n=求模后与查找的index(已求模)相同的但原始数据不同的元素个数。
如果我们要用location代替array,那么我们必须在算法上至少与拉链Hash一样才行。
我是这样想的。
N个全局变量数组(N=一个元素单位里有的数据个数)存储头结点。
当取余后得到index,就将数据插入得到的index的链表(头结点)里。
查找时,按顺序查找即可。
这样,此算法的开销就与拉链Hash一样了(能不一样吗?数学模型都是一样的……)。
但是不同就在于,这个方法,他没有理论上的数量限定。而拉链Hash的所有元素单位的个数只能限定在8192之内。所以,这样的话,这种存储结构就基本可以替代GameCache了。他可以完全为这个地图里的所有需要绑定数据的有价值的Handle绑定数据。
示例为方便采用了location。但是你在使用时绝对不会用location,而是能够放更多的数据进去的Handle。
也就是说,示例是一个什么也做不了的东西。但是你可以看出这个方法的模型。
[jass]globals
location array Hash_Set
location temp_Location
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Loc takes integer i returns location
return i
return null
endfunction
function Hash_Create takes integer value returns location
local integer index = value - value / 8191 * 8191
local location loc = Hash_Set[index]
set Hash_Set[index] = Location( H2I( Hash_Set[index] ), value )
if loc == null then
call MoveLocation( Hash_Set[index], H2I( Hash_Set[index] ), value )
//构造循环链表
endif
set loc = null
return Hash_Set[index]
endfunction
function Hash_Get takes integer value returns location
local integer index = value - value / 8191 * 8191
local location loc = Hash_Set[value]
loop
exitwhen loc == null
if GetLocationY( loc ) == value then
set temp_Location = loc
set loc = null
return temp_Location
endif
set loc = I2Loc( R2I( GetLocationX( loc ) ) )
endloop
endfunction
function Hash_Destroy takes location loc returns nothing
local location next = I2Loc( R2I( GetLocationX( loc ) ) )
local location before = next
local integer hi = H2I( loc )
loop
exitwhen GetLocationX( before ) == hi
set before = I2Loc( R2I( GetLocationX( before ) ) )
endloop
call MoveLocation( before, H2I( next ), GetLocationY( before ) )
call RemoveLocation( loc )
set next = null
set before = null
endfunction[/jass]
(演示中采取了单循环链表,你可以把它弄成双链表,这样Destroy会更快一些)
3.【另一种更方便的局部数组传递】
很简单的东西,就是把链表当成数组在函数间传递,与location+GameCache较相像。
[jass]function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Loc takes integer i returns location
return i
return null
endfunction
function AFunciton takes location loc, ... returns ...
local location array Set
local integer len = 0
set Set[len] = loc
loop
exitwhen Set[len] == null
set Set[len+1] = I2Loc( GetLocationX( Set[len] ) )
set len = len + 1
endloop
//...
endfunction
function BFunction takes ... returns ...
local location array Set
local integer len = ...
local integer i = len - 2
set Set[len-1] = Location( 0.0, ... )
loop
exitwhen i < 0
set Set = Location( H2I( Set[i+1] ), ... )
set i = i - 1
endloop
//...
call AFunction( Set[0], ... )
endfunction[/jass]
我也不太知道这会有什么用。但是疯人说,好像可以在T的方面有作用……(我真是个不折不扣的j啊,都是T的了,还用j写……)
恩就发一下了。
(提醒:这里的链表、数组是专指魔兽里的链表、数组。如果你用别的语言编程,如C++,你会发现别的编程语言的数组、链表和魔兽的数组、链表是有差别的,差别大到你会彻底改变自己对链表和数组的看法及评价)
//================================== |
|