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
:Tuple
由Field
组成,Field
为储存数据的单位。
TupleDesc类
参数:
private final TDItem[] tdItems;
Type fieldType
String fieldName
TupleDesc中提供了一个TDItem辅助类,类中有
Type fieldType
、String fieldName
两个参数。例如id为一个TDItem,filedName
==序号,fieldType
==int。 Type为枚举类型,实现了INT_TYPE
和STRING_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)
:合并两个tdItempublic 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
:元组的idprivate final Field[] fields
:用于存储Tuple中的所有字段
Field
接口,包含compare()
、getType()
、equals(
)、toString()
方法
IntField
和StringFiled
类实现了该接口,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对应的TupleDescpublic void setRecordId(RecordId rid)
:设置元组idpublic RecordId getRecordId()
:获得元组idpublic 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所在表的idprivate final int pgNo
:page的序号
heapPageId由page所在的表id和page的序号组成
方法:
public HeapPageId(int tableId, int pgNo)
:构造函数public int getTableId()
:返回该pageID所在表的IDpublic int getPageNumber()
:返回该pageId对应的序号public int hashCode()
:返回该pageId的hashcode"表id+page序号"public boolean equals(Object o)
:equals方法
RecordId类
参数:
private PageId pageId
:元组id所在页的pageIdprivate int tupleNo
:元组的序号
元组Id由pageId和元组序号构成
方法:
public RecordId(PageId pid, int tupleno)
:构造方法public int getTupleNumber()
:返回元组的序号public PageId getPageId()
:返回元组所属页的pageIdpublic boolean equals(Object o)
:equals方法public int hashCode()
:hashcode方法,返回"tableId+pageNo+tupleNo"
HeapPage类
参数:
private final HeapPageId pid
:pageIdprivate 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()
:返回pageIdpublic 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
:事务idprivate int tableId
:欲扫描的表的tableIdprivate String tableAlias
:表的别名,当返回TupleDesc时,要在TupleDesc.filedName前面加上tableAlias,如果tableAlias==null 或 filedName==null,则将TupleDesc.filedName设置为null.filedName、tableAlias.null、null.nullprivate 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();
}
}
}