Exemple de description et d'implémentation de BloomFilter (JAVA)

1. Description et exemple de BloomFilter

1-1. Cas d'utilisation des prérequis

C'est un peu difficile, alors je vais vous donner un exemple. Ce n'est ni un cas d'utilisation approprié ni une réalité.

  1. Créez une application qui vous permet d'envoyer de l'argent en saisissant le numéro de compte de l'autre partie sur votre smartphone
  2. Impossible de transférer vers un numéro de compte inexistant

Dans ce cas

  1. Recherchez le numéro de compte de l'autre partie dans la base de données du serveur
  2. S'il n'existe pas, une erreur est renvoyée, et si elle existe, le dépôt y est traité.

Sera.

Ici, comme suit Si l'application a une liste indiquant si le numéro de compte existe ou non Il n'est pas nécessaire de se soucier de demander à la base de données l'existence du numéro de compte via le réseau.

numéro de compte
111-111-111
222-222-222
333-333-333
.........

** Cependant, si le numéro de compte est énorme, il est difficile pour l'application pour smartphone de conserver autant d'informations. ** ** ** Dans un tel cas, si vous utilisez BloomFilter, vous pouvez avoir des informations très compactes qui vous indiquent "presque" si votre numéro de compte existe ou non. ** **

1-2. Mécanisme

L'explication détaillée est [Wiki](https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AB%E3%83%BC%E3%83%A0%E3%83%95% E3% 82% A3% E3% 83% AB% E3% 82% BF).

  1. Préparez un enregistrement d'une certaine longueur pouvant contenir des informations de bit de 0 et 1.
  2. Appliquez une fonction de hachage au numéro de compte et convertissez le hachage résultant en une chaîne de bits de longueur d'enregistrement 01.
  3. Réglez un peu à la position appropriée sur l'enregistrement

A ce moment, il y a une pluralité de positions où 1 se trouve.

0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0

Si cela est répété pour tous les numéros de compte, le nombre de positions où 1 se trouve augmentera.

Si vous avez cet enregistrement lorsque vous entrez le numéro de compte dans l'application ci-dessus ** Exactement le même hachage ⇒ Effectuer une conversion de chaîne de 01 bits et faire correspondre la partie où 1 se trouve avec l'enregistrement ** ** Si tous les 1 sont définis, cela signifie que la probabilité que le numéro de compte existe est extrêmement élevée. ** **

--En haut: enregistrements avec des informations sur tous les numéros de compte

0 0 1 0 0 1 1 0 0 0 0 0 1 0 1 1 0
0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0

⇒ Puisque 1 est défini dans toutes les positions cibles, il est très probable que l'enregistrement existe.

** «Extrêmement cher» n'est pas absolu. ** ** Parce que la position où 1 est en conflit avec d'autres numéros de compte Il est possible que 1 soit mis à cette position par la chaîne de bits d'autres informations de numéro de compte.

0 0 1 0 0 1 1 0 0 0 0 0 1 0 1 1 0
0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0

Entrez ici le numéro de compte inexistant "999-999-999" Si le résultat de la conversion de hachage ⇒ 01 bit est le suivant, il est déterminé que les données existent par erreur.

0 0 1 0 0 1 1 0 0 0 0 0 1 0 1 1 0
0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0

S'il y a même un 0 dans la position où 1 se trouve, il peut être déterminé à 100% qu'il n'y a pas de données. Même s'il y a un 1 dans la position où tous les 1 se trouvent, il ne peut pas être confirmé à 100% qu'il y a des données. ** Nous disons qu'il n'y a pas de faux négatifs, mais il y a de faux positifs. ** **

Prendre un enregistrement suffisamment long réduira les faux positifs, Si vous prenez un enregistrement trop long, la capacité sera réduite.

