`
eXolution
  • 浏览: 30610 次
社区版块
存档分类
最新评论

动态储存管理小教程

 
阅读更多

2012-1-9 

其实这个不是真正正统的教程(甚至可以认为是不务正业),因为我使用的语言是JASS语言 即魔兽争霸3地图编辑器的脚本语言。(好吧 我知道石头你又该嘲笑我了。确实 整这么长时间我一张成型的图也没拿出来。。。。但是,你不能否认我的技术!!

但是其中算法和思想。其实和操作系统中储存管理是没啥区别的。在家各种冷,没事码码字活动活动僵硬的手指。
正好也尝试以浅显易懂的方式写一下自己小小的研究成果。

JASS这个语言是很简陋的(PASCAL风格)。他本身的数组全都固定是8192长度的。这样利用率非常的低。所以我就想做一套动态分配的数组。可以自定义数组长度提高利用率。所以我这个系统应运而生。

原理概要

jass中提供的数组都是8192长度。如此的规整因此我们可以把他看成一条内存。以下称作内存数组
实际上动态数组就是一个内存分配与回收的过程。就好比切一块长条的吐司面包。把最初是一块整的面包,逐渐切成根据具体需要的小块。而不是一口吞下。因此一个动态数组就是一个内存块。如何申请动态数组就是如何切面包的问题。(下文会交替使用内存块和面包块大家知道指的是一个东西就行)

首先。我要管理这个面包,我必须要了解他。这里我要给每块内存块记录一些信息。这样我才能更好的分配他们给大家吃。
控制信息如下。
    struct block
    {   int state;//1 :内存块状态----1=占用、 0=空闲  
        int initaddr;//2:内存块头地址 也就是这块空间在内存数组中的起始索引
        int size;//3:内存块大小
        int prev;//4:前相邻内存块索引(前指针)
        int next;//5:后相邻内存块索引(后指针)
    }



这里面我是用C语言的struct表示。在jass中其实就是以5个连续整数为整体放到数组中的,这个数组姑且叫做目录表。其实就是一个清单,纪录目前都有哪些面包块,都是多大。是分配给烧饼了还是分配给山炮了。或者是还没有分配。
然后这些控制信息节点在目录表中组成了一个双向链表。即知道自己的邻居在哪。能够方便的找到相邻的内存块。
1、申请

发面包了!首先我要从一大整块面包切一刀(这一大整块面包是目录表初始生成的一个节点)。然后把切出来的一块同时记录各种信息放到目录表里。然后分配给用户一个id 就像这样 integer a=allocArray(INT,200) 
这样a就是一个200大小的整型数组了。这个a其实就是对应的内存块的控制信息节点的序号或者说索引。(因为我一个节点是包含5个数据的因此实际的索引是id*5再加上目录表初始位置)。这样就能通过目录表中的内存块信息。存取这段(数组)内存了。
存取就象这样 LoadInt(id,index);SaveInt(id,index,value)
2、回收

由于来面包party人比较多。你吃了面包,一定要自己再烤一块放到面包盘里供其他食用。否则后来到的人岂不是要饿死了。
做面包虽然复杂但对于咱们的内存块就简单多了。直接把内存块状态从占用改为空闲不就行了么!
真的这么简单?
确实是这么简单,但同时带来很大的问题。随着吃面包的人越来越多。原来那一整块面包被切的越来越碎。这样一来,后面如果来了饭量大的。每块面包都不够他吃但我们的规则又是只能拿一块。那么这个倒霉蛋是不是要注定饿死了。因此,当一个人吃完了之后又烤好新的放回去的时候。他要看下左右有没有别人新烤好放回去的面包,如果有,那他就应该回炉合并这两块或者三块面包组成一个更大块的面包。这样前面的杯具就不会发生了。这样就是释放做的最大的工作,合并相邻的空闲内存块。这里我的系统使用了一个策略即向右合并。即总是把左边的内存块合并到又边去,同时删掉左边的节点。如ABC三块A和C是空闲块。B是即将释放的。这是后会把A删掉各种信息合并到B里。变成B'C然后删掉B'把B'再合并到C里。
这样的好处是什么呢?因为我切面包总是从左开始切,那么那块面包剩余的部分(一般是较大的部分)总是在右边。这样的策略,总是能优先把内存释放到最大的那部分里。
不得不提的是,使用数组结构储存的另一大有点。上面所说的最大部分的面包在物理上总是存在数组的最左边的。而搜索可用内存块时又是从左边搜索。这样一来,分配总是从那块大的分配,而释放又总是释放到那块大的里面。这样就大大提高了内存的吞吐率,减少了内存碎片的产生。
3、存取

这个系统的重点就在于上述的分配与回首。当然存取也不得不提。
我这个系统内存理论上是没有长度限制的。我把多个数组拼成一个线性地址空间了。只需索引对8191取模即可 当然还需要分支语句使用这几个数组
具体看实际代码吧
4、相关技术

    下面我讲一下目录表表本身的存储机制
    其实我是把他们存到一个特殊的栈里的。这种栈只能在栈顶添加节点。但是允许在任意位置删除节点。
    原理是,栈顶指针指的是逻辑上的栈顶并不代表是物理上的顶端。举例如下【sp:stack pointer 栈顶指针】
     0   1  2  3   4   5  6                                                               0  1   2   3   4  5   6  7   8
    [1][2][3][4][0][0][0]  此时sp=4 如果删除第2个元素则变成 [1][(4)][3][4][0][0][0][0][0] 
                   I                                                                                   I
    这时sp=1即指向刚刚被删除的那个元素。但是刚才那个元素要储存上个栈顶的位置。用来指导sp的移动。
    也即是说这种情况下,如果新数据到来,会存到第2个元素这里。同时sp为指向第2元素所指导的位置。即索引4.
    以此类推。
    当然需要说明的是。我的元素不止一个字段。必须有字段来表示这个位置是否是空的。
    不然无法判断该位置是所存的4是业务数据还是栈顶指导数据。因此使用了块大小那个字段。
    如果扫描节点时遇到一个节点的块大小是-1,则此节点是被删掉的节点,即【SP位移指导节点】(好吧自己胡乱起的名字)。
    注:如果在需要位移指导时没有发现【SP位移指导节点】(其实就是初始的情况 如图1sp指的那个位置是个空节点没有指导信息)
    。那么此时sp=sp+1。和普通栈一样。


这种算法的好处就是利用本身的字段空间储存一些信息。而不用进行栈顶元素以为补空。但是对于单元素节点的表不适合此方法。
5、struct vector 与类型检查

我的代码中并未提供struct解决方案(这里是想在jass语言里实现struct封装)
但是我基于这个系统可以提供一个思路。
如struct
{
  int a;
  int b;
  real c;
unit u
}
则可以这样 
integer a=allocArray(INT,2)//假设此时a=3
real b=allocArray(REAL,1)//b=11
unit u=allocArray(UNIT,1)//u=321
然后将a、b、u这3个数封装为一个数。即3210011003做为这个struct的id
然后使用这个id存取对用类型数据时再对这个数解析取出符合该类型的数就行了。
为什么要同时说下类型检查呢 因为思路和这个是一样
我的动态数组分配的id号本身是无法表示类型的如果你申请了个整型的数组id用来存取unit的起步乱套了。
因此同理 我把类型代号封装到这个id里 存取时先检查类型。
当然这些所谓的类型检查是运行期检查而已 ,很没有意义。

也正如struct的意义在于一个新的语言支持(像vj)一样。
这些东西,如果我做一个新语言,实现也就显得有那么些价值了。编译器类型检查也比这方便效率多了。

globals 
    //内存地址空间 
    integer array IntArray1 
    integer array IntArray2 
    real array RealArray1 
    real array RealArray2 
    string array StrArray1 
    string array StrArray2 
    unit array UnitArray1 
    unit array UnitArray2 
    integer array ContentTable//目录表 记录着系统中内存块的信息。 
    //每块内存信息作为一个节点链式储存在这个数组中 
     integer CTRLINFO_LEN=5//内存块控制字段的长度 具体含义上面已经有解释了 

    integer  MAXTYPE=4//这个是支持的类型的最大数目。系统会根据这个为每个类型划分目录表的存储区间 
    //目前只是用了几个常用类型如下 如果需要添加则增加MAXTYPE的值并添入类型代号即可 注意类型代号必须是依次的。 
    //例如我想加一个新类型timer 则定义integer TIMER=4 
    integer INT=0//代表整形 
    integer REAL=1 
    integer STRING=2 
    integer UNIT=3 

    integer test=1 
endglobals 


function initArray takes nothing returns nothing 
//初始化内存块。也就是分配第一个内存块。 
    local integer atype=0 
    local integer initaddr 
    loop 
        exitwhen atype>=MAXTYPE 
        set initaddr=atype*R2I(8000/MAXTYPE)+atype 
        set ContentTable[initaddr]=1 
        set ContentTable[initaddr+2]=0 
        set ContentTable[initaddr+3]=16000 //寻址两个数组长度 
        set ContentTable[initaddr+4]=-1 
        set ContentTable[initaddr+5]=-1 
        set atype=atype+1 
    endloop 
endfunctionfunction dump takes string str returns nothing 
    call DisplayTextToPlayer(GetLocalPlayer(),0,0,str) 
endfunction 



function searchBlock takes integer asklen,integer initaddr returns integer 
    //寻找第一个满足分配要求的内存块 大多数情况总会是第一块最大的。(因为他就在位置0) 
    local integer n=0 
        loop 
            exitwhen ContentTable[initaddr+n*CTRLINFO_LEN+3]==0 
            //因为不存在内存块大小为0的内存块(被删除的块大小为-1)因此遇到这个情况说明搜索到目录表物理末端了 
            if(ContentTable[initaddr+n*CTRLINFO_LEN+1]==0 and ContentTable[initaddr+n*CTRLINFO_LEN+3]>=asklen) then 
                return n 
            endif 
            set n=n+1 
        endloop 
        return -1 

endfunction 


function setPrevBlock takes integer initaddr,integer id,integer previd returns nothing 
    //设置内存块id的前接内存块为previd 
    set ContentTable[initaddr+id*CTRLINFO_LEN+4]=previd 
endfunction 
function getPrevBlock takes integer initaddr,integer id returns integer 
    //获取内存块id的前内存接块 
    return ContentTable[initaddr+id*CTRLINFO_LEN+4] 
endfunction 


function setNextBlock takes integer initaddr,integer id,integer nextid returns nothing 
    //设置 内存块id的后接内存块为nextid 
    set ContentTable[initaddr+id*CTRLINFO_LEN+5]=nextid 
endfunction 
function getNextBlock takes integer initaddr,integer id returns integer 
    //获取内存块id的后接内存块 
    return ContentTable[initaddr+id*CTRLINFO_LEN+5] 
endfunction 


function removeBlock takes integer id,integer atype returns nothing 
    //删除一个已经合并到其他空闲块的内存块。 
    local integer initaddr=atype*R2I(8000/MAXTYPE)+atype 
    local integer previd 
    local integer nextid 
    if(id==0) then 
           call dump("严重警告 第0块被删除了")//因为第0块其实是最右边的那块 因此不可能被删除 
      
    endif 
     if(ContentTable[initaddr+id*CTRLINFO_LEN+3]==0) then 
        call BJDebugMsg("无法删除一个不存在的节点") 
        return 
    else 
        set ContentTable[initaddr+id*CTRLINFO_LEN+1]=ContentTable[initaddr] 
        set ContentTable[initaddr+id*CTRLINFO_LEN+3]=-1//块大小变成-1 表示此节点变成sp位移指导节点 
        set ContentTable[initaddr]=id 
        //以下是标准的双向链表删除节点操作 学过数据结构的都应该清楚 
        //就是把前节点的后指针指向该节点的后节点 
        //把后节点的前指针指向该节点的前节点 
        set previd=getPrevBlock(initaddr,id) 
        set nextid=getNextBlock(initaddr,id) 
        if(previd>=0) then 
            call setNextBlock(initaddr,previd,nextid) 
        endif 
        if(nextid>=0) then 
            call setPrevBlock(initaddr,nextid,previd) 
        endif 
     endif 

endfunction 

 function allocArray takes integer len,integer atype returns integer 
    local integer initaddr=atype*R2I(8000/MAXTYPE)+atype 
    local integer nid=-1 
    local integer bid=searchBlock(len,initaddr)//找到待切的蛋糕 

    if(bid==-1) then 
        call MemDefrag(atype)  
        //内存不足 
        call dump("内存不足!") 
    else 
        set nid=ContentTable[initaddr] 
        if(nid>8000/MAXTYPE/5) then 
            call dump("内存块数量溢出!") 
            return -1 
        endif 
        if(ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then//处理无sp指导信息时的sp步进位移 
            set ContentTable[initaddr]=ContentTable[initaddr]+1 
        else 
            set ContentTable[initaddr]=ContentTable[initaddr+nid*CTRLINFO_LEN+1]//否则sp位移到指导信息所标识位置 
        endif 

        set ContentTable[initaddr+nid*CTRLINFO_LEN+1]=1 
        set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2] 
        set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=len 
        set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+bid*CTRLINFO_LEN+4] 
        if(ContentTable[initaddr+bid*CTRLINFO_LEN+4]!=-1) then 
            set ContentTable[initaddr+ContentTable[initaddr+bid*CTRLINFO_LEN+4]*CTRLINFO_LEN+5]=nid 
        endif 
        set ContentTable[initaddr+nid*CTRLINFO_LEN+5]=bid 
        //set test=test+1 
        if(ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len==0) then//如果正好分配完 老内存块删掉 
                call removeBlock(bid,atype) 
        else 
            set ContentTable[initaddr+bid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2]+len 
            set ContentTable[initaddr+bid*CTRLINFO_LEN+3]=ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len 
            set ContentTable[initaddr+bid*CTRLINFO_LEN+4]=nid       
        endif 

    endif 

    return nid 
endfunction 
function destroyArray takes integer id,integer atype returns nothing 
    local integer initaddr=atype*R2I(8000/MAXTYPE)+atype 
    local integer flag=4 
    local integer nid 

    set ContentTable[initaddr+id*CTRLINFO_LEN+1]=0 
    set nid=ContentTable[initaddr+id*CTRLINFO_LEN+4] //合并前接空闲块 
     if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then 
        set ContentTable[initaddr+id*CTRLINFO_LEN+2]=ContentTable[initaddr+nid*CTRLINFO_LEN+2] 
        set ContentTable[initaddr+id*CTRLINFO_LEN+3]=ContentTable[initaddr+id*CTRLINFO_LEN+3]+ContentTable[initaddr+nid*CTRLINFO_LEN+3] 
        set ContentTable[initaddr+id*CTRLINFO_LEN+4]=ContentTable[initaddr+nid*CTRLINFO_LEN+4] 
        call removeBlock(nid,atype) 
    endif 
    set nid=ContentTable[initaddr+id*CTRLINFO_LEN+5]//合并后接空闲快 
    if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then 
         set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+id*CTRLINFO_LEN+2] 
         set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=ContentTable[initaddr+nid*CTRLINFO_LEN+3]+ContentTable[initaddr+id*CTRLINFO_LEN+3] 
         set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+id*CTRLINFO_LEN+4] 
        call removeBlock(id,atype) 
    endif 

endfunction 
function CountRealIndex takes integer atype,integer id,integer index returns integer 
//计算真实的索引 
    local integer initaddr=atype*R2I(8000/MAXTYPE)+atype 
    local integer loc=ContentTable[initaddr+id*CTRLINFO_LEN+2]+index 
    if(index>=ContentTable[initaddr+id*CTRLINFO_LEN+3] or ContentTable[initaddr+id*CTRLINFO_LEN+1]==0) then 
        call dump("非法的索引:"+I2S(index)) 
    endif 
    return loc 

endfunction 
function LDI takes integer id,integer index returns integer //loadinteger 
    local integer loc=CountRealIndex(INT,id,index) 
   if(loc>=8100) then 
        return IntArray2[loc-8100] 
    else 
        return IntArray1[loc] 
    endif 

endfunction 
function SVI takes integer id,integer index,integer val returns nothing //saveinteger 
    local integer loc=CountRealIndex(INT,id,index) 
   if(loc>=8100) then 
       set IntArray2[loc-8100]=val 
    else 
       set IntArray1[loc]=val 
    endif 
endfunction 
分享到:
评论

相关推荐

    简单的动态网页教程

    总的来说,这个“简单的动态网页教程”会涵盖动态网页开发的各个环节,包括前端基础、服务器端编程、数据库管理和客户端交互。无论你是完全的初学者还是希望巩固基础,这个教程都能提供有价值的指导。通过学习,你...

    数据结构(C语言版) 清华大学出版社 第八章动态存储管理 例题答案

    本资源聚焦于第八章——动态存储管理,这是编程中至关重要的一个环节,因为程序在运行时往往需要灵活地分配和释放内存。 动态存储管理主要涉及以下几个关键知识点: 1. **堆内存与栈内存**:在C语言中,内存分为栈...

    ORACLe数据库管理员教程

    Oracle数据库管理员教程旨在引导读者掌握如何管理和控制Oracle数据库系统,这一关键角色被称为DBA(Database Administrator)。DBA的职责广泛,包括理解Oracle数据库的体系结构、安装和升级数据库管理系统、控制...

    Oracle 数据库管理教程

    Oracle数据库管理是计算机科学中数据库管理系统领域的重要分支。...通过Oracle数据库管理教程的学习,数据库管理员可以获得数据库设计、管理、备份与恢复等方面的全方位知识,为日常工作中遇到的各种问题提供解决方案。

    7-4初级-信息系统运行管理员教程(目录结构图和思维导图).rar

    在本教程中,你将学习到如何管理和监控服务器、存储设备、网络设备,以及操作系统的基本操作和配置。 二、软件开发与运维结合 随着DevOps理念的普及,软件开发与运维之间的界限日益模糊。本教程会介绍如何进行有效...

    EXCEL_VBA学生成绩管理系统教程.pdf

    Excel VBA 学生成绩管理系统教程旨在教读者如何利用VBA(Visual Basic for Applications)编程语言,构建一个高效的学生考试成绩管理系统。这个系统能够自动化处理大量数据,减轻学校管理工作的负担,提高工作效率。...

    存储和文件管理解析系列视频教程

    教程名称:存储和文件管理解析系列视频教程课程目录:【】01:磁盘分区形式【】02.卷的管理【】03.动态磁盘管理【】04.设置ISCSI网络磁盘【】05:管理VHD【】06:管理NTFS权限【】07.EFS加密【】08:EFS恢复代理【】...

    联想ThinkSystem DE系列存储配置教程.docx

    联想ThinkSystem DE系列存储配置教程 联想ThinkSystem DE系列存储配置教程是联想提供的存储配置解决方案,旨在帮助用户快速配置存储系统。本教程涵盖了DE系列存储的基本配置、池和卷组的创建、工作负载的定义、卷的...

    《PHP+MySQL动态网站开发基础教程》

    MySQL则是一种流行的关系型数据库管理系统(RDBMS),用于存储和管理网站的数据。通过学习MySQL,你将了解如何创建数据库、表,并掌握SQL语言,包括数据的插入、查询、更新和删除操作。本书将引导你进行数据库设计,...

    微信小程序-云开发的图书管理小程序

    本项目是基于微信小程序的云开发实现的一个图书管理小程序,利用了微信提供的云端能力,包括数据库、存储和计算等服务。 云开发(Tencent Cloud Base,TCB)是微信小程序生态中的一项重要服务,它为开发者提供了...

    Citrix XenServer 6.0入门系列教程、安装、配置、管理存储库、高可用性、资源池

    12:配置和管理存储库(SR) 13:创建、使用资源池(Resource Pool) 14:使用vApp 15:配置高可用性(Hight Availablity) 16:虚拟机保护和恢复(VMPR) 17:配置和管理License 服务器 18:系统性能监视

    mysql经典教程+mysql存储过程讲解

    总的来说,通过学习“mysql经典教程+mysql存储过程讲解”,你不仅可以掌握MySQL的基础操作,还能深入了解如何利用存储过程、触发器和游标来实现更复杂的数据管理策略。这将有助于你成为一名更高效的数据库管理员或...

    windows server 2008 管理教程

    《Windows Server 2008 管理教程》涵盖了多方面的内容,旨在帮助管理员有效管理和维护Windows Server 2008操作系统。本教程重点介绍了以下几个核心知识点: 1. **配置终端服务**:Windows Server 2008的终端服务...

    ORACLE存储过程最全教程

    通过学习本教程,你将能够熟练地创建、调用和管理Oracle存储过程,解决复杂的业务问题,提升数据库应用的效率和安全性。教程中的20篇文档将覆盖这些知识点的详细解释和示例,帮助你逐步成为Oracle存储过程的专家。

    动态网站教程(PPT).rar

    本教程“动态网站教程(PPT)”旨在帮助初学者理解动态网站的工作原理并掌握开发技能。 在动态网站的开发中,ASP.NET是一个非常重要的平台,尤其是3.5版本,它结合了.NET Framework的强大功能和C#编程语言的简洁性...

    MySQL存储过程经典教程

    MySQL存储过程是数据库管理系统中的一种重要功能,它允许开发者预编译一组SQL语句并封装成一个可重复使用的对象。这个经典教程旨在深入探讨存储过程的各个方面,帮助读者掌握这一强大的数据库编程工具。 1. **存储...

Global site tag (gtag.js) - Google Analytics