实习生活告一段落,我正式从一名.NET程序员转入Java阵营,不得不说刚开始用Java的东西是多么的不习惯,但是经过三个月的使用与开发,我也发现了Java的优势:不在于语言,而在于开源。这意味着有更多免费可用的东西,直接复用,但是研究它的人也可以通过代码深造自己的技术水平。

  题外话说到这吧,很简单的一个问题,读取一个大型文件(可能超过内存),分析其中英文单词的词频,并输出结果。简化起见,我们假定编码不是Unicode,而是UTF-8或者ANSI,最快速度,榨干磁盘IO是关键所在。

  

  1、最开始

  一般来说,遇到这个问题,我们可能想法都是这样:

  --------------       ---------
  |   File   |  -->     | Buffer |  -->  分析  -->  结果
  --------------       ---------

  

  2、多线程

  再下来就可以开辟多个线程,分段读取文件,并以同步的方式将分析结果保存起来:

                ---------
                  | Buffer |  --> 分析
           /      ---------         \

  --------------       ---------
  |   File   |  -->     | Buffer |  -->  分析  -->  结果
  --------------       ---------        /

           \    ....     -->   ...

  这时有一个问题,如果保证分段时单词不被割断,如段落:...i love you...,如果正好选取在v上,那么love将会被拆成两个单词。对应的,我的解决办法是后搜机制:分割时,往后读char,直到遇到非字母、数字时认为分割完成。

  另外一个问题,结果的保存有两种方式,一种是同步机制,这会影响性能,但占用的内存空间小;另一种是各个线程维护一个结果集,然后在全部完成后结算,这种方式下速度更快,但会占用N倍于第一种的内存空间(N是线程数)。在内存允许的情况下,我更倾向于第二种解决方案。

  

  3、单IO线程

  目前来说,程序各个线程都会有IO操作,无疑,这在磁盘IO只有100Mb/s左右的时候,增加了线程切换、IO中断的开销,于是设计应该是统用一个大Buffer(大小取决于内存大小),然后各个线程再在Buffer的[start,end]区间分段进行分析:

                            ---------
                              | s1, e1|   -->  分析  -->  结果1
                         /    ---------                \
  --------------    IO     -------------       ---------
  |   File   |  ------> | Big Buffer |  -->     | s2, e2 |  -->  分析  -->  结果2 --> 结果
  ----------------         -------------        ---------
                        \     ....    -->   ...  -->   ...  /

  跟前面一样,[start, end]形式的分段分析也存在割断单词的情况,所以也有后搜机制来保证单词不被截断。所幸分割数取决于线程数N,而且由于单词长度有限,在内存内的后搜操作也非常迅速。总之与IO比起来,可以完全忽略了。

  4、双缓冲

  经过上面步骤的调整,IO已经完全独立出来了,但是在读取一个Buffer后,IO便会等待分析完成才会继续读入,有什么方法可以让IO线程在分析时也不停歇么?有,这便是双缓冲:
                                 ---------
                                   | s1, e1|   -->  分析  -->  结果1
                                /    ---------                \
                  ---------------           ---------
                | Big Buffer 1 | -----|| --->     | s2, e2 |  -->  分析  -->  结果2 --> 结果
  --------------     /      ---------------  切        --------- 
  |       File      |  IO     ---------------      换    \     .....     -->   ...  -->   ...  /
  --------------       \        | Big Buffer 2 | -----||
                ---------------

  这种方式的优势在于,Buffer 1在读入完成时,马上会进行分析,然后Buffer 2继续读入;当分析一个Buffer 1完成后,切换到另一个Buffer 2进行分析,然后Buffer 1继续进行读入。这就在一定程度上保证了IO的连贯性,充分利用IO资源(分析操作在内存中是相当快的)。 

  最后,我会附上我的代码,大家可以自己试着写下,其实跟算法没什么关系,主要是练习下多线程、IO方面的基础知识,蛮有意思的。另外,我这份代码需要支持C++ 11标准的编译器才能编译的哦~~

 #include <iostream>
