这是我之前在武汉大学彭智勇老师那边做过的一个对象代理数据库系统。文中给出了一整个系统的几乎所有代码,经测试可正常运行。文章比较长,超出了知乎的最长文章范围,因此分为两篇文章。这是第二篇。

执行

执行部分是整个系统最复杂的一个部分,稍不注意就会出错。并且这一部分的错误很难定位。

执行部分首先从编译部分得到数据,存入自己的数据结构中,然后对不同的语句,执行对应的操作,并将操作的结果及时交给存储部分,由存储部分存到硬盘。

整个系统的各个部分都是在执行部分调用的。可以说,执行部分是整个系统最核心的部分。

执行数据结构

编译部分的数据结构核心是方便编译时对数据的存储。执行部分,我们另外设计了一个数据结构,用来存放每个类的信息,以方便执行部分调用。虽然在存储部分我们设计了很多张表,但是我们并不会在内存里面同时维护那么多的表,而是将一个类我们需要的所有信息都读到执行部分的数据结构中。这种方式可以明显减少程序的内存占用。

class ClassStruct{

public int tupleNum;//元组数量,未使用 public String className;//类名 public String selectClassName; //代理的源类,仅代理类有用 public ArrayList children;//源类的孩子在哪里,仅源类有用,因为仅支持一级代理 public ArrayList attrList;//源类的实属性列表,代理类的实属性列表包括默认值 public String condition;//代理条件,仅代理类有用,实质存储的是创建该类的时候的sql语句 public ArrayList virtualAttr;//虚属性,仅仅代理类使用,attrName存储表达式(有的话),attrReName,存储重命名}

执行数据流

在执行的过程中,用户输入一条SQL语句,点击执行按钮,系统才会执行。因此我们重载了onClick函数,在这个函数里面进行处理。

调用分析器

用户输入的SQL语句,对于系统而言是一个string串。讲这个string串放到编译部分提供的分析器里面去分析,即可得到编译的结果。在后续的过程中,我们将多次调用这个编译的结果result。

sql = editText.getText().toString();

result = SQLParser.sqlParse(sql);//在这边调用语法解析器

执行处理过程

接下来,我们调用了一个分析函数Analyze,在这里完成了执行部分的处理。函数Analyze维护两个全局变量:tip:不同语句情况下给用户的提示信息

tf:标志位,判断是否执行成功

执行成功则提示用户成功,否则提示用户失败。如果是select语句,还要显示select的结果,因此需要跳转到select的界面,并将select的结果显示出来。

Analyze();//这边是主要的执行部分 if(tf){// return true if(result.Type != 5 && result.Type != 6){//5,6为查找,则提示查找成功 Toast.makeText(MainActivity.this, tip, Toast.LENGTH_SHORT).show();

if(tf)editText.getText().clear();

}else{// select Intent intent = new Intent(MainActivity.this, activity_table.class);//这就下一个界面了 intent.putExtra("sql", sql);

Bundle bundle=new Bundle();

bundle.putParcelableArrayList("tuples", (ArrayList)tuples);//这边继续显示 intent.putExtras(bundle);

startActivity(intent);

if(tf)editText.getText().clear();

}

}else{// return false Toast.makeText(MainActivity.this, tip, Toast.LENGTH_SHORT).show();

}

传给显示页面的参数是select执行之后获得的二维数组tuples,将其依次输入到表格中即可。

if(tuples.size() != 0){

int column = tuples.get(0).size();

int row = tuples.size();

int gridMinWidth = getScreenWidth()/column - column+1;

gridLayout.setColumnCount(column);

gridLayout.setRowCount(row);

for(int i = 0; i < row; i++){

for(int j = 0; j < column;j++){

GridLayout.Spec rowSpec=GridLayout.spec(i, 1, GridLayout.UNDEFINED);

GridLayout.Spec columnSpec=GridLayout.spec(j, 1, GridLayout.UNDEFINED);

GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpec, columnSpec);

params.setGravity(Gravity.CENTER);

TextView values = new TextView(this);

values.setText(tuples.get(i).get(j));

values.setTextSize(15);

values.setWidth(gridMinWidth);

// values.setHeight(30); values.setGravity(Gravity.CENTER);

values.setBackground(getResources().getDrawable(R.drawable.grid_frame));

gridLayout.addView(values, params);

gridLayout.setPadding(0,0,0,0);

}

}

}

编译出错处理

用户的输入可能不符合语法,编译不通过,此时编译部分将无法匹配,也即无法得到结果。因此,返回值为空,此时直接退出,提示语法错误即可。

//Toast tip = "Please check your syntax!";

Toast.makeText(MainActivity.this, tip, Toast.LENGTH_SHORT).show();

editText.setText(editText.getText());

执行细节

在Analyze函数中,我们分别调用了下面的函数,去完成不同的执行工作。

创建源类

源类是最基础的类,只需要将数据按照数据结构设计的内容分别存储即可。

//创建源类 static boolean Create(){

ClassStruct classStruct = new ClassStruct();

classStruct.className = result.className;//className存放的是类名 classStruct.selectClassName = null;

classStruct.attrList = result.attrList;//实属性列表 classStruct.virtualAttr = null;

classStruct.condition = sql;//condition先不管他,先存起来,后面再分析 classStruct.tupleNum = 0;

classStruct.children = new ArrayList<>();

