做業要求:php
對源文件中出現的字符,單詞,函數,詞頻進行統計。而且利用性能測試工具分析找出瓶頸並改進。html
基本要求的功能:java
1·統計文件的字符數;node
2·統計文件的單詞數;編程
3·統計文件的的總行數;數組
4·統計文件中各個單詞出現的次數;函數
5·統計文件中各個詞組出現的次數;工具
6·嘗試在Linux系統下進行性能分析;性能
功能設計:測試
必要的步驟:遍歷全部文件,讀取每個字符
根據字符的定義,對每個獲取的字符進行判斷其相似(數字字母符號,間隔符),進行相應的操做。順便記錄字符數增長。
用一個char型數組存儲一次連續得到的數字字母符號。
判斷字符串的長度即前四個元素是否爲字母來判斷是否爲合法單詞。
遇到'\n'行數增長,遇到文末時看成遇到'\n'處理。
因爲數據的不斷增長,數組是很難控制其時間複雜度的,放棄這一想法;哈希表的哈希函數的選取,表長度的選取很關鍵,畢竟是一個讀取200+M的文件夾,處理的數據量可想而知,要想獲得良好的時間效應就必須造一個填充率不能過高的哈希表,這會佔用太大的空間,暫且不能放棄這一思路;鏈式結構,最簡單的是按照字典順序有序的插入單詞結構體至一條鏈,可是這樣的話,每一次插入新元素都得遍歷不少單詞才能找到其位置,時間複雜度巨高!遍歷這種事實在是感受太蠢了,沒有效率,也沒有思想的優美。
比較哈希表和鏈的優缺點,想到了相似於桶式結構,由於單詞必須前四個是字母,那麼就將全部數據分在26^4(456976)個鏈中,用它們的前四個字母決定其所在的區間,考慮在小區間裏面單詞的數量減小不少,這時能夠容忍在小區間的遍歷帶來的時間的代價。哈希表的哈希函數的選取相當重要,解決衝突也不可忽視,在尚未想到其餘方法以前就肯定了採用使用過屢次的鏈式結構。在時間有限的狀況這樣應該是個人最佳選擇。
思考細節部分的處理:
因爲數字的加入,strcmp函數很差使用,那就本身寫比較的函數了。
想找到top10的單詞的話,固然最簡單的就是最後遍歷全部的鏈,畏懼龐大數據量帶來的時間的代價,想到要找top10的單詞,那麼咱們就用10個指針時時刻刻指向咱們的top10的單詞,最後只須要直接調用其數值就能夠了。
具體實現代碼:
結構體與全局變量:
#define word_MAX 1024 int TOP_WORD = 10;//想要查找前10個單詞 int TOP_WORD_WORD = 10;//想要查找前10個詞組 //文件夾最後 struct handle_chain { int change; long handle; handle_chain* before; handle_chain* next; }; struct word { char* name; int num; word* next_in_name; word* next_in_num; word* before_in_num; }; struct word_word { char* name1; char* name2; int num; word_word* next_in_name; word_word* next_in_num; }; struct top_word_word { int num; word_word* chain; }; word index_word[26][26][26][26] = {};//26個字母,前四個必爲字母 //word_word index_word_word[26][26][26][26] = {};//26個字母,前四個必爲字母 word* wordlist[10] = {}; //top_word_word word_wordlist[TOP_WORD_WORD] = {}; char OLD[word_MAX] = {}, NEW[word_MAX] = {};//儲存以前的那個單詞 int char_num = 0, word_num = 0, line_num = 0, file_num = 0; char to_search[N] = {}; long handle; handle_chain* head = (handle_chain*)malloc(sizeof(handle_chain)), *work = head;
文件的遍歷:
其中把文件和文件夾當作一棵樹,遍歷的步驟就是深度遍歷,因爲不知道句柄可否復原因此用棧的結構來儲存句柄的值!
void next_file(long &handle, _finddata_t &fileinfo) {//直接找到下一個文件或者文件夾 if (handle == -1) return; while (1) { if (!_findnext(handle, &fileinfo)) { if (strcmp(fileinfo.name, ".") && strcmp(fileinfo.name, "..")) break; } else { handle = work->handle;//出棧 work = work->before; int length = strlen(to_search); char tem[6] = "\\*.*"; to_search[length - 5 - work->next->change] = '\0'; free(work->next); if (work&&work == head) work->next = work; strcat(to_search, tem); if (handle == -1) break; } } return; } void findfile(_finddata_t &fileinfo) {//找到下一個文件! while (handle != -1) { if (!strcmp(".", fileinfo.name) || !strcmp("..", fileinfo.name)) { next_file(handle, fileinfo); if (fileinfo.attrib & 0x00000020) break; } int a = fileinfo.attrib & 0x00000020;//文件>0,文件夾==0 if (a) { next_file(handle, fileinfo); if (fileinfo.attrib & 0x00000020) break; } else if (!a) {//能夠去掉! int length = strlen(to_search); char tem[6] = "\\*.*"; to_search[length - 3] = '\0'; strcat(to_search, fileinfo.name); strcat(to_search, tem); creatnode_handle(strlen(fileinfo.name), handle);//進棧 handle = _findfirst(to_search, &fileinfo); if (!strcmp(".", fileinfo.name) || !strcmp("..", fileinfo.name)) { next_file(handle, fileinfo); if (fileinfo.attrib & 0x00000020) break; } } } return; }
判斷文件的類型是否爲合法的類型:
從文件名的最後向前尋找'.'找到以後今後回溯,用switch加if...else語句判斷文件類型
int judge_file(char* name) {//判斷是否可讀 0爲不可讀 1爲可讀 if (!name) return 0; int length = strlen(name), now = length; while (now) {//其實也許能夠不要這個斷定條件 if (*(name + now - 1) == '.') break; now--; } if (!now) return 0; switch (*(name + now)) {//txt cs cpp js java py h php html case 't': if (now + 3 == length && *(name + now + 1) == 'x'&&*(name + now + 2) == 't') return 1; else return 0; case 'c': if (now + 3 == length && *(name + now + 1) == 'p'&&*(name + now + 2) == 'p') { return 1; } else if (now + 2 == length && *(name + now + 1) == 's') return 1; else return 0; case 'h': if (now + 4 == length && *(name + now + 1) == 't'&&*(name + now + 2) == 'm'&&*(name + now + 3) == 'l') { return 1; } else if (now + 1 == length) return 1; else return 0; case 'j': if (now + 4 == length && *(name + now + 1) == 'a'&&*(name + now + 2) == 'v'&&*(name + now + 3) == 'a') { return 1; } else if (now + 2 == length && *(name + now + 1) == 's') return 1; else return 0; case 'p': if (now + 3 == length && *(name + now + 1) == 'h'&&*(name + now + 2) == 'p') { return 1; } else if (now + 2 == length && *(name + now + 1) == 'y') return 1; else return 0; default: return 0; } return 0; }
從文件獲取字符並進入單詞或者詞組的處理:
void calculate(FILE* fp) { if (!fp) { cout << "打開文件失敗!" << endl; return; } //bool nonempty = false;//空 char c;//初始化爲'1' int NEW_work = 0; while (1) {//c屬於合法符號 c = fgetc(fp); if (c == EOF) {//文末處理!!!!!!!!!!!!!!! //line_num+=nonempty;//文件之間的行數增長 line_num++; if (judge_word(NEW)) {//有剩餘 WORD(); WORD_WORD(); } memset(OLD, 0, sizeof(char)*word_MAX); memset(NEW, 0, sizeof(char)*word_MAX); return; } //判斷是字符 //nonempty = true;//非空 if (c == '\n' || c == '\r') { line_num++;//統計行數 //nonempty = false;//下一行爲空 } if ((c > 31 && c < 127)) char_num++; if ((c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123)) { NEW[NEW_work] = c; NEW_work++; /*if (NEW_work >word_MAX ) { cout << "出現超長單詞" << endl; }*/ } else {//出現分隔符 NEW_work = 0; if (judge_word(NEW)) {//有更新 WORD(); WORD_WORD(); copy(OLD, NEW);//詞組的傳遞 } memset(NEW, 0, sizeof(char)*word_MAX); } } }
int judge_word(char* src) { if (strlen(src) < 4) return 0; if (!((src[0] > 64 && src[0] < 91) || (src[0] > 96 && src[0] < 123))) return 0; if (!((src[1] > 64 && src[1] < 91) || (src[1] > 96 && src[1] < 123))) return 0; if (!((src[2] > 64 && src[2] < 91) || (src[2] > 96 && src[2] < 123))) return 0; if (!((src[3] > 64 && src[3] < 91) || (src[3] > 96 && src[3] < 123))) return 0; word_num++; return 1; }
單詞的比較:
逐一比較兩個單詞的每一個字母,根據比較結果判斷單詞插入鏈的未知以及是否更改當前單詞的內容:
float cmp_word(char* out_chain, char* in_chain) {//A<a int length_out_chain = strlen(out_chain), length_in_chain = strlen(in_chain), i = 0; float witch = 0;//肯定保留誰 while (i < length_out_chain && i < length_in_chain) { if (out_chain[i] == in_chain[i]) { if (fabs(witch)>1 && (out_chain[i] > '9')) return 2 * witch; } else if (out_chain[i] == in_chain[i] + 'a' - 'A'&&in_chain[i] > '9') { if (!witch) witch = 1;//保留in_chain else if (fabs(witch) > 1) return 2 * witch; } else if (out_chain[i] == in_chain[i] - 'a' + 'A'&&out_chain[i] > '9') { if (!witch) witch = -1;//保留out_chain else if (fabs(witch) > 1) return 2 * witch; } else if ('0' <= out_chain[i] && out_chain[i] <= '9'&&'0' <= in_chain[i] && in_chain[i] <= '9') { if (out_chain[i] < in_chain[i] && fabs(witch) <= 1) witch = -1.5; else if (out_chain[i] > in_chain[i] && fabs(witch) <= 1) witch = 1.5; } else { if (witch) { return 2 * witch;//斷定必定不是同一個單詞 } else { if (out_chain[i] < in_chain[i]) return -2;//斷定必定不是同一個單詞 out_chain在in_chain以前 else return 2;//斷定必定不是同一個單詞 out_chain在in_chain以後 } } i++; } if (!out_chain[i] && !in_chain[i]) { return (int)witch; } if (!out_chain[i]) { while (in_chain[i]) { if (in_chain[i] > '9') return -2;//斷定必定不是同一個單詞 out_chain在in_chain以前 i++; } if (!witch) { return -1; } else return (int)witch; } else { while (out_chain[i]) { if (out_chain[i] > '9') return 2;//斷定必定不是同一個單詞 out_chain在in_chain以後 i++; } if (witch) { return 1; } else return (int)witch; } }
單詞的插入:
按照字典順序插入
void input_word(word &word_head) { if (!word_head.next_in_name) { word_head.next_in_name = creat_word(NEW); upgrade(word_head.next_in_name); return; } word* work = &word_head; int cmp; while (work->next_in_name) { cmp = cmp_word(NEW, work->next_in_name->name); cmp = -1; if (cmp >= 2) { work = work->next_in_name; } else break; } if (!work->next_in_name) work->next_in_name = creat_word(NEW); else if (cmp <= -2) { word* tem = creat_word(NEW); tem->next_in_name = work->next_in_name; work->next_in_name = tem; } else { work->next_in_name->num++; if (cmp == -1) { work->next_in_name->name=(char*)realloc(work->next_in_name->name, sizeof(char)*strlen(NEW)); copy(work->next_in_name->name, NEW); } upgrade(work->next_in_name);//work->next_in_name要進行升級 } return; }
這兒應該還有關於如何用top10的指針,可是因爲想法太複雜,狀況太對,寫出來的程序至今沒有經過,這讓我...很差意思拿出來就略過吧.
PSP表格
代碼質量與性能分析:
有四個warning,均來自於int 與 float之間的轉化,這是函數cmp_word用來區別不一樣狀況和簡化步驟中涉及多數狀況對應少數的處理,這樣爲了簡單就將一些能夠歸於一類處理的狀況的輸出參數調整至相同,因此說這裏的是利用了強制轉化的特色故意爲之.
由於單詞的查找前十未能成功實在遺憾.起初word結構體name份量是固定的長度爲100的數組,然而實際運行時堆棧的內存不夠,並且幾天以後得知單詞長度的最大值限定是1024,我就當即爲了空間捨棄了時間上的效率將其改成char*類型的份量,每次創造和更改時都從新分配空間以節省空間.這一巨大的改變以後我當即又跑了一次程序,結果出乎所料,時間上的代價其實並不大,還算能夠忍受(否則我沒有辦法嘛,不能忍仍是得忍呀!空間不夠讓我無路可走).
反思以及收穫:
首先最重要的一點是今早想到的,由於每次讀入一個單詞,其數目(num份量)就會增長,我爲了實時更新top10的指針就會調用那個沒有拿出來的函數.看了看,想了想,雖然最後找到top10會是O(n)的複雜度,可是統計出來的單詞數但是接近2000w,也就是說我這個函數被調用了2000w次!!!這是何等的浪費時間!!!不能放棄原始的騷操做,沒有勇氣作出改變讓我付出了巨大的代價.不過算是一次血的教訓,值了!仔細算算時間的消耗,其實直接遍歷還會出奇的比這個想法更節省時間。看來得從新審視這個被我鄙視過久的想法,有時候越簡單的想法也會是約聰明的想法!!
還有一個教訓就是信息的獲取能力,雖然老師說過一些承諾(好比要給咱們須要讀取的文件的類型,可是最後沒有給,我就按照博客上所給的文件類型寫了文件的合法的判斷),但就像用戶的需求會隨時改變同樣,要實時與老師交流,得到正確的信息,纔是編程的最最關鍵的元素!!