繁体   English   中英

Java中最好的大集合数据结构

[英]Best big set data structure in Java

我需要找到一个大的整数集中的空白,该整数集中有一个遍历文件的读取循环,并且我想知道是否已经为此目的做了一些操作,以避免一个简单的Set对象存在堆溢出风险。

为了更好地解释我的问题,我必须告诉您我的票务Java软件是如何工作的。 每张票证都有一个全球累进号码,该号码与其他信息一起存储在每日日志文件中。 我必须编写一个检查过程来验证每日日志文件中是否存在数字间隔。

第一个想法是创建一个包含所有日志文件的读取循环,读取每一行,获取票证编号并将其存储在Integer TreeSet对象中,然后在此Set中查找空白。 问题在于票证编号可能很高,并且可能会使内存堆空间饱和,并且如果我必须切换到Long对象,我也想要一个好的解决方案。 Set解决方案浪费了很多内存,因为如果我发现前100个数字中没有空格,就没有意义将它们存储在Set中。

我该如何解决? 我可以使用一些已经为此目的完成的数据结构吗?

我假设(A)您正在寻找的差距是例外情况而不是规则,并且(B)您正在处理的日志文件主要按票证编号排序(尽管某些顺序错误的记录是可以的)。

如果是这样,那么我会考虑为此滚动自己的数据结构。 这是我的意思的简单示例(还有很多遗留给读者)。

基本上,它执行的是Set但实际上将其存储为Map ,每个条目代表该集中的一系列连续值。

重写add方法以适当地维护支持Map 例如,如果您向集合中添加5,并且已经有一个包含4的范围,那么它只是扩展了该范围,而不是添加新条目。

请注意,“基本排序”假设的原因是,对于完全未排序的数据,此方法仍会占用大量内存:后备映射在变小之前会变大(因为未排序的条目会在整个地方被添加)。因为其他条目填补了空白,因此可以合并连续的条目)。

这是代码:

package com.matt.tester;

import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;



public class SE {


    public class RangeSet<T extends Long> implements SortedSet<T> {

        private final TreeMap<T, T> backingMap = new TreeMap<T,T>();

        @Override
        public int size() {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            return false;
        }

        @Override
        public boolean contains(Object o) {
            if ( ! ( o instanceof Number ) ) {
                throw new IllegalArgumentException();
            }
            T n = (T) o;
            // Find the greatest backingSet entry less than n
            Map.Entry<T,T> floorEntry = backingMap.floorEntry(n);
            if ( floorEntry == null ) {
                return false;
            }
            final Long endOfRange = floorEntry.getValue();
            if ( endOfRange >= n) {
                return true;
            }
            return false;
        }

