怎么利用迭代器写入mysql_流迭代器实现文件操作(读取和写入)方法详解
流迭代器并不知道底層流的特性。當然,它們只適用于文本模式,否則它們不會關心數據是什么。流迭代器可以以文本模式來讀寫任何類型的流。這意味著除了其他的一些流之外,我們可以用迭代器以文本模式來讀和寫文件。在深入講解如何對文件使用流迭代器之前,需要提醒你文件流的一些本質特征以及如何生成一個封裝了文件的流對象。
文件流
文件流封裝了一個實際的文件。文件流有長度,也就是這個流中字符的個數,因此對于新的輸出文件,長度就是 0;文件流有起始位置,起始位置是流中索引為 0 的第一個字符的索引;文件流也有結束位置,結束位置是文件流中最后一個字符的下一個位置。文件流還有當前位置,是下一個寫或讀操作的開始位置的索引。可以以文本模式或二進制模式將數據轉移到文件中或從文件中讀出來。
在文本模式下,數據是字符的序列。可以用提取和插入運算符來讀寫數據,至少對于輸入來說,數據項必須由一個或多個空格隔開。數據經常被寫成以 '\n' 終止的連續行。一些系統,例如微軟的 Windows 系統,在讀寫時會轉換換行符。在讀到回車和換行符時,它們會被映射到單個字符 '\n'。在另一些系統中,換行符被當作單個字符讀寫。因此,文件輸入流的長度依賴于它們所來自的系統環境。
在二進制模式下,內存和流之間是以字節的形式傳送數據的,不需要轉換。流迭代器只能工作在文本模式下,因此不能用流迭代器來讀寫二進制文件。本章后面要介紹的流緩沖迭代器,可以讀寫二進制文件。
盡管在二進制模式下,從內存中讀取和寫入的字節從來不會改變,但當談到處理寫到不同系統中的二進制文件時,仍然會有很多陷阱。一個考慮是,寫文件的系統的字節順序和讀文件的系統的字節順序是相反的。字節順序決定了內存中字的寫入順序。
在小端字節序的處理器中,例如英特爾的 X86 處理器,最低位的字節在最低的地址,所以字節的寫入順序是從最底字節到最髙字節。在大端字節序的處理器中,例如 EBM 大型機,比特的順序是相反的,最高字節在最低位置,因此它們的文件會出現和小端字節序的處理器相反的順序。因此,當在小端字節序的系統中讀來自于大端字節序的系統中的二進制文件時,需要考慮字節序的差別。
注意,大端字節序也被稱為網絡字節序,因為數據一般是以大端序在互聯網上傳輸的。
文件流類的模板
這里有 3 個表示文件流的類模板:
ifstream:表示文件的輸出流;
ofstream:是為輸出定義的文件流;
fstream:定義了可以讀和寫的文件流;
這些類的繼承關系如圖 1 所示。
圖 1 表示文件流的類模板的繼承層次結構
文件流模板從 istream 和 / 或 ostream 繼承,因此在文本模式中,它們的工作方式和標準流相同。能夠對一個文件流做什么是由它的打開狀態決定的,可以用下面這些定義在 ios_base 類中的常量的組合來指定它們的打開狀態:
binary:會將文件設置成二進制模式。如果沒有設置二進制模式,默認的模式是文本模式。
app:在每個寫操作(append operation)之前會移到文件的末尾。
ate:會在打開文件之后(at the end),移到文件的末尾。
in:打開文件來讀。對于ifstream和fstream來說,這是默認的。
out:打開文件來寫。對于ostream和fstream來說,這是默認的。
trunc:將當前存在的文件長度截斷為0。
生成的文件流對象默認是在文本模式下;為了得到二進制模式,必須指定常量 binary。文本模式的操作會用 >> 和 << 運算符來讀寫數據,數值數據在被寫到流之前會被轉換為它們的字符表示。在二進制模式下,沒有數據轉換;內存中的字節會被直接寫到文件中。當指定一個并不存在的文件的名稱作為 ofstream 的構造函數參數時,這個文件會被生成。如果在生成或打開一個文件輸出流對象時不指定 app 或 ate,任何存在的文件的內容都會被覆蓋。
在本節的幾個示例中都會讀 dictionary.txt,它被包含在下載的代碼中。在微軟的 Windows 環境下,會用文本模式來寫文件;但如果在不同的環境中執行這個程序,這個示例也應該讀它。這個示例使用了微軟 Windows 的路徑 G:。這樣做是為了使需要改變這些來適應系統環境變得更加可能,同時會使我們有責任確認不會覆蓋掉重要的文件。
用流迭代器進行文件輸入
一旦創建用于讀文件的文件流對象,用流迭代器來訪問數據和從標準輸入流讀數據就 基本相同。我們可以寫一個查找字謎的程序,通過下載代碼中的字典文件來查找它們。在這種情況下,我們需要用流迭代器來將字典文件中的所有單詞讀取到容器中。下面是代碼:
//1.cpp
// Finding anagrams of a word
#include // For standard streams
#include // For file streams
#include // For iterators and begin() and end()
#include // For string class
#include // For set container
#include // For vector container
#include // For next_permutation()
using std::string;
int main()
{
// Read words from the file into a set container
string file_in {"G:/Beginning_STL/dictionary.txt"};
std::ifstream in {file_in};
if(!in)
{
std::cerr << file_in << " not open." << std::endl;
exit(1);
}
std::set dictionary {std::istream_iterator(in), std::istream_iterator()};
std::cout << dictionary.size() << " words in dictionary." << std::endl;
std::vector words;
string word;
while(true)
{
std::cout << "\nEnter a word, or Ctrl+z to end: ";
if((std::cin >> word).eof()) break;
string word_copy {word};
do
{
if(dictionary.count(word)) words.push_back(word);
std::next_permutation(std::begin(word), std::end(word));
} while(word != word_copy);
std::copy(std::begin(words), std::end(words), std::ostream_iterator{std::cout, " "});
std::cout << std::endl;
words.clear(); // Remove previous permutations
}
in.close(); // Close the file
}
字典文件中包含超過 100 000 個單詞,因此讀取會花費一些時間。ifstream 對象是用文件 dictionary.txt 的全路徑來生成。這個文件包含了相當數量的不同單詞,可以用來搜查檢查字謎。整個文件的內容被用來作為 set 容器的初始值。正如我們知道的那樣,set 容器會以升序保存單詞,并且這個容器中的每個單詞都有自己的 key。words 容器中包含從 cin 中輸入的單詞的字謎。每一個單詞都在 while 循環的第一個 if 表達式中讀取。這里調用流對象的 eof(),當輸入 Ctrl+Z 時,eof() 會返回 true。
輸入的單詞的字符重排列是通過在 do-while 循環中調用 next_permutation() 算法生成的。對于每次排列,包括第一次,都調用 count() 來確定這個單詞是否在 dictionary 容器中。如果在,這個單詞會被附加到 words 容器中。當排列返回原始單詞時,do-while 循環結束。當找到一個單詞的所有字謎時,會用以輸出流迭代器作為目的地址的 copy() 算法將 words 寫到 cout 中。如果期望每次出現 8 個字謎,需要使用循環來生成多行輸出:
size_t count {}, max {8};
for (const auto& wrd : words)
std::cout << wrd << ((++count % max == 0) ? '\n' : ' ');
下面是一些示例輸出:
109582 words in dictionary.
Enter a word, or Ctrl+z to end: realist
realist retails saltier slatier tailers
Enter a word, or Ctrl+z to end: painter
painter pertain repaint
Enter a word, or Ctrl+z to end: dog、
dog god
Enter a word, or Ctrl+z to end: ^Z
用流迭代器來反復讀文件
當然,如果字典文件非常大,可能不想把它全部讀到內存中。在這種情況下,可以在每次想要查找字謎時,用流迭代器來重讀文件。下面的版本可以實現這一點,盡管它的表現并不怎么令人印象深刻:
//2.cpp
//Finding anagrams of a word by re-reading the dictionary file
#include // For standard streams
#include // For file streams
#include // For iterators and begin() and end()
#include // For string class
#include // For set container
#include // For vector container
#include // For next_permutation()
using std::string;
int main()
{
string file_in {"G:/Beginning_STL/dictionary.txt"};
std::ifstream in {file_in};
if(!in)
{
std::cerr << file_in << " not open." << std::endl;
exit(1);
}
auto end_iter = std::istream_iterator {};
std::vector words;
string word;
while(true)
{
std::cout << "\nEnter a word, or Ctrl+z to end: ";
if((std::cin >> word).eof()) break;
string word_copy {word};
do
{
in.seekg(0); // File position at beginning
// Use find() algorithm to read the file to check for an anagram
if(std::find(std::istream_iterator(in), end_iter, word) != end_iter)
words.push_back(word);
else
in.clear(); // Reset EOF
std::next_permutation(std::begin(word), std::end(word));
} while(word != word_copy);
std::copy(std::begin(words), std::end(words), std::ostream_iterator{std::cout, " "});
std::cout << std::endl;
words.clear(); // Remove previous permutations
}
in.close(); // Close the file
}
結束流迭代器不會改變,因此為了可以多次使用它,會將它定義為 end_iter。循環基本相同,除了用來發現給定的排列是否在文件中,如果在文件中,就是一個字謎。文件的位置需要是第一個字符的位置,在這個示例中會調用文件對象的 seekg() 來保證這一點。find() 的前兩個參數是定義從當前位置得到的序列的 istream_iterator 對象,它們被設置為文件的開始位置和結束位置。find() 算法會返回一個迭代器,它指向的元素和第三個參數匹配,或者返回結束迭代器,如果它并不存在的話。因此,如果 find() 返回的是結束流迭代器,就沒有找到 word;返回其他任何迭代器都意味著找到了 word。當 w 沒有找到 word 時,為文件流對象調用 dear() 來清除 EOF 標志是有必要的。如果不這么做,隨后訪問文件會失敗,因為 EOF 標志已被設置。
下面是一些說明它工作的示例輸出:
Enter a word, or Ctrl+z to end: rate
rate tare tear erat
Enter a word, or Ctrl+z to end: rat
rat tar art
Enter a word, or Ctrl+z to end: god
god dog
Enter a word, or Ctrl+z to end: ^Z
選擇輸入短單詞,因為檢查字謎的過程緩慢而痛苦。n 個字符的單詞有 n! 種排列。為檢查一個排列是否在文件中,需要比較 100 000 次讀操作,取決于它是否在文件中。因此,檢查一個單詞,例如“retain”需要 7 百萬次讀操作,這也是為什么這個過程十分緩慢的原因。
istream_iterator 對象會從流中一次讀取一個 T 對象,因此如果有大量的對象,這個過程也總是很慢。一旦文件被讀到 set 容器中,1.cpp 肯定比 2.cpp 快,因為隨后的所有操作都是在內存的字典 words 中進行的。
1.cpp 更快的第二個原因是,對 set 容器的訪問包含時間復雜度為 O(logn) 的二分查找。串行訪問文件包含每次從起始位置開始讀單詞直到找到匹配,時間復雜度為 O(n)。如果文件中的數據是有序的(dictionary.txt 中的單詞是有序的),就能用二分查找技術來找到數據項。在這種情況下,流迭代器是多余的,因為總會讀單個單詞,對流對象使用 >> 運算符可以更容易地做到這一點。然而,這并不容易實現,因為單詞的長度不同。
用流迭代器輸出文件
寫文件和寫標準輸出流沒有什么不同。例如,可以按如下方式用流迭代器復制 dictionary.txt 文件的內容:
// Copying file contents using stream iterators
#include // For standard streams
#include // For file streams
#include // For iterators and begin() and end()
#include // For string class
using std::string;
int main()
{
string file_in {"G:/Beginning_STL/dictionary.txt"};
std::ifstream in {file_in};
if(!in)
{
std::cerr << file_in << " not open." << std::endl;
exit(1);
}
string file_out {"G:/Beginning_STL/dictionary_copy.txt"};
std::ofstream out {file_out};
std::copy(std::istream_iterator{in}, std::istream_iterator{}, std::ostream_iterator {out, " "});
in.clear(); // Clear EOF
std::cout << "Original file length: " << in.tellg() << std::endl;
std::cout << "File copy length: " << out.tellp() << std::endl;
in.close();
out.close();
}
這個程序會從輸入文件復制單詞到輸出文件,以空格隔開輸出的單詞。這個程序總會覆蓋輸出文件的內容。下面是得到的輸出:
Original file length: 1154336
File copy length: 1154336
輸出文件流的打開模式除了有 ios_base::out 標志之外,還有 ios_base::trunc 標志,因此如果文件已經存在,它會被截斷。這阻止生成不斷增長的文件,如果多次運行這個示例的話。如果用編輯器檢查 dictionary.txt 的內容,會看到單詞是被單個空格隔開的。我們寫的文件副本的每個單詞之間都有一個空格,因此文件的長度相同。
然而,如果原始文件中的單詞是用兩個或兩個以上空格隔開的,文件的副本會更短。為了確保能夠用流迭代器準確地復制原文件,必須一個字符一個字符地讀文件,并阻止 >> 運算符忽略空格。下面展示了如何這么做:
std::copy(std::istream_iterator{in >> std::noskipws},std::istream iterator{},std::ostream_iter ator {out});
這段代碼會按字符復制 in 流,包含空格,這樣復制文件比流迭代器更快,在本章的后面會進行解釋。
總結
以上是生活随笔為你收集整理的怎么利用迭代器写入mysql_流迭代器实现文件操作(读取和写入)方法详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python引入jit_从numba导入
- 下一篇: mysql查找内容某字符串出现的次数_查