Wed 23 Apr 2008
Aufbewahrung in Django der Boolean-Zustände mit Hilfe der Bitmasken
Posted by Alrond | Tags: Django :
Für die Aufbewahrung der Zustände der Elementen werden manchmal die Bitmasken verwendet. Zum Beispiel, als Zustände - gelesener/ungelesener Kommentar zu einem Artikel für einzelnen Benutzer. Obwohl es für diesen konkreten Fall besser wäre, die Uhrzeit/das Datum des letzten Besuchs eines Artikels aufzubewahren, hier können aber auch andere Aufgaben aufkommen, in denen man sich die binären Angaben für die Elemente merken muss, die kein Datum enthalten.
Man kann die M2M-Verbindung zwischen einem User und Objekt verwenden. Ein großer Nachteil ist aber wesentliche Ressourcen für Index und die Einträgeanzahl, wenn es viele Benutzer und Beobachtungsobjekte gibt. Hier ist es sinnvoll die Bitmasken zu verwenden, sie haben keinen solchen Nachteil.
Die Idee ist, dass es nur ein Eintrag in der Databasa für jeden Benutzer gibt, und die Nummer des Bytes in dem Eintrag und die Nummer des Bits in diesem Byte bestimmen den Zustand des bestimmten Elements. Normall hat Django kein Modell, das die blob-Elemente in den Databasen (die binären Daten) unterstützt. Deswegen habe ich das gewöhnliche Textfeld benutzt, ein Symbol dieses Feldes bestimmt den Zustand für vier Elemente. Das heißt, der Eintrag 250 Bytes groß gibt uns die Angaben über den Zustand für 1000 Elemente.
Zum Beispiel, es gibt 10.000 Anekdoten (diese Methode ist angebracht, wenn es viele Elemente gibt, das heißt, die Artikel sind eher klein:)
Möge es 100.000 Benutzer geben. Dann sieht unser Verbindungsmodell etwa so aus:
Und die Größe des readed_flags ist maximum 2500 Bytes (10.000/4).
Mit unserer Lösung braucht man alle 2500 Bytes nicht aufzubewahren, sondern nur die Bytesanzahl, die als die größte ID des gelesenen Artikles geteilt durch 4 bestimmt wird.
In dieser Geschichte haben wir 2 Funktionen: is_readed(id) и set_readed(id)
is_readed(id) gibt den Zustand (true/false) für die bestimmte ID zurück.
set_readed (id) weist dem Zustand "true" zu.
Nach set_readed() darf man nicht vergessen die Änderungen durch save() zu speichern.
Innerhalb der Funktion set_readed() habe ich kein save() durchgeführt, damit es möglich wäre, für große Artikelanzahl die Flage schnell einzuschalten und die Änderungen in die Database auf einmal zu speichern.
Hier ist der Modellcode:
Man kann die M2M-Verbindung zwischen einem User und Objekt verwenden. Ein großer Nachteil ist aber wesentliche Ressourcen für Index und die Einträgeanzahl, wenn es viele Benutzer und Beobachtungsobjekte gibt. Hier ist es sinnvoll die Bitmasken zu verwenden, sie haben keinen solchen Nachteil.
Die Idee ist, dass es nur ein Eintrag in der Databasa für jeden Benutzer gibt, und die Nummer des Bytes in dem Eintrag und die Nummer des Bits in diesem Byte bestimmen den Zustand des bestimmten Elements. Normall hat Django kein Modell, das die blob-Elemente in den Databasen (die binären Daten) unterstützt. Deswegen habe ich das gewöhnliche Textfeld benutzt, ein Symbol dieses Feldes bestimmt den Zustand für vier Elemente. Das heißt, der Eintrag 250 Bytes groß gibt uns die Angaben über den Zustand für 1000 Elemente.
Zum Beispiel, es gibt 10.000 Anekdoten (diese Methode ist angebracht, wenn es viele Elemente gibt, das heißt, die Artikel sind eher klein:)
Möge es 100.000 Benutzer geben. Dann sieht unser Verbindungsmodell etwa so aus:
class Readed(models.Model): user = models.ForeignKey(User, related_name='readed') readed_flags = models.TextField(blank=True)Die Einträgeanzahl ist gleich der Benutzeranzahl - 100.000.
Und die Größe des readed_flags ist maximum 2500 Bytes (10.000/4).
Mit unserer Lösung braucht man alle 2500 Bytes nicht aufzubewahren, sondern nur die Bytesanzahl, die als die größte ID des gelesenen Artikles geteilt durch 4 bestimmt wird.
In dieser Geschichte haben wir 2 Funktionen: is_readed(id) и set_readed(id)
is_readed(id) gibt den Zustand (true/false) für die bestimmte ID zurück.
set_readed (id) weist dem Zustand "true" zu.
Nach set_readed() darf man nicht vergessen die Änderungen durch save() zu speichern.
Innerhalb der Funktion set_readed() habe ich kein save() durchgeführt, damit es möglich wäre, für große Artikelanzahl die Flage schnell einzuschalten und die Änderungen in die Database auf einmal zu speichern.
Hier ist der Modellcode:
READED_FLAG = [1,2,4,8,0]
class Readed(models.Model):
user = models.ForeignKey(User, related_name='readed')
readed_flags = models.TextField(blank=True)
def dec2hex(self,n):
return "%X" % n
def hex2dec(self,s):
return int(s, 16)
def is_readed(self,num):
if num < 1:
raise ValueError("Must be positiv and not null")
byte_position, bit_position = divmod(num-1,4)
notpresent = byte_position + 1 - len(self.readed_flags)
if notpresent:
return False
four_status = self.hex2dec(self.readed_flags[byte_position])
return (four_status & READED_FLAG[bit_position]) != READED_FLAG[4]
def set_readed(self,num):
if num < 1:
raise ValueError("Must be positiv and not null")
byte_position, bit_position = divmod(num-1,4)
notpresent = byte_position + 1 - len(self.readed_flags)
for i in xrange(notpresent):
self.readed_flags += "0"
if notpresent:
four_status = 0
else:
four_status = self.hex2dec(self.readed_flags[byte_position])
four_status_new = self.dec2hex(four_status|READED_FLAG[bit_position])
readed_flags_new = self.readed_flags[:byte_position]+ \
four_status_new+self.readed_flags[byte_position+1:]
self.readed_flags = readed_flags_new
Die Anwendung dieser Methode ist viel breiter, als einfach nur Ersatz des M2M-Modells. Es liegt nur an ihrer Fantasie ;)
English
Deutsch