return storeAPI.createClass(result.className,classStruct);

}

值得一提的是,在存储表的时候,在执行部分我们只是调用了存储部分给的函数,并没有去维护这些表。创建表的工作由存储来完成。

创建代理类

源类在创建之初一定是空的,而代理类并不是这样。根据代理规则,代理类在创建之初,源类中符合代理规则的记录就将自动进入代理类中。因此,在创建代理类的时候,就需要找到源类,并根据代理规则进行第一次的更新。我们的更新操作是直接调用isSatisfy函数遍历所有源类中的记录,将符合条件的记录添加到代理类中。代理类中有这些记录,但是其虚属性是不保存的,而是由代理规则在查询的过程中实时计算出来。此时,我们不需要查询,因此代理规则并没有用到。由于代理规则的处理较为复杂,并且并不是全局需要的,我们在创建代理类的时候,并不直接存储代理规则。我们在代理规则部分依旧存储原语句,而在后续需要使用代理规则的时候,我们再重新调用编译部分的函数将其解析一次,以得到真正的代理规则。

//创建代理类 static boolean CreateSelectDeputy(){

ClassStruct classStruct = new ClassStruct();

classStruct.className = result.className;//类名 classStruct.selectClassName = result.selectClassName;//源类名 classStruct.children = new ArrayList<>();

classStruct.condition = sql;//将sql放到condition中间,留作后面继续解析获得where属性 classStruct.virtualAttr = result.attrNameList;//虚属性 classStruct.attrList = result.attrList;//实属性 if(storeAPI.existClass(classStruct.selectClassName)==false)return false;//如果不存在源类,就报错 if(!storeAPI.createClass(result.className,classStruct))return false;//创建失败,则返回错误

ClassStruct fa_classStruct = storeAPI.getClassStruct(result.selectClassName);//找到他们的父节点 fa_classStruct.children.add(classStruct.className);//父节点的孩子里面加上类名 Log.d("Hello", "CreateSelectDeputy: "+fa_classStruct.children.size());//记录日志,用于debug storeAPI.setClassStruct(fa_classStruct.className,fa_classStruct);

//更新迁移 storeAPI.initial(fa_classStruct.className);

MEM.initial(classStruct.className);

ArrayList tuple = null;

ArrayList son_tuple = new ArrayList<>();

son_tuple.add("-1");//pointers if(classStruct.attrList!=null)

for(int i=0;i

son_tuple.add(classStruct.attrList.get(i).defaultVal);

}//实属性如果有的话,直接就加上 while((tuple=storeAPI.Next())!=null){

int son_offset = -1;

if(isSatisfy(result.where,fa_classStruct,tuple)){

int offset = storeAPI.getOffset();

son_tuple.set(0,Integer.toString(offset));

MEM.insert(classStruct.className,son_tuple);

son_offset = MEM.getOffset();//将满足条件的虚属性加上 }

ArrayList tmp = storeAPI.decode(tuple.get(0));

tmp.add(Integer.toString(son_offset));

tuple.set(0,storeAPI.encode(tmp));

storeAPI.update(tuple);

}

MEM.flushToDisk();

return true;

}

删除记录

删除操作是将记录删除的操作。但是在对象代理数据库中,如果一个类有自己的代理类,那么很可能这条记录被它的代理类代理了。删除这条记录的同时,也应当将代理类中的记录删除。

在执行部分,我们只考虑将块删除,对于系统表一致性的维护,由存储部分完成。

//从源类中删除一个元组,并把孩子的该元组都删除 Return true Anyways! static boolean Delete(){

ClassStruct classStruct = storeAPI.getClassStruct(result.className);

//获取该类的所有结构,用于后面找孩子 ArrayList tuple = null;

storeAPI.initial(result.className);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,storeAPI.getClassStruct(result.className),tuple)){

//找孩子 ArrayList nexts = storeAPI.decode(tuple.get(0));

for(int i=0;i

if(!nexts.get(i).equals("-1")) {

int offset = Integer.parseInt(nexts.get(i));

//获取对应的块 MEM.initial(classStruct.children.get(i),

offset/StoreAPI.PAGESIZE,offset%StoreAPI.PAGESIZE);

ArrayList _t = MEM.Next();

MEM.delete();//将孩子也都删除 }

}

storeAPI.delete();//将记录删除 }

}

MEM.flushToDisk();//将删完的刷到磁盘上去,也即将这块删除 return true;

}

删除类

删除类分为两种,一是删除源类,一是删除代理类。我们首先判断要删除的类是源类还是代理类,接着分别进行处理。

删除源类

删除一个源类,就也要将其代理类都删除,其实就是删除了整棵树。

我们递归删除了整个由源类指向代理类的指针生成的树,实现了对一个类的删除。

//删除源类,实质上会把整棵树删除 static boolean Drop(String className){

if(storeAPI.existClass(className)==false){return false;}//不存在,则返回错误

ClassStruct classStruct = storeAPI.getClassStruct(className);

storeAPI.dropClass(className);

if(classStruct!=null)

for(int i=0;i

Drop(classStruct.children.get(i));//依次递归删除各个节点 }

return true;

}

删除代理类