** Cependant, pour 1 million de numéros de compte (n'importe quel nombre de chiffres) ** ** Les faux positifs convergent vers environ 1% s'il existe une région d'environ 1,2 Mo. ** ** ** Si une région d'environ 1,8 Mo peut être préparée, les faux positifs convergeront vers environ 0,1%. ** **

Ainsi, avec une application comme celle ci-dessus, il vous suffit de conserver quelques enregistrements L'accès au réseau et l'accès à la base de données peuvent être considérablement supprimés.

2. Exemple d'implémentation de BloomFilter

Je publierai l'implémentation en Java telle quelle.

MyBloomFilter.java



import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.BitSet;

/**
 *N'a que les fonctionnalités les plus basiques de BloomFilter
 * @author owl
 * @param <E>
 */
public class MyBasicBloomFilter<E> implements Serializable {

    private final BitSet bitFilter;                                             //Corps de filtre
    private final int bitFilterSize;                                            //Taille du filtre
    private int numberOfAddedElements;                                          //Nombre actuel d'éléments
    private final int countOfHashNumCreatePerOneElement;                        //Nombre de vrais bits par élément (hachage ⇒ Nombre de conversions numériques)

    static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");            

    /**
     *Faux niveau positif
     * High:Faux positifs élevés, mais taille de filtre compacte et bonnes performances
     *Milieu: équilibre les faux positifs, la taille du filtre et les performances
     * Low:Gardez les faux positifs aussi bas que possible même si la taille du filtre est augmentée ou si le temps de traitement est long.
     */
    public static enum FalsePositiveProbability{ High, Middle, Low }
    
    /**
     *Exécuter le résumé du message
     */
    static final MessageDigest digestFunction;
    static final String hashName = "MD5";                                       
    static {                                                                    
        MessageDigest tmp;
        try { tmp = java.security.MessageDigest.getInstance(hashName); } catch (NoSuchAlgorithmException e) { tmp = null; }
        digestFunction = tmp;
    }    

    /**
     *constructeur
     * @param c Nombre de bits utilisés par élément
     * @param n (nombre maximum attendu d'éléments)
     * @Nombre de bits vrais par élément param k (hachage ⇒ nombre de conversions de nombres)
     */
    public MyBasicBloomFilter(double c, int n, int k) {

        this.countOfHashNumCreatePerOneElement = k;
        
        //Nombre d'éléments*Déterminez la taille de bits du filtre par le nombre de bits utilisés par élément
        this.bitFilterSize = (int)Math.ceil(c * n);
        
        numberOfAddedElements = 0;
        this.bitFilter = new BitSet(bitFilterSize);
        
    }


    /**
     * 
     * @param expectedItemCount
     * @param fpp 
     */
    public MyBasicBloomFilter(int expectedItemCount, FalsePositiveProbability fpp) {
        
        switch(fpp){
            case High : {
                this.bitFilterSize = (int)Math.ceil(expectedItemCount * 4.8); 
                break;
            }
            case Low : {
                this.bitFilterSize = (int)Math.ceil(expectedItemCount * (9.6 + 4.8));
                break;
            }
            default : {
                this.bitFilterSize = (int)Math.ceil(expectedItemCount * 9.6);
            }
        }
        this.countOfHashNumCreatePerOneElement
                = (int) Math.round((this.bitFilterSize / (double)expectedItemCount) * Math.log(2.0));
        numberOfAddedElements = 0;
        this.bitFilter = new BitSet(bitFilterSize);        
        
    }
    public MyBasicBloomFilter(int expectedItemCount) {
        this(expectedItemCount, FalsePositiveProbability.Middle);
    }
    
    /**
     *Hashing
     * @données param Données d'entrée
     * @param hashCount Count
     * @return Valeur numérique obtenue par hachage (position debout du bit)
     */
    public static int[] createHashes(byte[] data, int hashCount) {
        int[] result = new int[hashCount];

        int k = 0;
        byte seed = 0;
        while (k < hashCount) {
            
            //Hashing
            byte[] digest;
            synchronized (digestFunction) {
                digestFunction.update(++seed);
                digest = digestFunction.digest(data);                
            }
        
            //Int à 4 octets délimiteur et pack dans le tableau
            for (int i = 0; i < digest.length/4 && k < hashCount; i++) {
                int h = 0;
                for (int j = (i*4); j < (i*4)+4; j++) {
                    h <<= 8;
                    h |= ((int) digest[j]) & 0xFF;
                }
                result[k] = h;
                k++;
            }
        }
        return result;
    }    
    
    /**
     *Ajouter un élément
     * @param element 
     */
    public void add(E element) {
       add(element.toString().getBytes(DEFAULT_CHARSET));
    }    
    
    /**
     *Ajouter un élément
     * @param bytes 
     */
    public void add(byte[] bytes) {
       int[] hashes = createHashes(bytes, countOfHashNumCreatePerOneElement);
       for (int hash : hashes) bitFilter.set(Math.abs(hash % bitFilterSize), true);
       numberOfAddedElements ++;
    }

    /**
     *Jugement
     * @élément élément param
     * @return 
     */
    public boolean contains(E element) {
        return contains(element.toString().getBytes(DEFAULT_CHARSET));
    }
    
    /**
     *Jugement
     * @Tableau d'octets de l'élément param bytes
     * @return 
     */
    public boolean contains(byte[] bytes) {
        int[] hashes = createHashes(bytes, countOfHashNumCreatePerOneElement);
        for (int hash : hashes) {
            if (!bitFilter.get(Math.abs(hash % bitFilterSize))) {
                return false;
            }
        }
        return true;
    }    
    
    /**
     *Obtenir le filtre
     * @filtre de retour
     */
    public BitSet getBitSet() {
        return bitFilter;
    }

    /**
     *Obtenez le nombre d'articles ajoutés
     * @nombre d'articles de retour
     */
    public int getAddItemCount(){
        return this.numberOfAddedElements;
    }
    
    public double getFalsePositiveProbability() {
        // (1 - e^(-k * n / m)) ^ k
        return 
            Math.pow((1 - Math.exp(-countOfHashNumCreatePerOneElement * (double) this.numberOfAddedElements
                / (double) bitFilterSize)), countOfHashNumCreatePerOneElement);
    }    
    
}

Échantillon d'appel


import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.RandomStringUtils;

/**
 *Essayez BloomFilter
 * @author owl
 */
public class ExecBloomFilterSample {
    
    public static void main(String argv[]){
        MyBasicBloomFilter<String> bf = new MyBasicBloomFilter<>(1000000, MyBasicBloomFilter.FalsePositiveProbability.Low);

        List<String> addList = new ArrayList<>();           //Données à ajouter à BloomFilter
        List<String> notAddList = new ArrayList<>();        //Données non ajoutées à BloomFilter
        
        //Ajout de 10000 données à BloomFilter
        //En même temps, générez 10000 données de vérification qui ne sont pas ajoutées à BloomFilter
        for(int i=0;i<2000000;i++){
            if(i % 2 == 0) {
                String s = RandomStringUtils.randomAlphabetic(24);
                bf.add(s);
                addList.add(s);
            } else {
                notAddList.add(RandomStringUtils.randomAlphabetic(24));
            }
        }

        //Sortie de l'état de BloomFilter
        for(int i=0; i < bf.getBitSet().size(); i++ ){
            System.out.print(bf.getBitSet().get(i) ? "1" : "0");
            if(i>1000) break;
        }
        
        //Taille des données de sortie BloomFilter et probabilité de faux positifs calculée
        System.out.println();
        System.out.println("Taille des données--> " + bf.getBitSet().size() + " | K-Nombre d'octets d'octets" + (double)((double)(bf.getBitSet().size())/8/1024));
        System.out.println("Probabilité faussement positive--> " + String.format("%.5f", bf.getFalsePositiveProbability()));

        //Vérifiez si toutes les données ajoutées à BloomFilter sont positives
        //(S'il n'y a pas de problème, rien ne s'affiche)
        System.out.println();
        System.out.println("■■■■■ Commencez à vérifier si toutes les données supplémentaires sont positives ■■■■■");
        addList
            .stream()
            .sequential()
            .filter(e -> !bf.contains(e))
            .forEach(e-> System.out.println("Jugement manquant:" + e));
        
        //Nombre de sorties pour lesquelles les données non ajoutées à BloomFilter étaient faussement positives
        System.out.println("■■■■■ Commencez à vérifier si des données non supplémentaires sont jugées faussement positives ■■■■■");
        System.out.println("Dans 1000000 données..." +
            notAddList
                .stream()
                .sequential()
                .filter(e -> bf.contains(e))
                .count() + "Un faux positif a été détecté.");
        
    }
    
}

Résultat d'exécution


000011100100001110011011100010101001100110111000100011001(réduction)
Taille des données--> 14400000 | K-Nombre d'octets 1757.8125
Probabilité faussement positive--> 0.00099

■■■■■ Commencez à vérifier si toutes les données supplémentaires sont positives ■■■■■
■■■■■ Commencez à vérifier si des données non supplémentaires sont jugées faussement positives ■■■■■
Dans 1000000 données...994 faux positifs ont été détectés.

Recommended Posts

Exemple de description et d'implémentation de BloomFilter (JAVA)
Nouvelles fonctionnalités de Java 9 et exemple de code
[Java] Exemple de génériques
Exemple de code Java 02
Exemple de code Java 03
Exemple d'interface graphique Java
Exemple de code Java 04
Comparaison des méthodes d'implémentation de thread en Java et de la méthode d'expression lambda
XXE et Java
Exemple de code Java 01
[ev3 × Java] Interface, implémentation et héritage (traitement événementiel)
Comparaison Java et Swift (3) Implémentation de classe / héritage de classe / conception de classe
PrintObserver "Modèle de conception d'observateur: description et exemple d'implémentation"
Implémentation de l'interpréteur par Java
Getter et Setter (Java)
Vérifier l'implémentation de Java toString ()
[Java] Thread et exécutable
Échantillon jusqu'à l'authentification OAuth 2.0 et l'acquisition de jetons d'accès (Java)
Java vrai et faux
[Java] Comparaison des chaînes de caractères et && et ||
[Java] Exemple de jugement de vacances
Implémentation Boyer-Moore en Java
Java - Sérialisation et désérialisation
Implémentation du tri de tas (en java)
[Java] Arguments et paramètres
timedatectl et Java TimeZone
[Java] Branchement et répétition
[Java] exemple de logback slf4j
[Java] Types de variables et types
java (classe et instance)
[Java] Surcharge et remplacement
Exemple de code pour analyser la date et l'heure avec SimpleDateFormat de Java
Un exemple CRUD simple utilisant Java Servlet / JSP et MySQL
Etudier Java # 2 (\ marque et opérateur)
Exemple de code de signature électronique (JAVA)
[Java] Différence entre == et égal
[Java] Zone de pile et zone statique
Collection d'exemples de code parallèle Java
[Java] Classe générique et méthode générique
Cryptage et décryptage Java PDF
Java et Iterator Part 1 External Iterator Edition
[Implémentation] Notes de classe de processus java
Instructions Java if et switch
[Java] Implémentation du réseau Faistel
Apache Hadoop et Java 9 (partie 1)
[Java] À propos de String et StringBuilder
[Java] HashCode et remplacement égal
[Java] Méthode d'implémentation du traitement du minuteur
☾ Instruction Java / Repeat et instruction de contrôle de répétition
Exemple de sortie de journal standard Java
Méthodes Java et surcharges de méthodes
java Generics T et? Différence
Avantages et inconvénients de Java
java (branchement conditionnel et répétition)
À propos du package Java et de l'importation
[Java] Téléchargez une image et convertissez-la en Base64
Implémentation Java de tri-tree
Histoire de remplacement C # et Java