一起写一个数据库(一)
项目来源:https://ziyang.moe/article/mydb1.html
事务的定义及相关操作
XID的定义
在MyDB中,每一个事务都有一个XID,这个XID唯一标识了这个事务。
- XID编号从1开始自增,不可重复
- 编号0为超级事务(Super Transaction)
- 当一些操作想在没有申请事务的情况下进行,那么可以将该事务的XID编号设置为0
- XID为0的事务的状态永远为committed
“ TransactionManager通过维护一个XID格式的文件来维护事务的状态并提供接口查询某个事务的状态。”
- XID文件的头部保存了一个8字节的数字,记录这个XID文件管理的事务个数
- XID文件给每个事务分配了一个字节的空间用来保存其状态
- XID 编号为0的状态不需要记录
- 主要属性及全局实例定义:
//XID文件头长度
private static final int LEN_XID_HEADER_LENGTH = 8;
//给每个事务分配一个字节的空间用来存储其状态
private static final int XID_FIELD_SIZE = 1;
//事务的三种状态
private static final byte FIELD_TRAN_ACTIVE = 0;
private static final byte FIELD_TRAN_COMMITTED = 1;
private static final byte FIELD_TRAN_ABORTED = 2;
//超级事务,永远为Committed状态
public static final long SUPER_XID = 0;
private RandomAccessFile file;
private FileChannel fc;
private long xidCounter;
private Lock counterLock;
构造方法
通过属性也能看出,基本上就是通过FileChannel结合RandomAccessFile的方式来实现的
private TransactionManager (RandomAccessFile raf,FileChannel fc){
this.file = raf;
this.fc = fc;
counterLock = new ReentrantLock();
checkXIDCounter();
}
其中的checkXIDCounter()方法用于检查XID文件是否合乎规定
/*
检查XID文件是否合乎规定
校验:通过文件头的8字节数字反推文件的理论长度,与文件的实际长度做对比
*/
private void checkXIDCounter() {
long fileLen = 0;
try {
fileLen = file.length();
}catch (IOException e){
Panic.panic(new RuntimeException("非法的XID文件!"));
}
if (fileLen < LEN_XID_HEADER_LENGTH) {
Panic.panic(new RuntimeException("非法的XID文件!"));
}
ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH);
try{
fc.position(0);
fc.read(buf);
} catch (IOException e) {
Panic.panic(e);
}
this.xidCounter = Parser.parseLong(buf.array());
long end = getXIDPosition(this.xidCounter + 1);
//读取XID_FILE_HEADER中的xidCounter,根据它计算文件的理论长度,对比实际长度
if ( end != fileLen ) {
Panic.panic(new RuntimeException("非法的XID文件!"));
}
}
- getXIDPosition()方法
//根据事务xid取得其在XID文件中对应的位置
private long getXIDPosition(long xid){
return LEN_XID_HEADER_LENGTH + (xid-1)*XID_FIELD_SIZE;
}
创建方式
public static TransactionManager create(String path){
File f = new File(path);
try {
if (!f.createNewFile()){
Panic.panic(new RuntimeException("File already exists!"));
}
} catch (Exception e) {
Panic.panic(e);
}
if (!f.canRead() || !f.canWrite()){
Panic.panic(new RuntimeException("File can not Read or Write!"));
}
FileChannel fc = null;
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(f,"rw");
fc = raf.getChannel(); //通过RandomAccessFile获取FileChannel的实例
} catch (FileNotFoundException e){
Panic.panic(e);
}
return new TransactionManager(raf,fc);
}
FileChannel1
FileChannel使用一个InputStream/OutputStream或RandomAccessFile来获取一个FileChannel实例
RandomAccessFile raf = new RandomAccessFile("test.txt"); raf.getChannel();
Channel的读写方法
从文件A中读取内容并写入到文件B:
ByteBuffer buf = ByteBuffer.allocate(1024); buf.clear(); channelA.read(buf); buf.flip(); while(buf.hasRemaining()) { channelB.write(buf); }
用完后关闭Channel
- public final void close( )
特定位置的读写操作
long pos = channel.position(); channel.position(pos + 100);
将位置设在文件符结束之后
- 然后试图从文件通道中读取数据,读方法将返回-1(代表文件结束)
- 试图向通道中写数据,文件将撑大到当前位置并写入数据,这可能导致“文件空洞”,磁盘上的物理文件中写入的数据间有空隙,撑大的那一部分将以0填充,但却不占用实际的磁盘空间(了解即可,不建议这种操作)
返回实例所关联文件的大小
- size( )
截取指定长度的文件,指定长度后面的部分将被删除
- truncate( 1024 ) //1024临时示意
将通道里尚未写入磁盘的数据强制写到磁盘上
出于性能方面的考虑,操作系统会将数据缓存到内存中,无法保证写入到Channel里的数据一定会即时写到磁盘上
- force( boolean )
事务操作的相关方法
- private void updateXID( long xid, byte status )
- 更新事务的状态为status
- private void incrXIDCounter( )
- 将XID+1,并更新XIDHeader
- public long begin( )
- 开始一个事务并返回XID
- public void commit(long xid)
- 提交XID事务
- public void abort(long xid)
- 回滚XID事务
- private boolean checkXID(long xid, byte status)
- 检查XID事务是否处于status状态
- public boolean isActive( long xid )
- 事务的状态是否正在进行
- public boolean isCommitted( long xid )
- 事务的状态是否已经提交
- public boolean isAborted( long xid )
- 事务的状态是否已经取消
关闭流
public void close() {
try {
fc.close();
file.close();
} catch (IOException e){
Panic.panic(e);
}
}
公共类
public class Parser {
public static long parseLong(byte[] buf){
ByteBuffer buffer = ByteBuffer.wrap(buf,0,8);
return buffer.getLong();
}
public static byte[] long2Byte(long value){
return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array();
}
}
工具类
public class Panic {
public static void panic(Exception err){
err.printStackTrace();
System.exit(1);
}
}