OMToolkit介绍(4) :Object-Oriented Database 实现
1. 概述
OMToolkit中数据存储的实现主要位于com.omc.data中,说是Object-Oriented Database可能有点夸大了,实际上是采用文本存储Entity的方式,实现方式比较初级。
存储文件有两个,分别是data/meta和data/data。程序启动时将加载meta文件的内容。meta存储了Entity的id,Entity数据在data文件中的位置,以及Entity的类型。读取一个对象时,先从meata中读取数据所在的位置,再到data文件中获取Entity的数据(各属性的值)。
程序启动时会加载全部的meta,数据量较大时将占用较大内存;目前的一个思路是可以建立“meta的meata”,从而形成多级的索引。这将在后续版本中实现。
数据的更新和删除采用“无修改”的方式,及无论是对数据进行更新还是删除,都会在meta文件和data文件的末尾追加值,而不会修改原来的数据。这样一来,这了两个文件就会越来越大了。后续版本将会提供数据清理的工具。
另一个需要解决的问题是更新锁。即以更新为目的获取对象时,将加锁以防止其他处理过程对数据的更新。这是一个潜在的性能瓶颈。后续的版本将以一定的策略对多个更新进行合并,以避免加锁的操作。
2. DataUtil:数据操作类
DataUtil类是数据处理的主要类。负责数据的读取、更新和删除。
根据id获取数据get(...)方法的实现如下:
public static Entity get(long id, boolean forUpdate) throws Exception {
if (forUpdate) {
while (locks.contains(id)) {}
locks.add(id);
}
Entity entity = cache.get(id);
return entity == null ? loadEntity(id) : entity;
}
首先,如果是以更新为目的获取数据,那么需要检查更新锁;然后尝试从cache中获取Entity;如果cache中没有数据,则从数据文件中读取。
读取Entity数据的loadEntity(...)方法的实现如下:
private static Entity loadEntity(long id) throws Exception {
Meta meta = Meta.get(id);
Entity entity = meta.getEntity();
loadFields(entity, meta);
entity.setId(id);
cache.add(entity);
return FieldUtil.clone(entity);
}
先获取Entity的meta(这些meta以HashMap的形式加载到内存),然后创建Entity加载Entity的属性值,设置Entity的id,保存到cache中,clone一份后返回。之所以要进行clone,是为了保证不同的处理过程中对Entity的修改互不影响。
加载Entity属性的loadFields(...)方法的实现如下:
private static void loadFields(Entity entity, Meta meta) throws Exception {
long position = meta.getPosition();
int size = meta.getSize();
String content = FileUtil.read(DATA_FILE, position, size);
parseFields(entity, content);
}
获取数据所在的位置和size,从data文件中读取存储的数据的内容,然后进行解析并为Entity的属性赋值。parseFields(...)是FieldUtil中的一个方法,是通过import static方法导入的。
保存对象的实现如下:
public static synchronized void save(Entity entity) throws Exception {
File dataFile = new File(DATA_FILE);
long position = dataFile.length();
saveData(entity);
int size = (int) (dataFile.length() - position);
Meta.saveMeta(entity, position, size);
cache.add(entity);
}
这个方法是synchronized的,因此同时更新多个对象并保存时需要等待。逻辑是将Entity以一定的规则序列化后保存到data文件中,同时把起始位置和长度保存在meta中,并将entity放入cache。
删除对象的实现如下:
public static synchronized void delete(long id) throws Exception {
Meta.delete(id);
cache.delete(id);
}
该方法也是synchronized的。仅需要id作为参数,向meta中写入一个删除记录,并从cache中将该Entity移除。
3. FieldVisitor:属性访问类
FieldVistor类仅有两个名称都为eachField的方法,同时还包含了两个接口:
public static interface Getable {
public Object get(Field f) throws Exception;
}
public static interface Operator {
public void operate(Field f) throws Exception;
}
接口Getable用于封装获取Field值的方法,Operator用于封装对Field进行操作的方法。
由于第一个eachField(...)方法调用了第二个eachField(...)方法,我们就先看看的二个eachField(...)方法的实现:
public static void eachField(Class<?> clz, Class<?> upper, Operator operator)
throws Exception {
do {
for (Field f : clz.getDeclaredFields()) {
f.setAccessible(true);
operator.operate(f);
}
clz = clz.getSuperclass();
} while (!clz.equals(upper));
}
该方法将遍历指定的clz的所有属性,并递归遍历父类属性,直到指定的“上界”为止;遍历的过程中,可以对每个属性进行操作;操作的过程以接口Operator进行封装。
这是一个对指定类的所有属性进行操作的通用方法。
第一个eachField(...)的实现如下:
public static boolean eachField(final Entity entity, Class<?> upper,
final Getable getable) throws Exception {
final Wrapper<Boolean> warapper = new Wrapper<Boolean>(false);
Operator operator = new Operator() {
public void operate(Field f) throws Exception {
Object obj = getable.get(f);
if (obj == null) {
return;
}
if (obj instanceof OMField<?>) {
OMField<?> omField = (OMField<?>) obj;
omField.setEntity(entity);
warapper.set(true);
}
f.set(entity, obj);
}
};
eachField(entity.getClass(), upper, operator);
return warapper.get();
}
该方法在第二个eachField(...)方法的基础上,实现对属性的赋值,其逻辑为:先利用传入的Getable对象获取属性的值;如果值不为空,检查它是非为OMField,如果为OMField则设置其enttiy,并记录更新;然后对属性进行赋值。
之所以要记录更新,是因为一旦更新了对象,在事务提交时就可能需要将更新过的对象重新保存到数据文件中。
该方法是一个为指定类的属性进行赋值的通用方法。
那么,这些方法是如何被运用的?这就需要看看属性操作类FieldUtil的实现了。
4. FieldUtil:属性操作类
FieldUtil提供对Entity的属性进行操作的方法,包括新创建的Entity的属性的初始化、从Map中解析属性并为Entity的属性赋值、充数据文件保存的数据中解析属性值、获取排序后的属性名称、将指定Entity的属性写入指定的writer中、对Entity进行clone等。这里只举两个例子来说明FieldUtil的实现方式。
首先,是获取排序后的属性值的getSortedFields(...)方法:
final Queue<String> fields = new PriorityQueue<String>();
Operator operator = new Operator() {
public void operate(Field f) {
if (ReflectUtil.isSubclass(f.getType(), OMField.class)) {
fields.offer(f.getName());
}
}
};
eachField(clz, Entity.class, operator);
return fields;
这个方法利用了FieldVisitor的eachField(...)方法,将获取属性值并添加到返回的Queue中的操作封装在Operator中传入,从而实现了对指定类的属性的遍历。排序是通过PriorityQueue本身的特性实现的,同时也限定了被添加的属性的类型必须为OMField的子类。
然后,看看从数据文件中读取属性的parseFields(...)方法:
public static void parseFields(Entity entity, String content)
throws Exception {
Queue<String> fields = getSortedFields(entity.getClass());
for (String value : StringUtil.split(content, '\0')) {
loadField(entity, fields.poll(), value);
}
}
private static void loadField(Entity entity, String field, String value)
throws Exception {
Field f = entity.getClass().getDeclaredField(field);
f.setAccessible(true);
OMField<?> omField = (OMField<?>) parseField(f.getType(), value);
omField.setEntity(entity);
f.set(entity, omField);
}
private static Object parseField(Class<?> type, String value)
throws Exception {
if (ReflectUtil.isSubclass(type, OMField.class)) {
if (value.isEmpty()) {
return type.getConstructor().newInstance();
} else {
return type.getConstructor(String.class).newInstance(value);
}
} else {
return ReflectUtil.parseField(type, value);
}
}
首先,将数据内容按“\0”进行分隔,“\0”这个字符因其特殊性被作为各属性值的分隔符;然后,对于每个属性值,则根据值是否为空,选择调用不同的构造函数实例化(见parseField(Class<?> type, String value));最后,将实例化的Field设置到Entity中。
5. Cache:缓存
Cache的实现比较简单,它用ConcurrentHashMap<Long, Entity>保存了一个Entity的集合,包含add()、delete()等充缓存中增删Entity的方法,以及获取Enity的get(long id)方法。
此外Cache有继承了OMThread,将在程序启动的同时启动一个清理Entity的线程:
protected void doRun() throws Exception {
Thread.sleep(Cfg.cache());
while (true) {
Thread.sleep(Cfg.cache());
clear();
}
}
private void clear() {
long deadLine = System.currentTimeMillis() - Cfg.cache();
Iterator<Entity> it = map.values().iterator();
while (it.hasNext()) {
if (it.next().touched() < deadLine) {
it.remove();
}
}
}
清理的间隔时间可以在Cfg.cfg中进行配置。
6. Meta:元数据/数据索引
Meta类保存了Entity在数据文件中的位置、长度和类型名称,并封装了一些操作meta据文件的方法。
加载meta文件到内存的方法如下:
public static void readMeta() throws Exception {
long max = 1;
BufferedReader reader = FileUtil.reader(META_FILE);
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",", 2);
long id = Long.parseLong(parts[0]);
max = Math.max(max, id);
if (parts.length == 1) {
metaMap.remove(id);
} else {
metaMap.put(id, new Meta(parts[1]));
}
}
idCounter.set(max);
}
meta数据时分行存储的,每行按“,”分隔,分别为id、position、size和className,内容大致如下:
1,0,1,Database
2,1,25,User
1,26,2,Database
2,28,26,User
1,54,3,Database
3,57,128,Text
4,185,367,Text
5,552,39,Article
1,591,5,Database
7,596,397,Text
通过使用Meta,可以起到延迟加载Entity的目的,但目前的做法是程序一启动就加载所有的Meta,这可能仍然要占用较多空间,目前的思路是采用“多级Meta”或按一定策略延迟加载Meta;如何做到初始加载的数据量最小,同时又能保证读取时的效率,仍是一个难题。
7. 总结
OMToolkit中的数据存储方式还比较初级,在后续版本中还需要持续修改。
如何控制启动时加载的数据量?如何避免数据文件大小的膨胀?如何避免更新锁成为性能瓶颈?除此之外,Database不仅仅是读写数据而已,如何使得排序、筛选的功能更加高效便捷?这些都是需要考虑的。
但是,直接在数据文件中读写Entity,而不是进行SQL解析,这个思路是不会改变的。这一切,都是为了是数据操作的部分与面向对象编程更加有效地结合。
数据存储部分的讲解就到此为止了,接下来的一篇文章将对前面提到的Web Server、Web Framework、Database部分做一个总结;不会有太多的代码,而是计划以图形和原理的讲解为主。
谢谢。(附件是目前最新的 OMSimpleBlog 和 OMToolkit 源码)
分享到:
相关推荐
Java 3: Object-oriented programming Software Development 第一版(2017年,英文版)PDF格式
中文名: Beginning C# Object-Oriented Programming (第1版, 涵盖.NET 4.0 and C# 4.0) 原名: Beginning C# Object-Oriented Programming, 1st edition 作者: Dan Clark 资源格式: PDF 出版社: Apress书号: 978-...
Systems Analysis and Design: An Object-Oriented Approach with UML, 5th Edition by Dennis, Wixom, and Tegarden captures the dynamic aspects of the field by keeping students focused on doing SAD while ...
Practical Object-Oriented Design With UML - McGraw-Hil Practical Object-Oriented Design With UML - McGraw-Hil
C++Object-oriented Programming-ZhaoQingjie-10
The primary strength of Object-Oriented Design Using Java is that it has one of the best presentations of problem solving using patterns available. It has received rave reviews from instructors and ...
If you've used a more traditional object-oriented language, such as C++ or Java, JavaScript probably doesn't seem object-oriented at all. It has no concept of classes, and you don't even need to ...
JavaSE程序设计课件:L02-Object-oriented programming - 1.pdf
JavaSE程序设计课件:L02-Object-oriented programming - 3.pdf
JavaSE程序设计课件:L02-Object-oriented programming - 2.pdf
Written by a developer for developers who want to make the leap to object-oriented technologies as well as managers who simply want to understand what they are managing, The Object-Oriented Thought ...
Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process, Second Edition
Object-Oriented Analysis and Design Book
Chapter 22-23 UML and C++ : A Partical Guide To Object-Oriented Development 7zip解压,沒有密碼,都是jpg.
It is a rapidly growing and evolving programming methodology that some feel may eventually replace object-orientation as the dominant programming paradigm. The first public release of the AspectJ ...