Mit6.830-SimpleDB-Lab1-学习笔记

Lab1共有6个exercise

exercise1实现了元组以及元组的属性行


exercise2实现一个数据库的实例,包含了数据库现有的表信息。需要实现添加新表的功能,以及从特定的表中提取信息。


exercise3实现了BufferPool,负责将内存最近读过的物理页缓存下来


exercise4实现了pageId、记录Id以及page的封装


exercise5实现磁盘文件的接口,通过该接口可以从磁盘中读取信息、向磁盘中写入信息


exercise6实现了扫描功能,即SELECT * FROM table


Database

Database类提供了database中要用到的静态全局对象。其中,包括了访问catalog(database中所有表的集合)、buffer pool(驻留在内存中所有数据库文件页的集合)以及log file的方法。在这个lab中不需要关心log file。


exercise 1


实现 Tuple、TupleDesc两个类

元组和属性

数据库中,行被称为记录(record)或元组(tuple),列称为字段(field)或属性(attribute)。tuples在SimpleDB中十分基础,是一组Field对象的集合。

  • 字段(属性)

  • 记录(元组)

  • 表:表示同一类事物的组合


    在这里插入图片描述

  • Tuple:元组,即表中储存数据的一行。

  • TupleDesc:每张表中有一个TupleDesc,用于描述表中元组的属性。

  • Field: TupleField组成,Field为储存数据的单位。


TupleDesc类



参数:

  • private final TDItem[] tdItems;
    • Type fieldType
    • String fieldName

TupleDesc中提供了一个TDItem辅助类,类中有Type fieldTypeString fieldName 两个参数。例如id为一个TDItem,filedName==序号,fieldType==int。 Type为枚举类型,实现了INT_TYPESTRING_TYPE。且fieldName可以为空。

可以创建数组或链表存储TDItem。

方法:

  • public TupleDesc(Type[] typeAr, String[] fieldAr):初始化方法
  • public Iterator<TDItem> iterator():返回所有属性的迭代器

数组迭代器的返回方式

return Arrays.asList(Object[] objects).iterator();
  • public int numFields():返回TupleDesc中属性的数量

  • public String getFieldName(int i):返回第i个属性的属性名

  • public Type getFieldType(int i):返回第i个属性的属性类型

  • public int fieldNameToIndex(String name):根据属性名返回它在tdItem中的序号

  • public int getSize():返回元组所占的字节大小

public int getSize() {
  // some code goes here
  int size = 0;
  for (int i=0; i<numFields(); i++){
      size += tdItems[i].fieldType.getLen();
  }
  return size;
}

Type中存在getLen()方法,可获取INT_TYPE和STRING_TYPE字节数。

  • public static TupleDesc merge(TupleDesc td1, TupleDesc td2):合并两个tdItem
  • public boolean equals(Object o):euqals方法,只要两个TupleDesc的属性数量相等,且td1的第i个属性类型==td2的第i个属性类型,则两个TupleDesc的相等
public boolean equals(Object o) {
    // some code goes here
    if(this.getClass().isInstance(o)){
        TupleDesc tupleDesc = (TupleDesc) o;
        if(numFields() == tupleDesc.numFields()){
            for (int i=0; i<numFields(); i++)
                if(!tdItems[i].fieldType.equals(tupleDesc.tdItems[i].fieldType)){
                    return false;
                }
            return true;
        }
    }
    return false;
}

关于instanceof 和 isinstance

obj.instanceof(class) 也就是说这个对象是不是这种类型,

  • 一个对象是本身类的一个对象
  • 一个对象是本身类父类(父类的父类)和接口(接口的接口)的一个对象
  • 所有对象都是Object obj.instance(Object) true
  • 凡是null有关的都是false null.instanceof(class)

class.isInstance(obj) 这个对象能不能被转化为这个类

1.一个对象是本身类的一个对象

2.一个对象能被转化为本身类所继承类(父类的父类等)和实现的接口(接口的父接口)强转

3.所有对象都能被Object的强转

4.凡是null有关的都是false class.isInstance(null)

可见与instanceof用法相同,关键在于动态等价

对obj.instanceof(class),在编译时编译器需要知道类的具体类型

对class.isInstance(obj),编译器在运行时才进行类型检查,故可用于反射,泛型中

  • public String toString():toString方法返回TupleDesc的所有属性名:“id,name,age”

Tuple类

参数:

  • private TupleDesc tupleDesc:元组对应的属性
  • private RecordId recordId:元组的id
  • private final Field[] fields:用于存储Tuple中的所有字段