删除代理类和删除源类的区别在于,删除代理类除了要将它自己的孩子都删除外,还要将它的父节点指向它的指针也都删除。我们的做法是便利它父节点的所有孩子,找到属于他的信息,然后将之删除。接着就把它当做源类,调用上面的删除源类的函数,完成接下来的删除处理。

//删除代理类 static boolean DropSelectDeputy(String className){

if(!storeAPI.existClass(className)){

return false;

}

//剔除父亲的关于它的指针 ClassStruct fa_classStruct = storeAPI.getClassStruct(

storeAPI.getClassStruct(className).selectClassName);

int index = 0;

if(fa_classStruct.children!=null)

for(;index

if(fa_classStruct.children.get(index).equals(className)){

fa_classStruct.children.remove(index);//找到对应的index,就将他删除 storeAPI.setClassStruct(fa_classStruct.className,fa_classStruct);

storeAPI.initial(fa_classStruct.className);

ArrayList tuple = null;

while((tuple = storeAPI.Next())!=null){

ArrayList pointer = storeAPI.decode(tuple.get(0));

pointer.remove(index);

tuple.set(0,storeAPI.encode(pointer));

storeAPI.update(tuple);//将删完的刷到磁盘上去 }

break;

}

}

Drop(className);

return true;

}

插入

插入操作是一个较为复杂的操作,因为它存在这些问题:编译部分获得的信息只有插入的类名,需要我们自己去获得类的属性表

插入的类型可能是非法的,我们需要进行类型检测

插入后,如果该类有代理类,而插入的语句符合代理规则,那么就需要将该记录同样插入到代理类中

因此,我们首先获得了要插入的类现有的类结构,同时将编译获取到的参数列表转换正确的形式。比如,对于所有的整型数据,我们在编译部分都是按照字符串存储的,现在需要将其转换成整型。此过程也检验插入数据是否合法,因为非法的数据是无法转换成功的。接着将合法的类型插入。

如果存在子类,我们则调用isSatisfy函数,判断其是否符合代理规则。如果符合代理规则,就将记录也插入子类中。在插入子类的时候,还要加上子类的实属性,由于此时没有给子类的实属性赋值,我们便插入了默认值。最后将执行结果刷进磁盘。

//insert static boolean Insert()

{

//获取要插入的源类的类结构 ClassStruct classStructOfSourceClass = storeAPI.getClassStruct(result.className);

//类型检查 try

{

//获取要插入的源类的属性类型等信息列表 ArrayList attrListOfSourceClass = classStructOfSourceClass.attrList;

Attribute attrTemp;

if(attrListOfSourceClass!=null)

for(int i = 0; i < attrListOfSourceClass.size(); i++)

{

attrTemp = attrListOfSourceClass.get(i);

if(attrTemp.attrType == 0)

{

Integer.parseInt(result.valueList.get(i));

}

}

}

catch(NumberFormatException e)

{

System.out.println("Type Error!");

return false;

}

/*建立新tuple的0号位,没有就是空串*/

ArrayListsourcePointer = new ArrayList();

result.valueList.add(0,storeAPI.encode(sourcePointer));

storeAPI.insert(result.className, result.valueList);

//获取新插入的tuple的offset int sourceOffset = storeAPI.getOffset();

//若存在子类,要判断是否插入子类,并且更新父亲的指针 if(classStructOfSourceClass.children != null && classStructOfSourceClass.children.size() > 0)

{

for(int i = 0; i < classStructOfSourceClass.children.size(); i++)

{

//用MEM块来处理子块 ClassStruct classStructOfChildrenClass = MEM.getClassStruct(classStructOfSourceClass.children.get(i));

//对创建子类时的SQL语句重新解析,目的是获得Where的结构 ParseResult conditionResult = SQLParser.sqlParse(classStructOfChildrenClass.condition);

//判断的是父类的实属性和要插入的tuple值 if(isSatisfy(conditionResult.where, classStructOfSourceClass, result.valueList))

{

//如果符合条件,则也插入子类,在子类新增一个tuple,该tuple的第0位是父类中新增tuple的偏移量 ArrayListnewTuple = new ArrayList();

newTuple.add(String.valueOf(sourceOffset));

//然后增加实属性的值 if(classStructOfChildrenClass.attrList != null && classStructOfChildrenClass.attrList.size()>0)

{

for(int j = 0; j < classStructOfChildrenClass.attrList.size(); j++)

{

//插入实属性的默认值 newTuple.add(classStructOfChildrenClass.attrList.get(j).defaultVal);

}

}

//将newTuple插入到子类中 MEM.insert(conditionResult.className,newTuple);

//修改源类指向子类的指针 int childrenOffset = MEM.getOffset();

sourcePointer.add(String.valueOf(childrenOffset));

}

else

{

//不符合条件,不插入子类,修改源类指向子类的指针为-1 sourcePointer.add("-1");

}

}

//对源类tuple的第0位编码 String pointer = storeAPI.encode(sourcePointer);

//更新源类刚插入的tuple值 result.valueList.set(0, pointer);

storeAPI.update(result.valueList);

}

MEM.flushToDisk();

return true;

}

更新

更新同样分为源类的更新和代理类的更新。

由于代理类的虚属性是实时计算出来的,其值依赖于源类的值。因此对代理类的虚属性更新是没有意义的。对于代理类,仅考虑对其实属性的更新。遍历代理类的属性列表和和要更新的属性的属性列表,如果属性名相同,就将更新的属性的计算结果赋值给代理类的属性列表,完成更新。

对于源类而言,除了要根据规则更新属性的值,还要考虑是否更新之后,代理类中的元组是否还满足代理规则,对于不满足代理规则的,应该删去,而满足代理规则的,则应该加入。我们的做法就是将子类中的所有记录都遍历一遍,分别进行判断。

//return true Anyways static boolean Update()

{

//获取要插入的源类的类结构 ClassStruct classStructOfSourceClass = storeAPI.getClassStruct(result.className);

//start 代理类更新 (支持源类和代理类的更新) if(classStructOfSourceClass.selectClassName!=null&&!classStructOfSourceClass.selectClassName.equals("")){

//deputy class update 只考虑对代理类实属性的更新 storeAPI.initial(classStructOfSourceClass.className);

ArrayList tuple = null;

ArrayList son_title = getTitle(classStructOfSourceClass);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,classStructOfSourceClass,tuple)){

ArrayList l_tuple = compute(classStructOfSourceClass,tuple);//获取这个类的完整的结构 if(classStructOfSourceClass.attrList!=null)

for(int i=0;i

for(int j=0;j

if(result.attrNameList.get(j).attrName.equals(classStructOfSourceClass.attrList.get(i).attrName)){

tuple.set(i+1,tinyCompute(son_title,l_tuple,result.valueList.get(j)));//将计算的结果存进去 }

}

}

storeAPI.update(tuple);

//代理类更新就不在乎会不会有源类跟着变化了,tinyCompute里面有处理 }

}

return true;

}

//END 代理类更新 //要拿到该类的所有tuple,然后根据where的限制选择更新 ArrayList oldTuple = null;

storeAPI.initial(result.className);

ArrayList fa_title = getTitle(classStructOfSourceClass);

while((oldTuple = storeAPI.Next()) != null)

{

//如果当前tuple符合where的限制则更新 if(isSatisfy(result.where, classStructOfSourceClass, oldTuple))

{

if(classStructOfSourceClass.attrList != null && classStructOfSourceClass.attrList.size() > 0)

{

//遍历原属性列表和要更改的属性列表,确定要更改的属性 for(int i = 0; i < classStructOfSourceClass.attrList.size(); i++)

{

for(int j = 0; j < result.attrNameList.size(); j++)

{

//找到要更改的属性,是原属性列表的第i+1个属性值(第0位是指针位),要变成更改属性列表的第j个属性的值 if(classStructOfSourceClass.attrList.get(i).attrName.equals(result.attrNameList.get(j).attrName))

{

oldTuple.set(i+1,tinyCompute(fa_title,oldTuple,result.valueList.get(j)));

}

}

}

}

//解析指向子类的指针 ArrayList sourcePointer = storeAPI.decode(oldTuple.get(0));

//接下来判断更新之后的元组是否还满足创建代理时的要求,不符合的话,要把对应的指针记录删掉,新满足的将会增加 if(classStructOfSourceClass.children != null && classStructOfSourceClass.children.size() > 0)

{

for(int i = 0; i < sourcePointer.size(); i++)//在这里循环 {

int childrenClassOffset = Integer.parseInt(sourcePointer.get(i));

//对创建子类时的SQL语句重新解析,目的是获得Where的结构 ClassStruct classStructOfChildrenClass = MEM.getClassStruct(classStructOfSourceClass.children.get(i));

ParseResult conditionResult = SQLParser.sqlParse(classStructOfChildrenClass.condition);

if(!sourcePointer.get(i).equals("-1"))

{

//子类符合,不用变动;子类不符合,要删除 if(!isSatisfy(conditionResult.where, classStructOfSourceClass, oldTuple))

{

MEM.initial(classStructOfChildrenClass.className,

childrenClassOffset/StoreAPI.PAGESIZE,childrenClassOffset%StoreAPI.PAGESIZE);

ArrayList temp = MEM.Next();

MEM.delete();

sourcePointer.set(i,"-1");

}

}else{

//子类符合应该添加 if(isSatisfy(conditionResult.where, classStructOfSourceClass, oldTuple))

{

ArrayList l_tuple = new ArrayList<>();

l_tuple.add(Integer.toString(storeAPI.getOffset()));

for(int j=0;j

l_tuple.add(classStructOfChildrenClass.attrList.get(j).defaultVal);

}

MEM.insert(classStructOfChildrenClass.className,l_tuple);

sourcePointer.set(i,Integer.toString(MEM.getOffset()));

}

}

}

}

//更新源类的元组 oldTuple.set(0,storeAPI.encode(sourcePointer));

storeAPI.update(oldTuple);

}

}

MEM.flushToDisk();

return true;

}

查询

查询分为两种:类间查询

跨类查询

我们分别对这两种情况分别进行了处理。

类间查询

对类间的查询,我们首先需要获得要查询的属性的列表,然后遍历存储的各个记录,如果符合要求,就将其加入结果返回。

在这里需要区分一些源类和代理类的情况。对于源类而言,我们直接获取其所有记录即可。而对于代理类而言,我们需要先进行一次计算,以恢复其完整结构。因为虚属性的值是不直接存储的。