April 25th, 2008 at 1:32 p.m.
Отличное решение, взял на заметку
April 25th, 2008 at 3:56 p.m.
Небольшое язвительное замечание :)
Правилнее бы все это дело запихнуть в отделное поле (например унаследованное от django.models.fields.TextFields к примеру) и назвать BitMaskField()
April 25th, 2008 at 4:56 p.m.
Не сказал бы, "правильнее", это только другой стиль программирования, каждый выбирает решение, на свой вкус ;)
July 5th, 2008 at 7:28 p.m.
Решение интересное, только мне кажется проблемка тут зарыта. Основное, что будут читать пользователи - это как правило последние анекдоты, таким образом мы просто глупо будем дописывать к каждому пользователю 2500 нулей в начало, и с каждым новым анекдотом это число будет только расти.
Мне кажется было бы _чуть_ логичнее для такой системы хранить 2 числа типа так: "9483;200000A03C" Что значит 9483 нуля, потом 200...3С. Это и сэкономит место и скорость (БД не нужно будет передавать лишние тысячи байт на каждого пользователя. А чтобы избежать оверхэда на split(';') можно хранить это как "00009483 " и затем int(field[:8]) - количество нулей, а str(field[9:]) - переменная.
Сохранять разумеется как "%08d;%s" % (..)
Но решение, как я и сказал - интересное.