Field接口,包含compare()getType()equals()、toString()方法
IntFieldStringFiled类实现了该接口,public boolean compare(Predicate.Op op, Field val)根据查询逻辑op(equals、not_equals、greater_than、less_than等)返回该字段与val字段比较的结果



方法

  • public Tuple(TupleDesc td):构造函数
  • public TupleDesc getTupleDesc():获得Tuple对应的TupleDesc
  • public void setRecordId(RecordId rid):设置元组id
  • public RecordId getRecordId():获得元组id
  • public void setField(int i, Field f):为字段赋值
  • public Field getField(int i):获得指定字段
  • public String toString():toString()方法返回所有字段
  • public Iterator<Field> fields():返回字段的迭代器
  • public void resetTupleDesc(TupleDesc td):重置TupleDesc


exercise 2


实现 Catalog类

  • Catalog:目录。数据库包含很多张表,每张表有一个TupleDesc,以及这个TupleDesc规范下的很多个Tuple。Catalog管理着数据库中的所有表。调用数据库的Catalog需要调用Database.getCatalog()方法。
  • DbFile:为数据库磁盘文件的接口。数据库中每张表对应着一个DbFile,DbFile储存着表中的所有信息。

在这里插入图片描述

CataLog类


参数:

  • private final HashMap<Integer,Table> catalog:表id与表的映射
  • private final HashMap<String,Integer> hashMap:表名字与表id的映射,方便后续通过表名获取表id

CataLog提供的辅助类Table,包含参数tableName、primartKey(表中的主键)、DbFile dbFile (用于存储表的内容)

DbFile中提供了getId()方法,可以获取此Dbfile对应表的tableid。这个id并不是顺序生成,后续exercise中通过file.getAbsoluteFile().hashCode()生成的



方法:

  • public Catalog():构造方法

  • ublic void addTable(DbFile file, String name, String pkeyField):向CataLog中添加表
    主键可为空、name可为空,name为空时随机一个UUID作为其name。

  • public int getTableId(String name):通过表名获得表id

  • public TupleDesc getTupleDesc(int tableid):通过表id 获得表的TupleDesc

  • public DbFile getDatabaseFile(int tableid):通过表id 获得表的内容DbFile

  • public String getPrimaryKey(int tableid):通过表id 获得表的主键

  • public Iterator<Integer> tableIdIterator():返回tableId的迭代器

return catalog.keySet().iterator();
  • public String getTableName(int id):通过表id 获得表名
  • public void clear():清空CataLog



exercise 3


实现BufferPool类

buffer pool(在SimpleDB中是BufferPool类)负责将内存最近读过的物理页缓存下来。所有的读写操作通过buffer pool读写硬盘上不同文件,BufferPool里的numPages参数确定了读取的固定页数,在之后的lab中,需要实现淘汰机制(eviction policy)。在这个lab中,只需要实现构造器和BufferPool.getPage()方法,BufferPool应该存取最多numPages个物理页,当前lab中如果页的数量超过numPages,先不实现eviction policy,先扔出一个DbException错误。

通过Database.getBufferPool()获得bufferPool




一个table/Dbfile中有多个page 一个page中有多个tuple

在这里插入图片描述

BufferPool类



参数

  • private static final int DEFAULT_PAGE_SIZE = 4096
  • private static int pageSize = DEFAULT_PAGE_SIZE:默认的page大小
  • private final int numPages:bufferPool能够读取的page数量
  • private final HashMap<PageId,Page> buffferPool:pageId到page的映射

方法

  • public BufferPool(int numPages):构造函数
  • public Page getPage(TransactionId tid, PageId pid, Permissions perm):从缓冲池中获取pid对应的page,如果缓冲池中没有就去硬盘中搜索并保存到缓冲池中,如果缓冲池满了则抛出异常
public Page getPage(TransactionId tid, PageId pid, Permissions perm)
    throws TransactionAbortedException, DbException {
    // some code goes here
    if(!buffferPool.containsKey(pid)){
        if(numPages > buffferPool.size()){
            //bufferpool 中没有指定页,到disk中去找到对应的page  并将它保存到bufferpool中
            //1、在disk中找到page 所在的Dbfile/Table
            DbFile dbFile = Database.getCatalog().getDatabaseFile(pid.getTableId());
            //2、在Dbfile中找到 pid所对应的page
            Page page = dbFile.readPage(pid);
            buffferPool.put(pid,page);
        }
        else{
            throw new DbException("bufferPool is full");
        }
    }
    return buffferPool.get(pid);
}

只有bufferPool能够获取page后续获取page的方法,都是调用bufferPool的getPage()方法。


exercise 4


实现HeapPageId类、RecordId类、HeapPage类

HeapPage中包含多个slot和一个header,每个slot是留给一行的位置。header是每个tuple slot的bitmap。如果bitmap中对应的某个tuple的bit是1,则这个tuple是有效的,否则无效(被删除或者没被初始化)。

文档内容:
虽然不会在Lab1中直接使用它们,但我们要求您在HeapPage中实现getNumEmptySlots()和isSlotUsed()。这需要在页眉中插入位。查看HeapPage或src/simpledb/HeapFileEncoder中提供的其他方法可能会有所帮助。java来理解页面的布局。您还需要对页面中的元组实现迭代器,这可能涉及辅助类或数据结构。


HeapPageId类

参数:

  • private final int tableId:page所在表的id
  • private final int pgNo:page的序号

heapPageId由page所在的表id和page的序号组成

方法:

  • public HeapPageId(int tableId, int pgNo):构造函数
  • public int getTableId():返回该pageID所在表的ID
  • public int getPageNumber():返回该pageId对应的序号
  • public int hashCode():返回该pageId的hashcode"表id+page序号"
  • public boolean equals(Object o):equals方法

RecordId类

参数

  • private PageId pageId:元组id所在页的pageId
  • private int tupleNo:元组的序号

元组Id由pageId和元组序号构成

方法

  • public RecordId(PageId pid, int tupleno):构造方法
  • public int getTupleNumber():返回元组的序号
  • public PageId getPageId():返回元组所属页的pageId
  • public boolean equals(Object o):equals方法
  • public int hashCode():hashcode方法,返回"tableId+pageNo+tupleNo"

HeapPage类

参数:

  • private final HeapPageId pid:pageId
  • private final TupleDesc td:page对应的属性行
  • private final byte[] header:slot的bitmap,用于判断slot是否被占用
  • private final Tuple[] tuples:page中的元组
  • private final int numSlots:page中slot的数量

方法

  • private int getNumTuples():返回每个page中最多包含的tuple数
private int getNumTuples() {        
    // some code goes here
    //_tuples per page_ = floor((_page size_ * 8) / (_tuple size_ * 8 + 1))
    int tupsPerPage = (int) Math.floor(BufferPool.getPageSize() * 8) / (td.getSize() * 8 + 1);
    return tupsPerPage;
}

SimpleDB数据库的每个tuple需要tuple size * 8 bits 的内容大小和1 bit的header大小。因此,在一页中可以包含的tuple数量计算公式是:tuples per page = floor((page size * 8) / (tuple size * 8 + 1))。其中,tuple size是页中单个tuple 的bytes大小。

  • private int getHeaderSize():返回page中的header的大小
private int getHeaderSize() {
    // some code goes here
    //每个page最多能存储的元组数 / 8 向上取整
    int headerSize = (int)Math.ceil(getNumTuples() * 1.0 / 8);
    return headerSize;
             
}

一旦知道了每页中能够保存的tuple数量,需要的header的物理大小是:headerBytes = ceiling(tupsPerPage/8)。

  • public HeapPageId getId():返回pageId
  • public boolean isSlotUsed(int i):判断第i个slot是否为空
public boolean isSlotUsed(int i) {
    // some code goes here
    //header中的每一个元素是一个8位的byte
    int index = i / 8;
    int offset = i % 8;
    int bit = header[index]>>offset & 1;
    return bit == 1;
}

header储存方式为byte数组

private final byte[] header;

每个byte包含8个bit,假如某个header状态如下:

{[11111111],[11111111],[00000011]}

则表明0~17号slot正在使用,18号及以上未被使用。(byte的右侧为低位)


bitmap中,low bits代表了先填入的slots状态。因此,第一个headerByte的最小bit代表了第一个slot是否使用,第二小的bit代表了第二个slot是否使用。

  • public int getNumEmptySlots():返回page中空slot的数量
public int getNumEmptySlots() {
    // some code goes here
    int cnt = 0;
    for(int i=0; i<numSlots; i++){
        if(!isSlotUsed(i)){
            cnt++;
        }
    }
    return cnt;
}
  • public Iterator<Tuple> iterator():返回HeapPage中所有元组的迭代器(不能包括空slot)
public Iterator<Tuple> iterator() {
    // some code goes here
    ArrayList<Tuple> filledSlot = new ArrayList<Tuple>();
    for(int i=0; i<numSlots; i++){
        if(isSlotUsed(i)){
            filledSlot.add(tuples[i]);
        }
    }
    return filledSlot.iterator();
}

Lab1中HeapPage只需实现以上方法


exercise 5

实现HeapFile类
一个HeapFile就是一个表

HeapFile对象包含一组“物理页”,每一个页大小固定,大小由BufferPool.DEFAULT_PAGE_SIZE定义,页内存储行数据。在SimpleDB中,数据库中每一个表对应一个HeapFile对象,HeapFile对象中的物理页的类型是HeapPage,物理页是存储在buffer pool中,通过HeapFile类读写。

HeapFile类实现了DbFile的接口

在这里插入图片描述

文档内容:
要从磁盘读取页面,首先需要计算文件中的正确偏移量。提示:需要随机访问该文件,以便以任意偏移量读取和写入页面。从磁盘读取页面时,不应调用缓冲池方法。
还需要实现HeapFile.iterator()方法,它应该遍历HeapFile中每个页面的元组。迭代器必须使用BufferPool.getPage()方法来访问“HeapFile”中的页面。此方法将页面加载到缓冲池中,并最终用于(后续的Lab)实现基于锁定的并发控制和恢复。不要在调用open()时将整个表加载到内存中——这将导致非常大的表出现内存不足错误。



参数:

  • private final File file:表中的内容
  • private final TupleDesc tupleDesc: 表的属性行

方法:

  • public HeapFile(File f, TupleDesc td):构造函数
  • public File getFile():返回表的内容
  • public int getId():返回标识次表的唯一id

应该确保每个表都有一个唯一的id,对于特定的HeapFile文件返回相同的值。文档建议使用heapfile文件的绝对文件名进行hash

public int getId() {
    // some code goes here
    return file.getAbsoluteFile().hashCode();
}
  • public TupleDesc getTupleDesc():返回表的属性行
  • public int numPages():返回表中的page数目
public int numPages() {
    // some code goes here
    return (int)(file.length() / Database.getBufferPool().getPageSize());
}
  • public Page readPage(PageId pid):读取page

在读取page时,readPage()方法仅会被BufferPool中的getPage()方法调用,而在其他位置需要获取page时,均要通过BufferPool调用。这也是BufferPool的意义所在。

RandomAccessFile去读文件,通过seek(offset)可以直接访问偏移量所对应的位置而不用从头进行查找,然后read(buf),将偏移量后面buf.length长度的数据保存到buf中。