static ArrayList> select(){

ArrayList> res = new ArrayList<>();

if(!storeAPI.existClass(result.selectClassName))return res;

ClassStruct classStruct = storeAPI.getClassStruct(result.selectClassName);

ArrayList title = new ArrayList<>();

if(result.attrNameList!=null)

for(int i=0;i

if(result.attrNameList.get(i).attrRename!=null&&

!result.attrNameList.get(i).attrRename.equals(""))

title.add(result.attrNameList.get(i).attrRename);//代理类 else

title.add(result.attrNameList.get(i).attrName);

}

res.add(title);

//源类查询 if(classStruct.selectClassName==null || classStruct.selectClassName.equals("")){

storeAPI.initial(classStruct.className);

ArrayList tuple = null;

ArrayList fa_title = getTitle(classStruct);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,classStruct,tuple)){

tuple.remove(0);

ArrayList tar = new ArrayList<>();

for(int i=0;i

String tmp = tinyCompute(fa_title,tuple,result.attrNameList.get(i).attrName);

tar.add(tmp);

}

res.add(tar);

}

}

return res;

}

//代理类查询 storeAPI.initial(classStruct.className);

ArrayList tuple = null;

ArrayList son_title = getTitle(classStruct);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,classStruct,tuple)){

ArrayList l_t = compute(classStruct,tuple);

ArrayList tar = new ArrayList<>();

for(int i=0;i

String tmp = tinyCompute(son_title,l_t,result.attrNameList.get(i).attrName);

tar.add(tmp);

}

res.add(tar);

}

}

return res;

}

跨类查询

跨类查询通过起点的条件判断,来查询终点的记录。跨类查询较类间查询要复杂得多,因为我们需要通过指针去找结果,而不能直接通过计算来得到。

在我们的编译部分,我们只存储了起点和终点。我们也没有对起点和终点的类型进行区分。因此,我们首将跨类查询分为了三类:源类->代理类

代理类->源类

代理类->代理类

源类->代理类

对于源类->代理类的情况,我们首先找到查询的代理类是源类的第几个孩子,通过孩子索引到查找的内容在终点中的位置,然后将结果从磁盘中读出返回。

int childIndex = 0;

for(int i=0;i

if(start_point.children.get(i).equals(end_point.className)){

childIndex = i;//找到代理类的索引号 break;

}

}

storeAPI.initial(start_point.className);

ArrayList stuple = null;

ArrayList title = getTitle(end_point);

while((stuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,start_point,stuple)){

ArrayList pointer = storeAPI.decode(stuple.get(0));

int childOffset = Integer.parseInt(pointer.get(childIndex));

if(childOffset<0)continue;

MEM.initial(end_point.className,

childOffset/StoreAPI.PAGESIZE,childOffset%StoreAPI.PAGESIZE);

ArrayList tmp = compute(end_point,MEM.Next());

res.add(getCalc(tmp,title));

}

}

return res;

代理类->源类

从代理类向源类的跨类查询比从源类向代理类的查询简单一些,因为一个代理类只有一个源类,而一个源类可以有很多的代理类。所以判断完条件之后直接从块的开始处开始读取即可,其他操作与源类查询代理类一致。

//deputy_1 -> source storeAPI.initial(start_point.className);

ArrayList tuple = null;

ArrayList title = getTitle(end_point);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,start_point,tuple)){

int fatherOffset = Integer.parseInt(tuple.get(0));

MEM.initial(end_point.className,

fatherOffset/StoreAPI.PAGESIZE,fatherOffset%StoreAPI.PAGESIZE);

ArrayList tmp = MEM.Next();

tmp.remove(0);

res.add(getCalc(tmp,title));

}

}

return res;

代理类->代理类

从代理类到代理类的跨类查询是前两个查询的综合。我们首先根据他们之间的联系——他们共同的父节点找到他们的索引关系,然后再依次索引得到查询结果。

//deputy_1 -> deputy_2 storeAPI.initial(start_point.className);

ArrayList tuple = null;;

ClassStruct father = storeAPI.getClassStruct(start_point.selectClassName);

int index = 0;

for(int i=0;i

if(father.children.get(i).equals(end_point.className)){

index = i;

break;

}

}

ArrayList son_title = getTitle(end_point);

while((tuple=storeAPI.Next())!=null){

if(isSatisfy(result.where,start_point,tuple)){

int fatherOffset = Integer.parseInt(tuple.get(0));

MEM.initial(father.className,

fatherOffset/StoreAPI.PAGESIZE,fatherOffset%StoreAPI.PAGESIZE);

ArrayList fa_tuple = MEM.Next();

int sonOffset = Integer.parseInt(storeAPI.decode(fa_tuple.get(0)).get(index));

if(sonOffset<0)continue;

MEM.initial(end_point.className,

sonOffset/StoreAPI.PAGESIZE,sonOffset%StoreAPI.PAGESIZE);

ArrayList son_tuple = MEM.Next();

son_tuple = compute(end_point,son_tuple);

res.add(getCalc(son_tuple,son_title));

}

}

return res;

存储

我们在存储部分的实现较为复杂,并且与数据库实现关系并不是很密切。为了防止偏离主题,在这里我将介绍一种较为简单的实现,仅供参考。

存储数据结构

在存储部分,我们定义了如下的数据结构:

public class StoreAPI {

private MainActivity mainActivity; // 来自安卓的mainactivity,主要用来打开文件Input、Output流 private SharedPreferences classIDView; // 记录所有的已经存在的类 private SharedPreferences.Editor classIDEditor; // 修改已经有的类,结果作用在classIDView上 private SharedPreferences classStructView; // 记录所有的已经存在类的内容 private SharedPreferences.Editor classStructEditor; // 修改已经有的类的内容,结果作用在classStructEditor private int m_blockNum,m_blockOffset; // 我们每次初始化StoreAPI的时候,都有是通过一个类进行初始化,这里面存储的是该类当前访问到的地址偏移 private String m_className; // 记录初始化该StoreAPI的类名 private ArrayList buffer; // buffer用来存储该类相关的内容 private int mode_none_null,is_dirty; // 指向buffer,是否被写了,用于存储时的策略

//the number of tuples in a PAGE; public static final int PAGESIZE = 32; // 一页最多的tuple数量 }

硬盘存储接口

我们采用了一种比较简单的存储方式,将每个类都存储在一个文件中,通过文件指针指向具体内容。规定每页最多的tuple数量为32,设置了一个8096比特的缓冲区用于取数据,以减小磁盘I/O。

为了方便执行部分的调用,我们封装了如下的这些函数。

encode函数

执行部分的数据不能直接存到文件里面,需要进行简单的编码,即进行一些数据格式的转换,以便于文件的读取。

public String encode(ArrayList data){ // 将即将存入的内容编码,之后将其写入磁盘 if(data==null)return "";

int len = data.size();

String rs = "";

for(int i=0;i

String tmp = data.get(i);

int n = 0;

if(tmp!=null)n = tmp.length();

for(int j=0;j

if(tmp.charAt(j)==';'){

rs += "!;";

}else if(tmp.charAt(j)=='!'){

rs += "!!";

}else{

rs += tmp.charAt(j);

}

}

rs += ";";

}

return rs;

}

decode函数

decode函数是encode函数的逆过程,将encode之后的数据再解码成正常的数据形式。

public ArrayList decode(String code){ // 将从磁盘读取到的原始数据进行解析 try{

ArrayList rs = new ArrayList<>();

int n = code.length();

for(int i=0;i

String tmp = "";

for(;code.charAt(i)!=';';i++){

if(code.charAt(i)=='!'){

i++;

}

tmp += code.charAt(i);

}

rs.add(tmp);

}

return rs;

}catch (Exception e){

return new ArrayList<>();

}

}

writeString函数

writeString函数能够向指定文件存储数据,利用Java内置的数据流函数。

public boolean writeString(String fileName,String data){ // 向指定文件存储数据,一般用在encode之后 try {

FileOutputStream out = mainActivity.openFileOutput(fileName,Context.MODE_PRIVATE);

BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));

writer.write(data);

writer.flush();

writer.close();

out.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

return false;

} catch (IOException e) {

e.printStackTrace();

return false;

}

return true;

}

readString函数

readString函数能够从指定文件读取数据,按8069字节一个块来读取。

private String readString(String fileName){ // 从指定文件读取数据,一般用在decode之前 try {

FileInputStream ins = mainActivity.openFileInput(fileName);

byte[] buf = new byte[8096];

StringBuilder sb = new StringBuilder("");

int len = 0;

while((len = ins.read(buf))>0){

sb.append(new String(buf,0,len));

}

ins.close();

return sb.toString();

} catch (Exception e){

return "";

}

}

saveClassStruct函数

saveClassStruct函数将类存储到磁盘中。输入参数className,是为了将数据写到对应的文件中。

private void saveClassStruct(String className,ClassStruct classStruct){ // classStruct的元数据存储到classStruct中 Log.d("Hello", "saveClassStruct: ");

/*className--selectClassName--tupleNum--children--Attr */

int n;

ArrayList data = new ArrayList<>();

data.add(className);

if(classStruct.selectClassName==null)classStruct.selectClassName="";

data.add(classStruct.selectClassName);

data.add(Integer.toString(classStruct.tupleNum));

if(classStruct.children!=null){

n = classStruct.children.size();

}else{

n = 0;

}

data.add(Integer.toString(n));

for(int i=0;i

data.add(classStruct.children.get(i));

}

if(classStruct.attrList != null){

n = classStruct.attrList.size();

}else{

n = 0;

}

data.add(Integer.toString(n));

for(int i=0;i

data.add(classStruct.attrList.get(i).attrName);

data.add(Integer.toString(classStruct.attrList.get(i).attrType));

data.add(Integer.toString(classStruct.attrList.get(i).attrSize));

data.add(classStruct.attrList.get(i).defaultVal);

}

data.add(classStruct.condition);

n = 0;

if(classStruct.virtualAttr!=null)n=classStruct.virtualAttr.size();

data.add(Integer.toString(n));

for(int i=0;i

data.add(classStruct.virtualAttr.get(i).attrName);

data.add(classStruct.virtualAttr.get(i).attrRename);

}

String code = encode(data);

/*coding over*/

//save code classStructEditor.putString(className,code);

classStructEditor.commit();

}

getClassStruct函数

getClassStruct函数可以通过类名获取类信息,在执行部分曾多次调用。