#include <fstream>
#include <unordered_map>
#include <time.h>
#include <thread>
#include <cstring>
using namespace std; struct CharCmp
{
bool operator()(const char *str1,const char * str2) const
{
return strcmp(str1,str2) == ;
}
}; struct WordHash
{
// BKDR hash algorithm
int operator()(char * str) const
{
int seed = ; // 31 131 1313 131313 etc..
int hash = ;
while(*str)
hash = hash * seed + (*str++);
return hash & (0x7FFFFFFF);
}
}; typedef unordered_map<char*, unsigned int, WordHash, CharCmp> HashMap;
typedef unordered_map<char*, unsigned int, WordHash, CharCmp>::iterator KeySet; bool words[];
int threadCount = ;
streamsize loadsize = ; // 1024*1024*1024 1879048192 1610612736 1073741824 536870912 268435456
char* loadedFile[];
HashMap* wordMaps; // 声明
void readBlock(int,int,streamoff,streamsize);
streamsize inline getRealSize(ifstream*,streamoff,streamsize);
void inline readLoad(int,ifstream*,streamoff,streamsize);
streamsize inline getBlockSize(int,streamoff,streamsize); int main(int argc, char *argv[]){ ios::sync_with_stdio(false);
if (argc==)
{
cout<<"WordCount多线程统计词频程序\r\n 参数:\r\n Path必需,ThreadNum可选(默认为4)\r\n BufferSize可选(双缓冲,实际占用双倍,1879048192 1610612736 1073741824 536870912 268435456,默认512M)\r\n Usage: \tWordCount [Path] [ThreadNum] [BufferSize]\r\n Example:\tWordCount input.txt"<<endl;
exit();
}
if(argc>)
threadCount = atoi(argv[]);
if(argc>)
loadsize = atol(argv[]);
wordMaps = new HashMap[threadCount];
char *filename = argv[];
// 双缓冲
streamsize maxsize = loadsize+;
loadedFile[] = new char[maxsize];
loadedFile[] = new char[maxsize]; cout<<"Starting to calculate with "<< threadCount <<" threads..."<<endl;
time_t t_start,t_end;
t_start = time(NULL); // 初始化可识别字符
memset(words,false,);
for (char c=;c!=;++c)
words[c] = true;
for (char c=;c!=;++c)
words[c] = true;
for (char c=;c!=;++c)
words[c] = true; // 读取文件
ifstream file;
file.open(filename,ios::binary|ios::in);
if (!file)
{
cout<<"Error: file \""<<filename<<"\" do not exist!"<<endl; // 失败
exit();
}
else
{
// 确认文件大小
streamoff start=;
file.seekg(,ios::end);
streamoff size,len = file.tellg();
if (len>)
{
// 确认有无BOM
char bom[];
file.seekg();
file.read(bom,);
if (bom[]==-&&bom[]==-&&bom[]==-){
start = ;
size = len - ;
}else
size = len;
}else
size = len;
// 读入文件数据到缓存
thread* threads = new thread[threadCount];
streamsize realsize;
streamoff index,part;
bool step = ,needWait = false;
while (size)
{
// 缓冲
realsize = size>maxsize ? getRealSize(&file,start,loadsize) : size;
readLoad(step,&file,start,realsize);
start+=realsize;
size-=realsize;
// 等待
if(needWait)
for (int i=;i<threadCount;++i) threads[i].join();
else
needWait = true;
// 多线程计算
index=,part = realsize/threadCount;
for (int i=;i<threadCount;++i)
{
len = getBlockSize(step,index,part);
// 开算
threads[i] = thread(readBlock,step,i,index,len);
index+=len;
}
threads[] = thread(readBlock,step,,index,realsize-index);
// 转换
step = !step;
}
// 清理
for (int i=;i<threadCount;++i) threads[i].join();
delete loadedFile[];
delete loadedFile[];
file.close(); // 关闭
// 结算累加
HashMap* map = wordMaps;
for (int i=;i<threadCount;++i)
{
KeySet p=(wordMaps+i)->begin(),end=(wordMaps+i)->end();
for (; p!=end; ++p)
(*map)[p->first] += p->second;
}
// 输出
cout<<"Done.\r\n\nDifferent words: "<< map->size()<<endl;
KeySet p=map->begin(),end=map->end();
long total = ;
for (; p!=end; ++p)
total+=p->second;
cout<<"Total words:"<<total<<endl;
cout<<"\nEach words count:"<<endl;
for (KeySet i = map->begin(); i!=map->end(); ++i)
cout << i->first << "\t= " << i->second << endl;
//out.close();
}
t_end = time(NULL);
// 结束
cout<<"\r\nAll completed in "<<difftime(t_end,t_start) <<"s."<<endl;
return ;
} // 文件获取临界不截断的真正大小
streamsize inline getRealSize(ifstream* file,streamoff start,streamsize size)
{
file->seekg(start+size);
while (words[file->get()])
++size;
return size;
} // 文件读入到堆
void inline readLoad(int step,ifstream* file,streamoff start,streamsize size){
file->seekg(start);
file->read(loadedFile[step],size);
} // 分块读取
void readBlock(int step,int id,streamoff start,streamsize size){
char c = '\0';
char word[];
int i = ;
HashMap* map = wordMaps + id;
KeySet curr,end = map->end();
char* filebuffer = loadedFile[step];
streamsize bfSize = start + size;
for (streamoff index = start;index!=bfSize;++index)
{
c= filebuffer[index];
if (c> && words[c])
word[i++] = c;
else if (i>)
{
word[i++] = '\0';
// 先判断有没有
if ((curr=map->find(word)) == end)
{
char* str = new char[i];
memcpy(str,word,i);
map->insert(pair<char*, unsigned int>(str,));
}else
++(curr->second);
i = ;
}
}
if (i>)
{
word[i++] = '\0';
if ((curr = map->find(word))==end)
{
char* str = new char[i];
memcpy(str,word,i);
map->insert(pair<char*, unsigned int>(str,));
}else
++(curr->second);
}
} // 内存截断检查
streamsize inline getBlockSize(int step,streamoff start,streamsize size)
{
char* p = loadedFile[step] + start + size;
while (words[*p]){
++size;
++p;
}
return size;
}