public Page readPage(PageId pid) {
    // some code goes here
    int tableId = pid.getTableId();
    int pageNumber = pid.getPageNumber();
	//tableId和pageNumber 用于获取heapPageId
	
    int pageSize = Database.getBufferPool().getPageSize();
    //page是数据库处理的最基本数据单元,每次读取一个page
    long offset = pageNumber * pageSize;
    byte[] data = new byte[pageSize];
    RandomAccessFile rfile = null;
    try{
        rfile = new RandomAccessFile(file,"r");
        rfile.seek(offset);
        rfile.read(data);
        HeapPageId heapPageId = new HeapPageId(tableId,pageNumber);
        HeapPage heapPage = new HeapPage(heapPageId,data);
        return heapPage;

    } catch (FileNotFoundException e) {
        throw new IllegalArgumentException("HeapFile: readPage: file not found");
    } catch (IOException e) {
        throw new IllegalArgumentException(String.format("HeapFile: readPage: file with offset %d not found",offset));
    } finally {
        try {
            rfile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • public DbFileIterator iterator(TransactionId tid):返回HeapFile中所有的heapPage 中的所有元组的迭代器
public DbFileIterator iterator(TransactionId tid) {
    // some code goes here
    return new HeapFileIterator(tid);
}

private class HeapFileIterator implements DbFileIterator{
    private final TransactionId tid;
    private Iterator<Tuple> tupsIterator;
    private final int tableId;
    private final int numPages;
    private int pageNo;


    public HeapFileIterator(TransactionId transactionId) {
        this.tid = transactionId;
        tableId = getId();
        numPages = numPages();
    }


    @Override
    public void open() throws DbException, TransactionAbortedException {
        pageNo = 0;
        tupsIterator = getTuplesIterator(pageNo);
    }

    private Iterator<Tuple> getTuplesIterator(int pageNumber) throws DbException, TransactionAbortedException {
        if(pageNumber>=0 && pageNumber<numPages){
            HeapPageId heapPageId = new HeapPageId(tableId,pageNumber);
            HeapPage heapPage = (HeapPage) Database.getBufferPool().getPage(tid, heapPageId, Permissions.READ_ONLY);
            return heapPage.iterator();
        } else {
            throw new DbException(String.format("heapfile %d does not contain page %d!",tableId, pageNumber));
        }

    }

    @Override
    public boolean hasNext() throws DbException, TransactionAbortedException {
        if(tupsIterator == null){
            return false;
        }
        if(!tupsIterator.hasNext()){
            if(pageNo < (numPages-1)){
                pageNo++;
                tupsIterator = getTuplesIterator(pageNo);
                return tupsIterator.hasNext();
            }else {
                return false;
            }
        } else {
            return true;
        }

    }

    @Override
    public Tuple next() throws DbException, TransactionAbortedException, NoSuchElementException {
        if(tupsIterator==null || !tupsIterator.hasNext()){
            throw new NoSuchElementException("This is the last element");
        }
        return tupsIterator.next();
    }

    @Override
    public void rewind() throws DbException, TransactionAbortedException {
        close();
        open();
    }

    @Override
    public void close() {
        tupsIterator = null;
    }
}

Lab1中只需实现以上方法


exercise 6

实现SeqScan类

实现SELECT * FROM table 的功能

文档内容:
该类应该能够通过构造函数时传入的tableId扫描对应表中的所有元组,使用DbFile.iterator()访问元组

参数:

  • private final TransactionId tid:事务id
  • private int tableId:欲扫描的表的tableId
  • private String tableAlias:表的别名,当返回TupleDesc时,要在TupleDesc.filedName前面加上tableAlias,如果tableAlias==null 或 filedName==null,则将TupleDesc.filedName设置为null.filedName、tableAlias.null、null.null
  • private DbFileIterator iterator:用于遍历表中的所有tuple

方法:

  • public SeqScan(TransactionId tid, int tableid, String tableAlias):构造函数
  • public String getAlias():返回表的别名
  • public void reset(int tableid, String tableAlias):查找新的表,重新对tableId、tableAlias赋值
  • public TupleDesc getTupleDesc():返回tupleDesc,在tupleDesc中的filedName前添加表的别名
public TupleDesc getTupleDesc() {
    // some code goes here
    TupleDesc tupleDesc = Database.getCatalog().getTupleDesc(tableId);
    String prefix = "null";

    if(tableAlias != null){
        prefix = tableAlias;
    }
    int length = tupleDesc.numFields();
    Type[] typeAr = new Type[length];
    String[] fieldAr = new String[length];
    for(int i=0; i<length; i++){
        typeAr[i] = tupleDesc.getFieldType(i);
        String filedName = "null";
        if(tupleDesc.getFieldName(i) != null){
            filedName = tupleDesc.getFieldName(i);
        }
        fieldAr[i] = prefix + "." + filedName;
    }
    tupleDesc = new TupleDesc(typeAr,fieldAr);
    return tupleDesc;
}
  • 遍历表需要用到的方法:
public void open() throws DbException, TransactionAbortedException {
    // some code goes here
    DbFile table = Database.getCatalog().getDatabaseFile(tableId);
    iterator = table.iterator(tid);
    iterator.open();
}
public boolean hasNext() throws TransactionAbortedException, DbException {
	// some code goes here
	if(iterator != null){
    	return iterator.hasNext();
	}
	throw new TransactionAbortedException();
}

public Tuple next() throws NoSuchElementException,
        TransactionAbortedException, DbException {
    // some code goes here
    Tuple tuple = iterator.next();
    if(tuple != null){
        return tuple;
    } else {
      throw new NoSuchElementException("This is the last element");
    }
}

public void close() {
    // some code goes here
    iterator.close();
}

public void rewind() throws DbException, NoSuchElementException,
        TransactionAbortedException {
    // some code goes here
    close();
    open();
}


测试

创建一个"some_data_file.txt"文件,内容如下:

1,1,1
2,2,2 
3,4,4

将其转换为二进制文件。
以下代码对此文件实现了一个简单的选择查询,对some_data_file.dat进行查询。这段代码相当于 SQL 语句 SELECT * FROM some_data_file

public class test {
    public static void main(String[] args) {
        Type[] typeAr = new Type[]{Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE};
        String[] fieldAr = new String[]{"filed0", "field1", "field2"};
        TupleDesc tupleDesc = new TupleDesc(typeAr, fieldAr);
        HeapFile heapFile = new HeapFile(new File("some_data_file.dat"), tupleDesc);
        Database.getCatalog().addTable(heapFile, "testTable", "filed0");

        TransactionId tid = new TransactionId();
        int tableId = heapFile.getId();
        SeqScan seqScan = new SeqScan(tid, tableId);
        try {
            seqScan.open();
            while (seqScan.hasNext()) {
                Tuple tuple = seqScan.next();
                System.out.println(tuple);
            }
            Database.getBufferPool().transactionComplete(tid);
        } catch (TransactionAbortedException e) {
            e.printStackTrace();
        } catch (DbException e) {
            e.printStackTrace();
        } finally {
            seqScan.close();
        }

    }
}
  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值