public ClassStruct getClassStruct(String className){ // classStruct元数据的获取,来自classStruct if(existClass(className)==false)return null;

int n,index = 0;

String code = classStructView.getString(className,"");

ArrayList data = decode(code);

ClassStruct classStruct = new ClassStruct();

classStruct.className = data.get(index++);

classStruct.selectClassName = data.get(index++);

classStruct.tupleNum = Integer.parseInt(data.get(index++));

n = Integer.parseInt(data.get(index++));

if(n==0)classStruct.children=new ArrayList<>();

else{

classStruct.children = new ArrayList<>();

for(int i=0;i

classStruct.children.add(data.get(index++));

}

}

n = Integer.parseInt(data.get(index++));

classStruct.attrList=new ArrayList<>();

for(int i=0;i

Attribute attr = new Attribute(data.get(index++),

Integer.parseInt(data.get(index++)),

Integer.parseInt(data.get(index++)),

data.get(index++));

classStruct.attrList.add(attr);

}

classStruct.condition = data.get(index++);

classStruct.virtualAttr = new ArrayList<>();

n = Integer.parseInt(data.get(index++));

for(int i=0;i

classStruct.virtualAttr.add(new AttrNameTuple(data.get(index++),data.get(index++)));

}

return classStruct;

}

createClass函数

createClass函数是对save的封装,多了一步操作磁盘,建立属于该类的第一个磁盘小分块。

public boolean createClass(String className,ClassStruct classStruct){

if(regClass(className)==false)return false;

classStruct.tupleNum = 0;

classStruct.className = className;

saveClassStruct(className,classStruct);

writeString(className+"_0","");

return true;

}

dropClass函数

dropClass可以删除类,也就是修改类表,并且删除对应的文件。

public boolean dropClass(String className){ // 如果初始化storeAPI的类恰恰为要删除的类,防止以后将其同步到磁盘,先同步到磁盘再将其删除 if(className.equals(m_className) && is_dirty==1 && buffer!=null){

flushToDisk();

buffer = null;

}

if(delClass(className)==false)return false;

classStructEditor.remove(className);

classStructEditor.commit();

int blockNum = 0;

String prefix = "/data/data/"+mainActivity.getPackageName()+"/files/";

File file = new File(prefix+className+"_"+Integer.toString(blockNum++));

while(file.exists()){

file.delete();

file = new File(prefix+className+"_"+Integer.toString(blockNum++));

}

//INDEX and something else //Code Here return true;

}

initial函数

initial函数能够重新初始化storeAPI,先将原来的类buffer全部同步到磁盘,然后将新初始类的读写偏移改为0或改为指定值。

public void initial(String className){ // 重新初始化storeAPI,先将原来的类buffer全部同步到磁盘,然后将新初始类的读写偏移改为0 flushToDisk();

m_className = className;

m_blockNum = m_blockOffset = 0;

buffer = null;

mode_none_null = 1;

is_dirty = 0;

}

public void initial(String className, int _bn,int _bo){ // 指定了读写偏移 flushToDisk();

m_className = className;

m_blockNum = _bn;

m_blockOffset = _bo;

buffer = null;

mode_none_null = 1;

is_dirty = 0;

}

Next函数

Next函数能够获取下一个该class的文件块。如果正好处于块的头部或者尾部,还需要计算块的位置。

public ArrayList Next(){ // 获取下一个该class的文件块 if(buffer==null){

String filename = m_className+"_"+Integer.toString(m_blockNum);

buffer = decode(readString(filename));

is_dirty = 0;

}

if(m_blockOffset==PAGESIZE){

flushToDisk();

m_blockOffset = 0;

m_blockNum++;

buffer = decode(readString(m_className+"_"+Integer.toString(m_blockNum)));

}

int len = buffer.size();

if(m_blockOffset>=len)return null;

String tmp = buffer.get(m_blockOffset++);

if(mode_none_null>0 && tmp.equals(""))return Next();

ArrayList rs = decode(tmp);

return rs;

}

insert函数

insert函数能够插入元组,也就是更新该类的数据。直接插就好。

public void insert(String className,ArrayList tuple){ // 插入元组,也就是更新该类的数据 if(buffer!=null && className.equals(m_className)){

int len = buffer.size();

for(int i=0;i

if(i

buffer.set(i,encode(tuple));

is_dirty = 1;

m_blockOffset = i+1;

return ;

}else if(i>=len){

buffer.add(encode(tuple));

is_dirty = 1;

m_blockOffset = i+1;

return ;

}

}

}

flushToDisk();

initial(className);

setNoneNull(0);

ArrayList l_tuple = Next();

while(l_tuple != null && l_tuple.size()>0){

l_tuple = Next();

}

if(l_tuple==null) {

buffer.add(encode(tuple));

m_blockOffset++;

is_dirty = 1;

return;

}

buffer.set(m_blockOffset-1,encode(tuple));

is_dirty = 1;

return;

}

update函数

update函数能够更新元组,直接写就好了。

public void update(ArrayList tuple){ // 直接写,写之前要确定好offset位置 buffer.set(m_blockOffset-1,encode(tuple));

is_dirty = 1;

}

delete函数

delete函数能够删除元组,就直接将当前位置置为空串就好。

public void delete(){ // 直接删 buffer.set(m_blockOffset-1,"");

is_dirty = 1;

}

参考文献

[1]《一个简单的Android端对象代理数据库系统的实现 - 浮生思潮》. [在线]. 载于: http://www.liyunzhe.cn/index.php/archives/89/. [见于: 10-2月-2020].

