Filtrar 100 millones de objetos requiere una huella de memoria de menos de 1G

inserte la descripción de la imagen aquí

La antigua pregunta de filtrado de grandes conjuntos, que a muchas entrevistas de grandes fábricas les gusta hacer, de hecho, ruge, la implementación es muy simple, la dificultad es la optimización

Punto de optimización:

  • Cómo mejorar la eficiencia
  • Cómo reducir el uso de la memoria

En este caso, implementémoslo primero y luego optimicémoslo gradualmente.


100 millones de objetos, poner la lista directamente y luego juzgar si el objeto existe o no.

Fila

Pero poner 100 millones de objetos en una lista es un poco difícil.

¿Por qué no probamos primero con 100w?

intentar y morir

Primero preparemos algunas herramientas para generar objetos.

1. Crea un nuevo objeto, llámalo persona

public class Person {
    
    
    private Long id;
    private String name;//姓名
    private Long phone;//电话
    private BigDecimal salary;//薪水
    private String company;//公司
    private Integer ifSingle;//是否单身
    private Integer sex;//性别
    private String address;//住址
    private LocalDateTime createTime;
    private String createUser;
}

2. Para simular datos reales, necesitamos usar algunos valores de enumeración y algoritmos aleatorios

    private static Random random = new Random();
    private static String[] names = {
    
    "黄某人", "负债程序猿", "谭sir", "郭德纲", "李狗蛋", "铁蛋", "赵铁柱", "蔡徐鸡", "蔡徐母鸡"};
    private static String[] addrs = {
    
    "二仙桥", "成华大道", "春熙路", "锦里", "宽窄巷子", "双子塔", "天府大道", "软件园", "熊猫大道", "交子大道"};
    private static String[] companys = {
    
    "京东", "腾讯", "百度", "小米", "米哈游", "网易", "字节跳动", "美团", "蚂蚁", "完美世界"};
    private static LocalDateTime now = LocalDateTime.now();

	//获取指定数量person
    private static ArrayList<Person> getPersonList(int count) {
    
    
        ArrayList<Person> persons = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
    
    
            persons.add(getPerson());
        }
        return persons;
    }

    //随机生成person
    private static Person getPerson() {
    
    
        Person person = Person.builder()
                .name(names[random.nextInt(names.length)])
                .phone(18800000000L + random.nextInt(88888888))
                .salary(new BigDecimal(random.nextInt(99999)))
                .company(companys[random.nextInt(companys.length)])
                .ifSingle(random.nextInt(2))
                .sex(random.nextInt(2))
                .address("四川省成都市" + addrs[random.nextInt(addrs.length)])
                .createTime(LocalDateTime.now())
                .createUser(names[random.nextInt(names.length)]).build();
        return person;
    }

Los objetos generados por este algoritmo tienen una tasa de repetición muy baja (casi sin repeticiones)

Echemos un vistazo al efecto,
inserte la descripción de la imagen aquí
pero no todo es aleatorio, necesitamos tener un objeto definido, porque necesitamos determinar si existe un objeto más tarde, por lo que debe haber un método para generar un objeto definido.

    //获得一个确定的person,需传入一个date,什么作用这里先别管,后面一看就懂
    private static Person getFixedPerson(LocalDateTime date) {
    
    
        Person person = Person.builder()
                .name("薇娅")
                .phone(18966666666L)
                .salary(new BigDecimal(99999))
                .company("天猫")
                .ifSingle(0)
                .sex(0)
                .address("上海市徐家汇某栋写字楼")
                .createTime(date)
                .createUser("老马").build();
        return person;
    }

Empezar a probar (por fin, buena jalea)

inserte la descripción de la imagen aquí

Es un poco demasiado instalar 100w a la vez, lo instalaremos por separado e instalaremos 50w a la vez

    public static void main(String[] args) {
    
    
        ArrayList<Person> arrayList = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
    
    
            arrayList.addAll(getPersonList(500000));
        }
        //add一个确定的person
        arrayList.add(getFixedPerson(now));
        long start = System.currentTimeMillis();
        System.out.println(arrayList.contains(getFixedPerson(now)));
        System.out.println("arrayList内对象数量" + arrayList.size());
        long end = System.currentTimeMillis() - start;
        System.out.println("耗时(ms):" + end + ",消耗内存(m):" + (ObjectSizeCalculator.getObjectSize(arrayList) / (1024 * 1024)));
    }

inserte la descripción de la imagen aquí

Dañino, la eficiencia de 100w es dañina y la memoria también puede soportar

Súper doble, prueba 1000w

//其它代码跟上面一样
for (int i = 0; i < 20; i++) {
    
    
    arrayList.addAll(getPersonList(500000));
}

inserte la descripción de la imagen aquí

La eficiencia también es mala, pero la memoria, mi buen chico, usó 2.5G, que es un poco estirada, y así sucesivamente, 100 millones tiene 25G de memoria, que es un poco insoportable.
No puedo probar una lista de 100 millones . .0.0

esquema de lista terminó en fracaso

De hecho, rugido, no es imposible usar una lista. Si no puede instalar 100 millones, puede dividirlo en lotes. Jiangzi resolvió el problema de la memoria, pero la solución que consume mucho tiempo no se puede resolver. Se calcula de acuerdo con a los 1000w0.3s anteriores, y se necesitan al menos 3s para 100 millones. , todavía es un poco exagerado


¿Existe un contenedor que pueda contener 100 millones de objetos y aun así ocupar menos memoria?

Filtro Bloom: solo puede leer mi tarjeta de identificación

Sí, el tema de hoy es BloomFilter (BloomFilter)

Aquí viene el protagonista

El frente es todo para obtener BloomFilter, sabrá lo increíble que es esto si tiene una comparación

La siguiente es una versión de bajo perfil de BloomFilter, finalmente se dice el principio

public class MyBloomFilter {
    
    
    //后面hash函数会用到,用来生成不同的hash值,可以随便给,但别给奇数
    private final int[] ints = {
    
    6, 8, 16, 38, 58, 68};
    //统计当前对象数量
    private Integer currentBeanCount = 0;
    //你的布隆过滤器容量
    private int DEFAULT_SIZE = Integer.MAX_VALUE;
    //bit数组,用来存放结果
    private final BitSet bitSet = new BitSet(DEFAULT_SIZE);

    public MyBloomFilter() {
    
    
    }

    public MyBloomFilter(int size) {
    
    
        if (size > Integer.MAX_VALUE) throw new RuntimeException("size is too large");
        if (size <= (2 << 8)) throw new RuntimeException("size is too small");
        DEFAULT_SIZE = size;
    }
	
	//获取当前过滤器的对象数量
    public Integer getCurrentBeanCount() {
    
    
        return currentBeanCount;
    }

    //计算出key的hash值,并将对应下标置为1
    public void push(Object key) {
    
    
        Arrays.stream(ints).forEach(i -> bitSet.set(hash(key, i)));
        currentBeanCount++;
    }

    //判断key是否存在,true不一定说明key存在,但是false一定说明不存在
    public boolean contain(Object key) {
    
    
        boolean result = true;
        for (int i : ints) {
    
    
            result = result && bitSet.get(hash(key, i));
        }
        return result;
    }

    //hash算法,借鉴了hashmap的算法,利用i对同个key生成一组不同的hash值
    private int hash(Object key, int i) {
    
    
        int h;
        int index = key == null ? 0 : (DEFAULT_SIZE - 1 - i) & ((h = key.hashCode()) ^ (h >>> 16));
        return index > 0 ? index : -index;
    }
}

Primero mira el efecto

1000w

    public static void main(String[] args) {
    
    
        //实例化
        MyBloomFilter filter = new MyBloomFilter();
        for (int i = 0; i < 20; i++) {
    
    
            //push到BloomFilter
            getPersonList(500000).forEach(person -> filter.push(person));
        }
        //push一个确定的对象
        filter.push(getFixedPerson(now));
        //判断这个对象是否存在
        long start = System.currentTimeMillis();
        System.out.println(filter.contain(getFixedPerson(now)));
        long end = System.currentTimeMillis() - start;
        System.out.println("bloomFilter内对象数量:" + filter.getCurrentBeanCount());
        System.out.println("耗时(ms):" + end + ",消耗内存(m):" + (ObjectSizeCalculator.getObjectSize(filter) / (1024 * 1024)));
    }

inserte la descripción de la imagen aquí
Oooo rápido, no es así, y mientras la memoria solo use 256m, y rugir, esta memoria no aumentará con el aumento de objetos, no nos creas, intentemos

cien millones

//其他代码全一样
for (int i = 0; i < 200; i++) {
    
    
      //push到BloomFilter
      getPersonList(500000).forEach(person -> filter.push(person));
}

inserte la descripción de la imagen aquí
Incluso si son 100 millones, el rendimiento puede verse afectado rápidamente y la memoria sigue siendo de 256 m, lo que no es bueno.

Por supuesto, desde la antigüedad, las cosas buenas han estado en un dilema.Aunque BloomFilter tiene un rendimiento sólido, también tiene deficiencias.

Tiene una tasa de falsos positivos cuando se usa para juzgar si existe , y la tasa de falsos positivos aumenta a medida que aumenta la cantidad de objetos,
pero cuando se usa para juzgar si el objeto no existe , no hay una tasa de falsos positivos.

Esta característica hace que se use a menudo para lidiar con la penetración de caché (para determinar si la clave es válida)


Cansado de mirar, recompensa un hola seda
inserte la descripción de la imagen aquí


Explique brevemente el principio.

Es el subíndice calculado comparando el algoritmo hash, pero tenga en cuenta que es un grupo de comparaciones, no una vez, y un resultado hash corresponde a un solo subíndice

Realice múltiples operaciones hash en la misma clave, coloque el subíndice hash en la matriz, la matriz es todo 0 de forma predeterminada y el subíndice es 1 después de colocar el elemento, y la misma cantidad de hash se realiza más tarde al juzgar si hay un elemento Operación, ver si todos los subíndices correspondientes al resultado son todos 1, si todos son 1, significa que la clave puede existir, si no hay 1, significa que la clave no debe existir;

Matriz de bits predeterminada: [0, 0, 0, 0, 0, 0]
Por ejemplo, un conjunto de subíndices hash calculados por una clave es 0, 2, 5
correspondiente a la matriz de bits: [1, 0, 1, 0, 0, 1]
Al juzgar si existe una clave desconocida, suponga que el subíndice que calculamos es la
matriz de bits correspondiente de 0, 2 y 4: [ 1, 0, 1, 0, 1, 0]
En este momento, 5 en el La matriz de bits corresponde a El valor del subíndice es 0, y el 5 de la matriz de bits de clave conocida corresponde al bit de subíndice 1, lo que indica que las dos claves deben ser diferentes

Por el contrario, si el subíndice calculado por una clave es [1, 0, 1, 0, 0, 1], solo se puede decir que la clave puede existir, porque estas posiciones pueden ser calculadas por otras claves.


Ok. Ya terminé

Supongo que te gusta

Origin blog.csdn.net/qq_33709582/article/details/122046773
Recomendado
Clasificación