2014-01-06 3 views
0

Так недавно я взял на себя личный проект, чтобы сделать свою собственную БД на Python, главным образом потому, что я ненавижу возиться с большинством БД, и мне нужно было что-то легкое в настройке, переносном и простом изучать большие наборы данных.Как создать буферизованный писатель?

Теперь я обнаружил, что застрял в проблеме, эффективный способ удалить строку из файла DB (это действительно просто текстовый файл). То, как я нашел это, - это написать весь контент, который после строки перед ним, а затем truncate файл (я предлагаю варианты по его лучшим способам). Проблема возникает, когда мне нужно написать контент после строки перед ним, потому что все это сразу может сразу загрузить миллионы строк в ОЗУ. Код следующим образом:

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 из 5000 по своей сути означает разбор 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, обычный жесткий диск будет очень медленным при выполнении этого. Я буду отмечать ваш ответ как правильный, но я думаю, что пойду о реализации идеи Гипербореуса. Спасибо вам за помощь. –

+0

@LuizBerti: Не просто предположите, что знаете, что «будет очень медленным». В худшем случае это всего в 2 раза медленнее, чем «буферизованное с памятью» решение, и, как я объяснил в ответе, скорее всего, будет быстрее. Но вы должны всегда _test_, а не гадать, если это важно. А между тем, если производительность важна для вас, я уже объяснил, почему весь дизайн имеет такую ​​же низкую производительность, как вы можете получить; коэффициент 2 здесь и не имеет ничего общего с O (N), когда вы можете быть постоянным или логарифмическим. – abarnert

+0

@LuizBerti: Во всяком случае, что-то вроде идеи Hyperboreus - хорошая идея.Но опять же, вы действительно должны прочитать праймер по реализации базы данных, потому что есть много важных идей, которые никто не мог вписать в ответ на SO. – abarnert

2

Отвечая на ваш вопрос «Как мне это сделать?» по индексам и вакууму.

Отказ от ответственности: Это очень простой пример и никоим образом не сравнится с существующей СУБД, и я категорически против этого.

Основная идея:

Для каждой таблицы в БДЕ, хранить различные файлы, некоторые для объектов идентификаторов (строка идентификаторов, запись IDS) и некоторых (файлов страниц) с фактическими данными. Предположим, что каждая запись имеет переменную длину.

Каждая запись имеет уникальный идентификатор таблицы. Они хранятся в файлах 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 в положении 16 длиной 100 и т.д.

Теперь, когда вы хотите удалить запись, просто коснуться OID файл, например, для удаления записи 2, редактировать его:.

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

И даже не потрудились коснувшись вашей страницы файлы

Это создаст. дыры в вас r файлов. Теперь вам нужно реализовать некоторую «техническую» процедуру, которая перемещает блоки в ваших файлах страниц и т. Д., Которые могут выполняться при запросе пользователя или автоматически, когда вашей СУБД больше нечего делать. В зависимости от используемой стратегии блокировки вам может потребоваться заблокировать соответствующие записи или всю таблицу.

Также, когда вы вставляете новую запись, и вы найдете отверстие достаточно большое, вы можете вставить его там.

Если ваши файлы oid также должны функционировать как индекс (медленные вставки, быстрые запросы), вам нужно будет его перестроить (конечно, при вставке, возможно, при удалении).

Операции над oid-файлами должны быть быстрыми, так как они фиксированные и фиксированные длины.

Это только очень основная идея, не касаясь темы, как деревья поиска, хеширования, и т.д., и т.д.

Смежные вопросы