伯克利大学数据库homework project1 实现

最近发现了一个来自伯克利大学的数据库作业,看起来很不错,所以打算认认真真的做完。

homework链接为:https://sites.google.com/site/cs186fall2013/homeworks

构建工具

作业使用ant构建工具进行构建,但我觉得使用Maven工具更好一点。

导入本地jar包

项目中涉及到了一些jar包,既可以采用远程让Maven自己下载安装的方式,也可以使用本地安装的方式。比如对于jar包:Zql,这个包我在Maven的中心仓库中找不到(可能是我没发现),所以我采用本地安装的方式。

使用Maven命令如下:mvn install:install-file -Dfile=/home/chenyu/java_jar/zql.jar -DgroupId=chenyu -DartifactId=Zql -Dpackaging=Zql -Dversion=1.0.0 -Dpackaging=jar

其中 -DgroupId,-DartifactId,-Dpackaging,-Dversion参数为必需输入的参数,可以根据自己的设置进行设定。

-Dile 参数代表着本地的jar包路径,需要注意的是必须要有 -Dpackaging 参数 。

然后在pom.xml中引入包:

开发环境

编写程序我选择了vscode+java的环境,个人觉得idea有点笨重,vscode更为的灵活一点。

project1 中涉及到的知识很少,没有涉及到并发的部分,所以一些基本的java知识足以应对,而且对于自己对面向接口编程的思维的理解大有帮助。

类的理解

Tuple: 元组。每一个元祖包含一行数据,是构成数据库数据的基本单元。Tuple中包含Field,每一个Field代表一个具体的数据,Field是一个接口,项目中StringField和IntegerField实现了该接口,说明数据类型有String和int两种。Tuple可以用HaspMap或者是Array来对Field进行索引。

TupleDesc:元组描述。顾名思义,用于描述元组。这个类对于Tuple中的每一个数据,有Type和FieldName两种描述,分别描述类型和列名。TupleDesc中将两个描述封装成了TDItem内部类。

Page: 页。存储和读取的基本单位。是一个抽象的接口。项目中使用HeapPage这个类实现了该接口并用于具体的实现。每一个Page中都有一个header,是一个字节数组。Page是由一系列的slot组成的(slot个人理解其实就是tuple)。header中的每一位代表某一个slot是否有tuple。比如:如果header是10010,代表第1个slot和第3个slot存储着tuple,但是其他slot没有tuple,只是一个空的slot。所以每一个tuple需要多余的一个bit的来存储。

所以一个页能存储的tuple数量为:

tupsPerPage = floor((BufferPool.PAGE_SIZE * 8) / (tuple size * 8 + 1))

计算出tuplesPerPage之后,我们就知道了需要用多少个字节来存储header,header需要向上取整,因为字节数组要取整数。

headerBytes = ceiling(tupsPerPage/8)

每一个Page都有一个唯一的索引用来索引这个页,这个索引被封装成了一个类:PageId,所以PageId类需要override hashCode函数。

Page需要实现一个Iterator函数,用于依次返回这个Page中的每一个Page,即header对应位为1的slot中存储的tuple。

HeapFile: 文件。实现了DFile接口。封装了涉及文件读取的操作。与文件相关的大部分内容已经由教授们实现。仅需要实现readPage等几个函数。readPage代表从文件中读取一个页(根据PageId)。具体实现如下:

    public Page readPage(PageId pid){
        byte[] buf = new byte[BufferPool.PAGE_SIZE];
        Page wantedPage = null;
        try{
            InputStream is = new FileInputStream(f);

            //skip to get the wanted data according to pageid            
            /*
                is.skip() method can skip the specific bytes from file head,
                so, we use skip method to make offset from head.
            */
            int offset = pid.pageNumber() * BufferPool.PAGE_SIZE;
            if(offset > 0)  is.skip(offset);

            //read data
            is.read(buf);
            wantedPage = new HeapPage((HeapPageId)pid,buf);
            is.close();

        }catch (IOException e){
            //throw new IOException("fail read page!");
            e.printStackTrace();
        }

        return wantedPage;
    }