        @Override
        public Iterator<T> iterator() {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.  (You'd need a custom Iterator class, I think)");
        }

        @Override
        public Object[] toArray() {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public <T> T[] toArray(T[] a) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public boolean add(T e) {
            if ( (Long) e < 1L ) {
                throw new IllegalArgumentException("This example only supports counting numbers, mainly because it simplifies printGaps() later on");
            }
            if ( this.contains(e) ) {
                // Do nothing.  Already in set.
            }
            final Long previousEntryKey;
            final T eMinusOne = (T) (Long) (e-1L); 
            final T nextEntryKey = (T) (Long) (e+1L); 
            if ( this.contains(eMinusOne ) ) {
                // Find the greatest backingSet entry less than e
                Map.Entry<T,T> floorEntry = backingMap.floorEntry(e);
                final T startOfPrecedingRange;
                startOfPrecedingRange = floorEntry.getKey();
                if ( this.contains(nextEntryKey) ) {
                    // This addition will join two previously separated ranges
                    T endOfRange = backingMap.get(nextEntryKey);
                    backingMap.remove(nextEntryKey);
                    // Extend the prior entry to include the whole range
                    backingMap.put(startOfPrecedingRange, endOfRange);
                    return true;
                } else {
                    // This addition will extend the range immediately preceding
                    backingMap.put(startOfPrecedingRange,  e);
                    return true;
                }
            } else if ( this.backingMap.containsKey(nextEntryKey) ) {
                // This addition will extend the range immediately following
                T endOfRange = backingMap.get(nextEntryKey);
                backingMap.remove(nextEntryKey);
                // Extend the prior entry to include the whole range
                backingMap.put(e, endOfRange);
                return true;
            } else {
                // This addition is a new range, it doesn't touch any others
                backingMap.put(e,e);
                return true;
            }
        }

        @Override
        public boolean remove(Object o) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public boolean containsAll(Collection<?> c) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public boolean addAll(Collection<? extends T> c) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public boolean retainAll(Collection<?> c) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public boolean removeAll(Collection<?> c) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public void clear() {
            this.backingMap.clear();
        }

        @Override
        public Comparator<? super T> comparator() {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public SortedSet<T> subSet(T fromElement, T toElement) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public SortedSet<T> headSet(T toElement) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public SortedSet<T> tailSet(T fromElement) {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public T first() {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        @Override
        public T last() {
            throw new IllegalAccessError("Method not implemented.  Left for the reader.");
        }

        public void printGaps() {
            Long lastContiguousNumber = 0L;
            for ( Map.Entry<T, T> entry : backingMap.entrySet() ) {
                Long startOfNextRange = (Long) entry.getKey();
                Long endOfNextRange = (Long) entry.getValue();
                if ( startOfNextRange > lastContiguousNumber + 1 ) {
                    System.out.println( String.valueOf(lastContiguousNumber+1) + ".." + String.valueOf(startOfNextRange - 1) );
                }
                lastContiguousNumber = endOfNextRange;
            }
            System.out.println( String.valueOf(lastContiguousNumber+1) + "..infinity");
            System.out.println("Backing map size is " + this.backingMap.size());
            System.out.println(backingMap.toString());
        }




    }


    public static void main(String[] args) {

        SE se = new SE();

        RangeSet<Long> testRangeSet = se.new RangeSet<Long>();

        // Start by putting 1,000,000 entries into the map with a few, pre-determined, hardcoded gaps
        for ( long i = 1; i <= 1000000; i++ ) {
            // Our pre-defined gaps...
            if ( i == 58349 || ( i >= 87333 && i <= 87777 ) || i == 303998 ) {
                // Do not put these numbers in the set
            } else {
                testRangeSet.add(i);
            }
        }

        testRangeSet.printGaps();

    }
}

输出为:

58349..58349
87333..87777
303998..303998
1000001..infinity
Backing map size is 4
{1=58348, 58350=87332, 87778=303997, 303999=1000000}

好吧,要么将所有内容都存储在内存中,就有可能使堆溢出,或者没有将其存储在内存中,并且需要进行大量计算。

我建议介于两者之间-存储处理期间所需的最少信息。 您可以将已知的非空缺序列的端点存储在具有两个Long字段的类中。 所有这些序列数据类型都可以存储在排序列表中。 当您找到一个新号码时,请遍历列表以查看其是否与端点之一相邻。 如果是这样,请将端点更改为新的整数,并检查是否可以合并相邻的序列对象(从而删除其中一个对象)。 如果不是,请在正确排序的位置创建一个新的序列对象。

这将最终被O(n)中的内存使用情况和O(n)中的CPU使用率。 但是,使用任何存储有关所有数字的信息的数据结构,在内存使用情况中将只是n ;如果在恒定时间内未完成查找时间,则在cpu中将使用O(n*lookuptime)

我相信这是一个熟悉bloom-filter的绝佳时机。 这是一个极好的概率数据结构,可用于立即证明元素不在集合中。

它是如何工作的? 这个想法很简单,提升更加复杂,实现可以在Guava中找到。

这个主意

初始化一个过滤器,该过滤器将是一个长度为若干位的数组,该数组将允许您存储所用hash function最大值。 将元素添加到集合中时,计算其哈希值。 确定1位是什么,并确保在滤波器(阵列)中将所有位都切换为1 当您要检查元素是否在集合中时,只需计算其哈希值,然后检查哈希值中所有1 s的所有位在过滤器中是否为1 s。 如果这些位中的任何一位在过滤器中为0 ,则该元素肯定不在集合中。 如果将所有元素都设置为1 ,则该元素可能在过滤器中,因此您必须遍历所有元素。 助推器

简单的概率模型提供了以下答案:所有位均为1 s但元素不在集合中的情况下,滤波器(以及散列函数的范围)应有多大才能为false positive提供最佳机会。

实作

Guava实现为bloom-filter提供了以下构造函数: create(Funnel funnel, int expectedInsertions, double falsePositiveProbability) 你可以根据你自己的配置过滤器expectedInsertionsfalsePositiveProbability

假阳性

由于误报的可能性,有些人知道bloom-filters 布隆过滤器可以不依赖mightBeInFilter标记的方式使用。 如果可能,则应遍历所有元素,并逐个检查元素是否在集合中。

可能的用法根据您的情况,我将为集合创建过滤器,然后在添加所有票证之后,简单地循环遍历所有数字(因为无论如何您都必须循环),并检查它们filter#mightBe将集合filter#mightBe整数。 如果将falsePositiveProbability设置为3%, falsePositiveProbability实现O(n^2-0.03m*n)左右的复杂度,其中m表示间隙数。 如果我对复杂度估算有误,请纠正我。

读取尽可能多的凭单号以放入可用内存。

对它们进行排序,然后将排序后的列表写入临时文件。 根据期望的间隔数,在写入排序的数字时可以节省时间和空间,以使用游程长度编码方案。

将所有票证编号分类到临时文件中后,您可以将它们合并到一个单独的,已分类的票证流中,以查找差距。

如果这将导致太多临时文件无法同时打开,则可以将文件组合并为中间文件,依此类推,以使总数保持在可行限制以下。 但是,这种额外的复制会大大减慢该过程。

旧的磁带驱动器算法仍然很重要。

这是一个主意:如果您事先知道您的数字范围,那么

预先计算您希望在那里的所有数字的总和。 2.然后继续阅读您的数字,并生成所有阅读数字的总和以及您的数字。 3.如果您得出的总和与预先计算的总和相同,则没有差距。 4.如果总和不相同,而您的数字个数短于预期数字之一,则预先计算的总和-实际总和将为您提供丢失的数字。 5.如果您的数字个数短于一个,那么您将知道丢失了多少个数字以及它们的总和。

最好的部分是,您将不需要将数字的集合存储在内存中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM