2014-01-06 24 views
0

所以最近我作为一个个人项目在Python中创建了我自己的数据库,主要是因为我讨厌与大多数数据库混杂在一起,而且我需要一些易于安装,便于携带且易于学习大型数据集的东西。如何制作缓冲作家?

我现在发现自己陷入了一个问题,一种从DB文件(这实际上只是一个文本文件)中删除一行的有效方法。我发现这样做的方式是在它之前写的所有行后的内容多数民众赞成,然后truncate文件(我采取更好的方法建议这样做)。当我需要在内容之前写下内容时,问题就到了,因为一次完成所有内容都可能会一次将数百万行载入到RAM中。代码如下:

ln = 11 # Line to be deleted 
with open("test.txt", "r+") as f: 
    readlinef = f.readline 
    for i in xrange(ln): 
     line = readlinef() 

    length, start = (len(line), f.tell()-len(line)) 
    f.seek(0, 2) 
    chunk = f.tell() - start+length 
    f.seek(start+length, 0) 

    # How to make this buffered? 
    data = f.read(chunk) 
    f.seek(start, 0) 
    f.write(data) 
    f.truncate() 

眼下这就是一次阅读完所有的数据,我怎么会作出这样的一个缓冲的方式最后的代码块的工作?每次在它之前写入新的数据块时,开始位置都会切换,我想知道做什么是最有效和最快的(执行时间明智的)方法。

在此先感谢。

编辑

我已经决定要跟随这里提交的建议,而只是出于好奇的缘故,我找到了一种方法来读取和写入的块。它遵循:

with open("test.txt", "r+") as f: 
    readlinef = f.readline 
    for i in xrange(ln): 
     line = readlinef() 

    start, length = (f.tell()-len(line), len(line)) 

    readf = f.read 
    BUFFER_SIZE = 1024 * 1024 

    x = 0 
    chunk = readf(BUFFER_SIZE) 
    while chunk: 
     f.seek(start, 0) 
     f.write(chunk) 
     start += BUFFER_SIZE 
     f.seek(start+length+(x*BUFFER_SIZE), 0) 
     chunk = readf(BUFFER_SIZE) 

    f.truncate() 
+0

如果效率你以后,你在这里使用了最糟糕的数据结构。固有地去除2000行中的2000行意味着解析文件的40%并重写60%,这肯定比任何你能做的事都慢。 – abarnert

+2

“让我自己的数据库”+“易于安装,便于携带和简单地学习大型数据集”+“从数据库文件中删除一行(实际上只是一个文本文件)”=我很感兴趣,先生。 – Hyperboreus

+0

现在认真的是,为什么不删除索引中要删除的记录,并且在数据库管理系统有时间的情况下“真空”你的页面文件? – Hyperboreus

回答

1

为此,您可以以同样的方式(有效)memmove作品:寻求来回源范围和目标范围之间:

count = (size+chunksize-1) // chunk size 
for chunk in range(count): 
    f.seek(start + chunk * chunksize + deleted_line_size, 0) 
    buf = f.read(chunksize) 
    f.seek(start + chunk * chunksize, 0) 
    f.write(buf) 

使用临时文件和shutil使它简单得多 - 而且,尽管你期望,它可能实际上会更快。 (有两倍多的写作,却少了一大堆求,且多块对齐书写。)例如:

with tempfile.TemporaryFile('w') as ftemp: 
    shutil.copyfileobj(ftemp, f) 
    ftemp.seek(0, 0) 
    f.seek(start, 0) 
    shutil.copyfileobj(f, ftemp) 
f.truncate() 

然而,如果你的文件是足够大,以适应您的虚拟内存空间(他们可能是在64位的土地,但可能无法在32位的土地),它可以更简单,只是mmap文件,并让OS/libc中把工作的护理:

m = mmap.mmap(f.fileno(), access=mmap.ACCESS_WRITE) 
m[start:end-deleted_line_size] = m[start+deleted_line_size:end] 
m.close() 
f.seek(end-deleted_line_size) 
f.truncate() 
+0

我考虑使用交换文件,但除非它在SSD上运行,否则常规硬盘在完成此操作时会非常缓慢。我会标记你的答案是正确的,但我想我会去实施Hyperboreus的想法。感谢您的帮助。 –

+0

@LuizBerti:不要假设你知道“会很慢”。最糟糕的情况是,它只有“缓存内存”解决方案的两倍 - 而且,正如我在答案中解释的那样,实际上它可能会更快。但你应该总是测试而不是猜测它是否重要。与此同时,如果性能对您来说很重要,我已经解释了为什么整个设计的性能可以达到您所能达到的水平;这里的因数是2,当你可以是常数或对数时,与O(N)相比没有什么比。 – abarnert

+0

@LuizBerti:无论如何,做一些像Hyperboreus的想法是一个好主意。但是,你真的应该阅读一篇关于数据库实现的入门书,因为有很多重要的想法,没有人可以把它放在SO的答案中。 – abarnert

2

回答你的问题“我该怎么做?”关于指数和真空。免责声明:这是一个非常简单的例子,与现有的DBMS没有任何区别,我强烈建议不要这样做。

基本思想:

对于你的数据库的每个表,保存各种文件,有的为你的对象ID(行ID,记录ID)和部分(页面文件)与实际数据。假设每条记录的长度都是可变的。

每条记录​​都有一个表格唯一的OID。这些存储在oid文件中。让我们命名表“test”和oid文件“test.oidX”。 oid文件中的每条记录都是固定长度的,每个oid文件的长度都是固定的。

现在如果“测试。OID1" 读取:

0001:0001:0001:0015 #oid:pagefile:position:length 
0002:0001:0016:0100 
0004:0002:0001:0001 

这意味着,记录1是在页面文件1中,在位置1,并且具有长度15,记录2是在页面文件1在长度为100的16位置等

现在,当你想删除一条记录,只需触摸OID文件,例如删除记录2,它并编辑成:

0001:0001:0001:0015 
0000:0001:0016:0100 #0000 indicating empty cell 
0004:0002:0001:0001 

而且甚至不打扰接触你的页面文件

这将创建。在你的洞里r页面文件。现在,您需要实现一些“维护”例程,它可以在您的页面文件中移动块等,这些块可以在用户请求时运行,也可以在您的DBMS无法执行时自动执行。根据您使用的锁定策略,您可能需要锁定相关记录或整个表格。

此外,当您插入一条新记录,并且您找到一个足够大的孔时,可以将其插入。

如果你的oid文件也应该作为一个索引(慢速插入,快速查询),你需要重建它(当然在插入时,也许在删除时)。

对oid文件的操作应该很快,因为它们是固定长度记录和固定长度记录。

这只是非常基本的想法,没有动人的主题,例如搜索树,哈希,等等,等等