同样的,HeapFile也需要实现Iterator函数,将HeapFile中存储的所有页中的每一个tuple的按照顺序进行迭代,读取页需要通过BufferPool的getPage函数,这是是BufferPool的缓存意义所在。读取出每一个页之后,就可以使用page的Iterator方法来进行迭代,具体实现上比较灵活,因为只要返回正确的Tuple顺序即可。

BufferPool: 缓冲区。用来缓存已经从硬盘读取到内存中的页(page)。并且指明了page的大小和缓冲区的大小。BufferPool中需要设置已经缓存的page的数据结构,我使用的是HashMap。BufferPool中需要实现替换算法,即Instruction中提到的eviction policy,思想和操作系统中的页面的换入换出策略思想应该相同(揣测),但在Project1中不需要实现既可以通过测试。

    public Page getPage(TransactionId tid, PageId pid, Permissions perm)
        throws TransactionAbortedException, DbException {
        Page tempPage = pageBuffers.get(pid);
        if(tempPage != null){
            return tempPage;
        }else{
            HeapFile file = (HeapFile)Database.getCatalog().getDbFile(pid.getTableId());
            Page pageRead = (HeapPage)file.readPage(pid);

            if (pageBuffers.size() == MAX_CAPACITY){
                //eviction policy
                throw new DbException("can't reach");
            }else{
                pageBuffers.put(pid,pageRead);
            }
            return pageRead;
        }        
    }

RecordID: 注释中解释的非常清楚,用来索引每一个table中的每一个page中的每一个tuple。注意有一个override hashCode,用于实际的索引,我是通过用page的索引在加上tuple的序号来实现的。

    @Override
    public int hashCode() {
        // some code goes here
        //throw new UnsupportedOperationException("implement this");
        return pid.hashCode()+tupleno;
    }

Catalog: 用于记录数据库中的Table。起到一个记录的作用,Table和HeapFile的关系有点不太理解透彻,我的理解是Table中有一个DFile的成员变量,说明了一个Table包含一个File,如果是一个DFile数组,那关系就是一个Table中的数据存储在了多个File中。

Table需要用两个元素来进行索引,一个是Table的name,另一个是Table的id。这两个索引我都使用HashMap来实现。

SeqScan:用于扫描一个Table的所有数据。即起到select * from table 的效果。SeqScan需要实现DbIterator接口,可以调用DFile的Iterator来实现。同时SeqScan需要返回一个具有可读性的列名,即通过getTupleDesc()函数。

    public TupleDesc getTupleDesc() {
        // some code goes here
        TupleDesc tempDesc = Database.getCatalog().getTupleDesc(tableId);
        int numFields = tempDesc.numFields();
        Type[] tempTypes = new Type[numFields];
        String[] tempNames = new String[numFields];

        //这里的目的是将tupledesc的显示变得更加的可读
        for(int i = 0; i < numFields; i++){
            String prefix = tableAlias == null ? "null." : tableAlias + ".";
            String suffix = tempDesc.getFieldName(i) == null ? "null" : tempDesc.getFieldName(i);
            tempNames[i] = prefix + suffix;
        }
        return new TupleDesc(tempTypes, tempNames);
    }

结果验证

测试结果

使用mvn test命令进行测试,可以指定测试的类。比如:

mvn test -Dtest=testClass

放一个我的测试结果:

简单查询实现

按照Instruction的指导,新建一个test类,然后复制代码进行一个简单的查询。

首先先转化一个负数据库文件,使用命令如下:

java -classpath .\target\classes simpledb.SimpleDb convert some_data_file.txt 3

注意我使用的是classpath参数指定的执行目录,classpath用于指定一个具体的javaclass执行目录,注意执行命令的路径:

我找到一篇博客是对这个classpath的解释的博客,具体可以看这里:

https://blog.csdn.net/bluishglc/article/details/9972951 

执行了命令之后,就会出现一个some_data_file.dat二进制文件,存储的二进制数据。

之后我们执行test类,就会在终端显示出想要的结果了。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值