1. 程式人生 > >c++ fstream + string 處理大資料(與c 的fread)

c++ fstream + string 處理大資料(與c 的fread)

一:起因

(1)之前處理文字資料時,各種清洗資料用的都是java的File,FileReader/FileWriter,BufferedReader/BufferedWriter等類,

(2)應用java的原因是java裡面的map非常靈活,eclipse編譯器更是給力,而且ctrl 可以追蹤函式 等,

(3)應用java的另一個原因是java裡面的string類的字串處理非常靈活,各種函式是應用盡有。

(4)上面兩點算是自己的誤解吧,因為c++裡面也有也有與之對應的 fstream類,  c++map容器類,

(5)c++裡面也有相對比較成熟的string類,裡面的函式也大部分很靈活,沒有的也可以很容易的實現split,strim等,

詳見c++string 實現

(6)最近從網上,看到了一句很經典的話,c++的風fstream類 + string類 也可以非常好的處理文字檔案,讓我們一起來見證

二:fstream的前世今生

(1)簡介

   包含的標頭檔案    #include <fstream>                using namespace std;                                                       
   C++中的三個檔案流                                                          
   a ---- ofstream ofs("檔名", 開啟方式);                b ---- ifstream ifs("檔名", 開啟方式);     c ---- fstream fs("檔名",輸入開啟方式 | 輸出開啟方式);   三種檔案      流分別用於寫檔案、讀檔案、讀寫檔案 ,一般用a b兩種方式進行,因為一個檔案同時進行讀寫的情況採用c 方式。
   三種檔案流都可先定義,再開啟檔案,以fstream為例                            
   fstream fs;         fs.open("檔名", 輸入開啟方式 | 輸出開啟方式);                            
   其中“開啟方式”可以不給出。     若不給出,對於oftream預設為ios::out,iftream預設為ios::in 

(2)檔案開啟函式

在C++中,對檔案的操作是通過stream的子類fstream(file stream)來實現的,所以,要用這種方式操作檔案,就必須加入標頭檔案fstream.h。下面就把此類的檔案操作過程一一道來。
開啟檔案在fstream類中,有一個成員函式open(),就是用來開啟檔案的,其原型是:
  void open(const char* filename,int mode,int access);

  引數:
  filename:     要開啟的檔名
  mode:         要開啟檔案的方式
  access:       開啟檔案的屬性

(3)開啟方式                                                                   

   ios::out 輸出資料覆蓋現有檔案 (預設的寫代開方式,檔案不存在,建立之;若存在,則覆蓋原來的內容)
   ios::app 輸出資料填加之現有檔案末尾  (追加末尾寫代開方式,不覆蓋原內容)
 
   ios::ate 開啟檔案並移動檔案指標至末尾                                     
   ios::in     開啟檔案以輸入 (預設讀的開啟方式) 
   ios::trunc   輸出檔案中現有內容(ios::out的預設操作)                         
   ios::binary 二進位制開啟供讀寫

(4)檔案指標定位

和C的檔案操作方式不同的是,C++ I/O系統管理兩個與一個檔案相聯絡的指標。一個是讀指標,它說明輸入操作在檔案中的位置;另一個是寫指標,它下次寫操作的位置。每次執行輸入或輸出時,相應的指標自動變化。所以,C++的檔案定位分為讀位置和寫位置的定位,
對應的成員函式是 seekg()和seekp(),seekg()是設定讀位置,seekp是設定寫位置。它們最通用的形式如下: 

istream &seekg(streamoff offset,seek_dir origin); 
ostream &seekp(streamoff offset,seek_dir origin); 

streamoff定義於 iostream.h 中,定義有偏移量 offset 所能取得的最大值,seek_dir 表示移動的基準位置,是一個有以下值的列舉:
ios::beg:  檔案開頭 
ios::cur:  檔案當前位置 
ios::end:  檔案結尾 

這兩個函式一般用於二進位制檔案,因為文字檔案會因為系統對字元的解釋而可能與預想的值不同。 
例: 
file1.seekg(1234,ios::cur);//把檔案的讀指標從當前位置向後移1234個位元組
file2.seekp(1234,ios::beg);//把檔案的寫指標從檔案開頭向後移1234個位元組
file1.seekg(-128,ios::end); //把檔案的讀指標從檔案末尾向前移128個位元組

注意:一個漢字是佔用兩個位元組的,一個字母佔用一個位元組。

(5)fstream , stream ;ifstream,istream;ofstream,ostream等的關係


三:實戰篇

(1)read word by word ;no write

 //讀取方式: 逐詞讀取, 詞之間用空格區分(遇到空格認為本次讀取結束),輸出之後進行下一次讀取
 //read data from the file, Word By Word
 //when used in this manner, we'll get space-delimited bits of text from the file
 //but all of the whitespace that separated words (including newlines) was lost.
void ReadDataFromFileWBW()
{
    ifstream fin("data.txt");
    string s;
    cout << "*****start*******" << endl;
    while( fin >> s )
    {
          cout << "Read from file: " << s << endl;
    }
    cout << "*****over*******" << endl;
}
(2)read by line  fin.getline(char*,n)
 //讀取方式: 逐行讀取, 將行讀入字元陣列, 行之間用回車換行區分
 //If we were interested in preserving whitespace,
 //we could read the file in Line-By-Line using the I/O getline() function.
