我的服务器上的RAM数量有限,但是需要在控制台程序的内存中处理大量数据。是否有任何技巧可以使我仍然获得相同的最终结果,而又不需要那么多的RAM
在此示例中,我在字符串列表中有1亿个电子邮件地址。我需要找出与之比较的任何新电子邮件是否已经存在。如果是这样,请添加它们。如果没有,请不要添加它们。因此,我们总是有一个唯一的电子邮件列表,没有重复。
此示例中的1亿封电子邮件需要大约17GB的RAM。
您是否知道有任何技巧或技巧可以减少需要多少RAM才能至少能够执行“列表集中是否存在?”比较? -想到的示例类型:例如不同类型的集合,或自定义的第三方参考的压缩内存中数据的软件工具,但您仍可以对该数据进行排序或比较,或者使用基于文件的数据库系统在相同数据量上的内存要少得多。
我已经编写了代码来演示如何以普通方式执行此操作,从而导致消耗17GB的RAM。
using System;
using System.Collections.Generic;
using System.Linq;
namespace NewProgram
{
class Program
{
public static List<string> emails = new List<string>();
public static void Main(string[] args)
{
LoadAllEmails();
Console.WriteLine(emails.Count() + " total emails"); //100000000 total emails
AddEmailsThatDontExistInMasterList(
new List<string>()
{
"something@test.com", //does not already exist, so it will be added to list
"testingfirst.testinglast"+ (1234567).ToString() + "@testingdomain.com", //should already exist, won't be added
"testingfirst.testinglast"+ (3333335).ToString() + "@testingdomain.com", //should already exist, won't be added
"something2@test.com", //does not already exist, so it will be added to list
"testingfirst.testinglast"+ (8765432).ToString() + "@testingdomain.com", //should already exist, won't be added
});
Console.WriteLine(emails.Count() + " total emails after"); //100000002 total emails
Console.ReadLine();
}
public static void LoadAllEmails()
{
for (int i = 0; i < 100000000; i++) //100,000,000 emails = approximately 17GB of memory
{
emails.Add("testingfirst.testinglast" + i.ToString() + "@testingdomain.com");
}
}
public static void AddEmailsThatDontExistInMasterList(List<string> newEmails)
{
foreach (string email in newEmails)
{
if (emails.Contains(email) == false)
{
emails.Add(email);
}
}
}
}
}
将100,000,000封电子邮件添加到“电子邮件”集合后,它将在添加到其中的新列表中查看另外5封电子邮件。将添加2个,不添加3个,因为它们是列表中已经存在的重复项。完成后的总数为100,000,002封电子邮件。这仅是为了表明我的最终目标是能够与现有集合进行比较,以查看值是该集合中的重复项还是已存在的非常大的数据集合。另一个目标是使总消耗的RAM从17 GB减小到更小。
答案 0 :(得分:3)
选项1使用三叉树
此数据结构是一种将字存储在内存中的有效方法。高度压缩且搜索迅速。
选项2使用内存散列和磁盘文件
仅保留内存中每封电子邮件的哈希值。如果您在哈希表中遇到问题,请在磁盘上查看。
选项3使用布隆过滤器和磁盘文件
答案 1 :(得分:1)
您似乎在暗示您有1亿个电子邮件地址,例如文本文件。我认为不需要将整个文件加载到内存中并循环遍历;使用流读取器并逐行读取它。对于每一行,请检查刚读取的电子邮件地址是否在您要导入的10个列表中,如果是,则将其从导入列表中删除
在此过程结束时,您将已将导入列表缩减为不包含在大文件中的那些地址,并且您一次不会读取多行内容(阅读器将缓存少量的行)。千字节)
改编自Microsoft的示例集合:
string line;
string[] emailsToImport = "a@b.com c@d.com".Split();
// Read the file and process it line by line.
System.IO.StreamReader file =
new System.IO.StreamReader(@"c:\100million.txt");
while((line = file.ReadLine()) != null)
{
for(int i = 0; i < emailsToImport.Length; i++){
if(emailsToImport[i] == line)
emailsToImport[i] = null;
}
}
file.Close();
System.Console.WriteLine("new emails remaining to import: {0} ", string.Join(",", emailsToImport));
这是一个快速且非常肮脏的示例,不区分大小写;它仅是对概念的简单说明,而不是生产代码
我按照以下假设进行工作:
您的服务器内存量很低(例如4gb),并且不常需要(例如每5分钟一次)将少量电子邮件地址(例如10个)添加到大量的100M地址列表中,确保不会重复新地址
逐行读取文件,将每一行与所有10个新地址进行比较,删除任何已知地址。在读取文件结束时,一旦您拥有最多N个开始的地址,就知道主列表中不存在该地址。
我断言,在这种情况下,您原来的声明“我需要在内存中处理大量数据”可以在磁盘上使用
答案 2 :(得分:0)
要检查某项是否不在非常大的列表中,可以使用Bloom Filter。它是哈希表的一种概括,它为每个输入字符串生成一个哈希表列表,这些哈希表存储的哈希表与在多个存储桶中的哈希表不同。因此,如果您有一个哈希值冲突,则仍然可以通过检查其他哈希(如果曾经添加过该确切的字符串)来解决。
您仍然可以有误报(filter.Contains(“ xxxx”)虽然从未添加到列表中,但返回true),但也不会有误报。
通过调整容量,您可以配置错误率。如果您的过滤器可以以很小的误报率生存,那么这个应该是不错的选择。
有关示例,请查看this one。
我尝试了几次。大多数实现都在其节点类中使用引用,这在内存方面效率极低。 SO上的一个似乎很不错:How to create a trie in c#。这个节省约。与普通清单相比,降低了30%。
除数据结构外,我认为您还需要查看总体目标。我猜您的实际数据不是电子邮件地址,因为垃圾邮件过滤器是一个长期解决的问题。但是,以您的示例作为参考,您可以利用数据中的结构。您有一个由名称和域组成的大型列表。要做的第一件事是将数据拆分为较小的文件,这些文件仅包含一个域的邮件地址。然后,按名称排序并为每个域创建一个索引,其中每个字母的文件起始位置存储在其标题中。
当收到新邮件时,您可以先使用Bloom Filter进行快速的预检查。如果它说的很干净,您已经完成所有病例的99.5%。为什么是99.5%?您可以通过计算要花费多少内存来获得此精度来将Bloom Filter配置为具有此属性。
从系统角度来看,这非常棒,因为您现在可以有意识地决定要为此任务花费多少内存/ CPU /磁盘资源。
点击后,您可以打开该域的索引文件,直接跳至排序后的邮件地址,然后开始阅读地址,直到遇到坏人或超过字母排序顺序后即可停止阅读。
如果您拥有更多领域,请知道如何变得更聪明。因为您知道公司的有效发件人数量受到很大限制,所以您可以创建一个较小的有效发件人检查白名单。如果发件人在白名单上,您可以跳过其他检查。