C++源代码

  转载请注明原址:http://www.cnblogs.com/lekko/p/3370825.html

C++双缓冲多线程分析大文件词频的更多相关文章

  1. Android 开发工具类 27_多线程下载大文件

    多线程下载大文件时序图 FileDownloader.java package com.wangjialin.internet.service.downloader; import java.io.F ...

  2. c# GDI画图 双缓冲画图分析

    双缓冲绘图分析  1.Windows 绘图原理  我们在 Windows 环境下看到各种元素,如菜单.按钮.窗口.图像,从根本上说,都是“画”出来的.这时的屏幕,就相当于一块黑板,而 Windows ...

  3. Java多线程读取大文件

    前言 今天是五一假期第一天,按理应该是快乐玩耍的日子,但是作为一个北漂到京师的开发人员,实在难想出去那玩耍.好玩的地方比较远,近处又感觉没意思.于是乎,闲着写篇文章,总结下昨天写的程序吧. 昨天下午朋 ...

  4. 简单的GDI+双缓冲的分析与实现

    为什么要使用双缓冲绘制 在进行多图元绘制的时候: 因为是要一个一个画上去,所以每画一个图元,系统就要做一次图形的绘制操作,图形的重绘是很占用资源的,特别当需要重绘的图形数量很多的时候,所造成的消耗就特 ...

  5. Android多线程下载大文件解析

    1.多线程介绍 用过迅雷的同学都知道.迅雷有个功能叫做多线程.另一个叫离线下载,我们这里重点介绍一下多线程下载.多线程,顾名思义就是非常多歌线程同一时候在执行,为什么要提出多线程这个概念呢?由于有时候 ...

  6. Java缓冲流高效大文件的复制实例

    public class BufferedDemo { public static void main(String[] args) throws FileNotFoundException { // ...

  7. java多线程实现复制大文件

    有些开发的时候我们经常遇到这样一个问题,对大文件的处理.比如:日志文件.那么十几G的大文件.我们应该如何复制呢? 还有就是希望从本地和远程复制文件,文件都很大,10G级的如何办呢? 在这里我告诉你们, ...

  8. php使用file函数、fseek函数读取大文件效率分析

    php读取大文件可以使用file函数和fseek函数,但是二者之间效率可能存在差异,本文章向大家介绍php file函数与fseek函数实现大文件读取效率对比分析,需要的朋友可以参考一下. 1. 直接 ...

  9. iOS开发网络请求——大文件的多线程断点下载

    iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了.网络文件传输对移动客户端而言主要分为文件的上传和下载.作为开发者从技术角度会将文件分为小文件和大文件.小文件因为文件大 ...

随机推荐

  1. maven 中snapshot版本和release版本的区别

    maven中的仓库分为两种,snapshot快照仓库和release发布仓库.snapshot快照仓库用于保存开发过程中的不稳定版本,release正式仓库则是用来保存稳定的发行版本.定义一个组件/模 ...

  2. Android—Volley:接收服务端发送的json数据乱码问题解决

    new JsonObjectRequest中重写方法parseNetworkResponse,内容如下: /** * 重写此方法不会导致乱码 */ @Override protected Respon ...

  3. Kotlin类:功能更强、而更简洁(KAD 03)

    作者:Antonio Leiva 时间:Dec 7, 2016 原文链接:http://antonioleiva.com/classes-kotlin/ Kotlin类尽可能简单,这样用较少的代码完成 ...

  4. 电脑新建svn仓库

    步骤1:安转svg: 注意事项: 安装的时候选择:Modify 安装到以下图片的步骤时: 黄色区域选择: 步骤2:新建svn仓库文件夹(本教程例子:D:\svn-5gpos),选择文件夹右键,点击下图 ...

  5. 用javascript写星际飞机大战游戏

    在github里看到了个不错的脚本游戏,决定亲自动手来写,效果如下 下面是代码的思路分享 把整个代码理解消化确实不容易,但是如果你坚持看完相信你一定会有收获 如果没兴趣可以直接点击下面的链接 复制代码 ...

  6. Linux上课笔记--随手记Linux命令

    初次接触Linux就是感觉这系统不够友好不够人性化,因为首先接触电脑就是win,图形化界面什么操作都可以清晰看到.随着更多的接触越来越发现Linux的强大,虽然我只是一个小白,可我就是爱上他了.现在就 ...

  7. Openfire阶段实践总结

    从3月开始研究Openfire,其实就是要做一套IM系统,也正是这个原因才了解到Openfire.之前还真没想过有这么多的开源产品可以做IM,而且也没想到XMPP这个协议竟然如何强大.看来还是标准为先 ...

  8. TODO:Github的使用技巧之同步代码

    TODO:Github的使用技巧之同步代码 GitHub 是一个面向开源及私有软件项目的托管平台,因为只支持 Git 作为唯一的版本库格式进行托管,故名 GitHub. GitHub 于 2008 年 ...

  9. iOScollectionView广告无限滚动(Swift实现)

    今天公司里的实习生跑过来问我一般App上广告的无限滚动是怎么实现的,刚好很久没写博客了,就决定写下了,尽量帮助那些处于刚学iOS的程序猿. 做一个小demo,大概实现效果如下图所示: 基本实现思路: ...

  10. 8.GitHub实战系列~8.使用GitHub建立自己的免费博客

    github实战汇总:http://www.cnblogs.com/dunitian/p/5038719.html 创建一个用户名.github.io的仓库 选择设置页面风格 页面基本信息 选择一个你 ...