void ReadDataFromFileLBLIntoCharArray()
{
    ifstream fin("data.txt",ios::in);// 預設的開啟模式就是ios::in
    ofstream fout("out.txt",ios::out);// 預設代開模式就是ios::out
    const int LINE_LENGTH = 100;
    char str[LINE_LENGTH];
    fout << "****CharArray start******" << endl;
    cout << "****CharArray start******" << endl;
    fin.seekg(-20,ios::end);// -20表示從end向前移動20位元組,漢字佔兩位元組;20表示向後移動指標
    while( fin.getline(str,LINE_LENGTH) )
    {
        fout << str << endl;
        cout << "Read from file: " << str << "..." << endl;// ****str裡面本身包含著換行的,原來是什麼樣子,現在儲存的就是什麼樣子
    }
    fout << "*****over*******" << endl;
    cout << "*****over*******" << endl;
}
(3) read by line fin.getline(fin,string)
 //讀取方式: 逐行讀取, 將行讀入字串, 行之間用回車換行區分
 //If you want to avoid reading into character arrays,
 //you can use the C++ string getline() function to read lines into strings
void ReadDataFromFileLBLIntoString()
{
    ifstream fin("data.txt",ios::in);// 預設的開啟模式就是ios::in
    ofstream fout("out.txt",ios::app);// 追加到檔案尾的方式開啟
    string s;
    cout << "****start******" << endl;
    while( getline(fin,s) )
    {
        fout << s << endl;// ofstream是預設,若檔案不存在,則先建立此檔案,並且再向檔案寫的過程中換行已經不存在了,這與cout控制檯輸出一樣哦。。。
        cout << "Read from file: " << s << endl;//****s同str裡面本身已經沒有了換行的,這和原來的getline()函式是一樣的;資料原來是什麼樣子,現在儲存的就是什麼樣子
    }
    fout << "*****over*******" << endl;
    cout << "*****over*******" << endl;
    fout.close();
}

(4) main函式

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>// exit()函式

using namespace std;

 //輸出空行
void OutPutAnEmptyLine()
{
      cout<<"\n";
}

 //帶錯誤檢測的讀取方式
 //Simply evaluating an I/O object in a boolean context will return false
 //if any errors have occurred
void ReadDataWithErrChecking()
{
    string filename = "dataFUNNY.txt";
    ifstream fin( filename.c_str());
    if( !fin )
    {
          cout << "Error opening " << filename << " for input" << endl;
          exit(-1);
    }
}

int main()
{
      ReadDataFromFileWBW(); //逐詞讀入字串
      OutPutAnEmptyLine(); //輸出空行

      ReadDataFromFileLBLIntoCharArray(); //逐詞讀入字元陣列
      OutPutAnEmptyLine(); //輸出空行

      ReadDataFromFileLBLIntoString(); //逐詞讀入字串
      OutPutAnEmptyLine(); //輸出空行

      ReadDataWithErrChecking(); //帶檢測的讀取
     return 0;
}

data文字檔案的資料格式


(插入c 與 c++檔案處理對比)

<p>#include <iostream>
#include <fstream>
#include <cassert>
#include <ctime>
#include <cstdio>
using namespace std;</p><p>void test_read()
{
  const char* read_file = "D:\\zyp\\大資料實驗<a target=_blank href="file://\\TOKEN_ENEX_201404_20W_yuansi.csv">\\TOKEN_ENEX_201404_20W_yuansi.csv</a>";
  const int BUF_SIZE = 1024 ;
  char buf[BUF_SIZE];
  //c++ style writing file
  ifstream ifs(read_file,ios::binary);
  assert(ifs);
  time_t start, end;
  start =  clock();
  while(!ifs.eof())
  {
    ifs.read(buf,BUF_SIZE);
  }
  end = clock();
  ifs.close();
  cout<<"C++ style: "<<end - start <<" ms"<<endl;
  //c style writing file
  FILE* fp = fopen(read_file, "rb");
  start =  clock();
  int len = 0;
  char *pline = NULL;
  do
  {
      //len = fread(buf,1,BUF_SIZE,fp);
      pline = fgets(buf,BUF_SIZE,fp);
      cout << buf << endl;
      //cout<<len<<endl;
  }while(pline != NULL);
  end = clock();
  fclose(fp);
  cout<<"C style: "<<end - start <<" ms"<<endl;
  cin.get();
}
int main()
{
    test_read();
    return 0;
}</p>總之,/fread() 的效率 是 ifstream.read()的將近十倍!但是,fstream 對於處理資料而言,還是統一的應用STL的標準好;總之,語言僅僅是一門工具,本身沒有優劣之分。fgets(char*,int,File*); getline(stream,string),還是個人習慣的好。如今,還是建議應用面向物件的語言好一些,java 或者 c++,java更加強大一些,有自己的各種類庫。c++沒有的,連結JDBC的東東的

(5)總結

第一條,(寫了這麼多了,用兩句話概括吧)最近從網上,看到了一句很經典的話,c++的風fstream類 + string類 也可以非常好的處理文字檔案;
第二條,語言僅僅是一種工具,本身並沒有優劣之分