[2]J. D. Ullman, A first course in database systems. Pearson Education India, 1997.

[3]H. Garcia-Molina, J. D. Ullman和J. Widom, Database system implementation, 卷 672. Prentice Hall Upper Saddle River, NJ:, 2000.

[4]guanhuankang, guanhuankang/TOTEM. 2019.

[5]彭智勇和彭煜玮, PostgreSQL 数据库内核分析. Ji xie gong ye chu ban she, 2012.

android实现mysql数据库存储_一个简单的Android端对象代理数据库系统的实现(二、执行+存储)...相关推荐

  1. mysql 消息队列_一个简单的 MySQL 批量事务消息队列

    基于 MySQL 的批量事务消息队列 消息队列本质上是一个存储介质,通常是链表结构,不同的进程或线程可以向消息队列中写入或读取消息.消息队列的使用场景有很多,比如异步处理任务.应用解耦.流量削锋等等. ...

  2. python 搭建登陆系统,用Python连接操作MySQL数据库,做一个简单的用户登录注册系统...

    我们可以很容易地用Python实现一个用户登录系统,相信这即使是对编程新手来说也是小菜一碟. 作为Python的小萌新,今天我想记录下来的是如何实现一个连接了MySQL数据库的用户登录注册系统,它的效 ...

  3. android原生定时任务_一个简单的Android定时任务

    Android中(Service )服务的最佳实践--后台执行的定时任务 版权声明:本文为博主原创文章,未经博主允许不得转载.https://blog.csdn.net/u010046908/arti ...

  4. php mysql主从延迟_如何解决主从数据库同步延迟问题?php连接 mysql 数据库如何添加一个公共的配置文件50...

    在上一篇文章中,小编为您详细介绍了关于<图上属标注的什么样元器件?火车购票明明显示无座为什么样乘车后却发现有很多空座>相关知识.本篇中小编将再为您讲解标题如何解决主从数据库同步延迟问题?p ...

  5. Android 连接Mysql数据库步骤(新手步骤)

    Android 连接Mysql数据库步骤 1.新建项目project 2.运行,显示helloworld 3.复制mysql-connector-java-5.1.48.jar,到libs文件夹 4. ...

  6. Android 连接 MySQL 数据库教程

    在 Android 应用程序中连接 MySQL 数据库可以帮助开发人员实现更丰富的数据管理功能.本教程将介绍如何在 Android 应用程序中使用低版本的 MySQL Connector/J 驱动程序 ...

  7. android视频用什么组件,一个简单的移动端视频组件的实现

    一个简单的移动端视频组件的实现 据说移动端需要个视频组件,然后自己尝试了一下,不知道能不能用上,有问题希望大家提出来,(>= 这里还是采用了标签video的方式来实现的视频播放. 当然video ...

  8. 一个简单的android便签app

    一个简单的android便签app 源码下载 MainActivity.java package com.zp.myfirstapp;import java.io.File; import java. ...

  9. java做一个数据库网站,用javaSwing和mysql数据库做的一个登录页面

    用javaSwing和mysql数据库做的一个登录页面 用javaSwing和mysql数据库做的一个登录页面 一:首先在eclipse中新建一个java工程 二:然后新建一个包,并且在包中新建两个类 ...

最新文章

  1. windos10下编译opencv_4.0.1+opencv-contrib_4.0.1
  2. Visual Studio LightSwitch初体验和定位看法
  3. cron每2天跑一次_直购直测,进口新极光每2年或34000公里才需要保养一次?
  4. java大数据组件Kafka
  5. 徐直军 华为没有鸿蒙,3亿只剩1亿?华为高管改口,推出鸿蒙到底有啥苦衷?
  6. shell脚本练习题
  7. xencenter如何安装Centos7虚拟机系统
  8. 手机APP渠道推广怎么玩?
  9. NLP推理与语义相似度数据集
  10. python里面while true是什么意思_Python里while True是什么意思?
  11. 数据结构学习笔记:算法复杂度的度量之“大O记号”
  12. arcgis for javascript API3.13 加载天地图卫星影像
  13. 搭建读书笔记网站基于docsfty
  14. IIS支持APK文件下载的方法
  15. 电厂数字化进阶之路(二):时间的朋友
  16. 人力资源管理中企业档案的重要性
  17. 硬币翻转问题-c++
  18. [转载]初识Windows 脚本文件(*.wsf)
  19. 2021科技公司薪酬报告
  20. 赛效:怎么在图片上打马赛克

热门文章

  1. 个人备案的网站能放企业服务器吗,个人及企业域名备案对网站有什么影响
  2. 面试题---可乐题:28人买可乐喝,3个可乐瓶盖可以换一瓶可乐,那么要买多少瓶可乐,够28人喝?假如是50人,又需要买多少瓶可乐?(需写出分析思路)
  3. 构建智慧城市管理体系,京东方智慧物联平台赋能细分场景
  4. 【VB6|第17期】16进制颜色值与RGB值互相转换(含源码)
  5. linux 快速删除大文件夹
  6. 答读者问(2):有关研究生论文及实习等问题
  7. PHPSpreadsheet学习笔记——访问单元格
  8. UVaOJ572---Oil Deposits
  9. 我是如何从屌丝程序员逆袭成为大厂总监的?
  10. 【观察】“互联网+政务服务”提速,迈入数字政府新阶段