1# 流 2 3在 RapidJSON 中,`rapidjson::Stream` 是用於读写 JSON 的概念(概念是指 C++ 的 concept)。在这里我们先介绍如何使用 RapidJSON 提供的各种流。然后再看看如何自行定义流。 4 5[TOC] 6 7# 内存流 {#MemoryStreams} 8 9内存流把 JSON 存储在内存之中。 10 11## StringStream(输入){#StringStream} 12 13`StringStream` 是最基本的输入流,它表示一个完整的、只读的、存储于内存的 JSON。它在 `rapidjson/rapidjson.h` 中定义。 14 15~~~~~~~~~~cpp 16#include "rapidjson/document.h" // 会包含 "rapidjson/rapidjson.h" 17 18using namespace rapidjson; 19 20// ... 21const char json[] = "[1, 2, 3, 4]"; 22StringStream s(json); 23 24Document d; 25d.ParseStream(s); 26~~~~~~~~~~ 27 28由于这是非常常用的用法,RapidJSON 提供 `Document::Parse(const char*)` 去做完全相同的事情: 29 30~~~~~~~~~~cpp 31// ... 32const char json[] = "[1, 2, 3, 4]"; 33Document d; 34d.Parse(json); 35~~~~~~~~~~ 36 37需要注意,`StringStream` 是 `GenericStringStream<UTF8<> >` 的 typedef,使用者可用其他编码类去代表流所使用的字符集。 38 39## StringBuffer(输出){#StringBuffer} 40 41`StringBuffer` 是一个简单的输出流。它分配一个内存缓冲区,供写入整个 JSON。可使用 `GetString()` 来获取该缓冲区。 42 43~~~~~~~~~~cpp 44#include "rapidjson/stringbuffer.h" 45 46StringBuffer buffer; 47Writer<StringBuffer> writer(buffer); 48d.Accept(writer); 49 50const char* output = buffer.GetString(); 51~~~~~~~~~~ 52 53当缓冲区满溢,它将自动增加容量。缺省容量是 256 个字符(UTF8 是 256 字节,UTF16 是 512 字节等)。使用者能自行提供分配器及初始容量。 54 55~~~~~~~~~~cpp 56StringBuffer buffer1(0, 1024); // 使用它的分配器,初始大小 = 1024 57StringBuffer buffer2(allocator, 1024); 58~~~~~~~~~~ 59 60如无设置分配器,`StringBuffer` 会自行实例化一个内部分配器。 61 62相似地,`StringBuffer` 是 `GenericStringBuffer<UTF8<> >` 的 typedef。 63 64# 文件流 {#FileStreams} 65 66当要从文件解析一个 JSON,你可以把整个 JSON 读入内存并使用上述的 `StringStream`。 67 68然而,若 JSON 很大,或是内存有限,你可以改用 `FileReadStream`。它只会从文件读取一部分至缓冲区,然后让那部分被解析。若缓冲区的字符都被读完,它会再从文件读取下一部分。 69 70## FileReadStream(输入) {#FileReadStream} 71 72`FileReadStream` 通过 `FILE` 指针读取文件。使用者需要提供一个缓冲区。 73 74~~~~~~~~~~cpp 75#include "rapidjson/filereadstream.h" 76#include <cstdio> 77 78using namespace rapidjson; 79 80FILE* fp = fopen("big.json", "rb"); // 非 Windows 平台使用 "r" 81 82char readBuffer[65536]; 83FileReadStream is(fp, readBuffer, sizeof(readBuffer)); 84 85Document d; 86d.ParseStream(is); 87 88fclose(fp); 89~~~~~~~~~~ 90 91与 `StringStreams` 不一样,`FileReadStream` 是一个字节流。它不处理编码。若文件并非 UTF-8 编码,可以把字节流用 `EncodedInputStream` 包装。我们很快会讨论这个问题。 92 93除了读取文件,使用者也可以使用 `FileReadStream` 来读取 `stdin`。 94 95## FileWriteStream(输出){#FileWriteStream} 96 97`FileWriteStream` 是一个含缓冲功能的输出流。它的用法与 `FileReadStream` 非常相似。 98 99~~~~~~~~~~cpp 100#include "rapidjson/filewritestream.h" 101#include <cstdio> 102 103using namespace rapidjson; 104 105Document d; 106d.Parse(json); 107// ... 108 109FILE* fp = fopen("output.json", "wb"); // 非 Windows 平台使用 "w" 110 111char writeBuffer[65536]; 112FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer)); 113 114Writer<FileWriteStream> writer(os); 115d.Accept(writer); 116 117fclose(fp); 118~~~~~~~~~~ 119 120它也可以把输出导向 `stdout`。 121 122# iostream 包装类 {#iostreamWrapper} 123 124基于用户的要求,RapidJSON 提供了正式的 `std::basic_istream` 和 `std::basic_ostream` 包装类。然而,请注意其性能会大大低于以上的其他流。 125 126## IStreamWrapper {#IStreamWrapper} 127 128`IStreamWrapper` 把任何继承自 `std::istream` 的类(如 `std::istringstream`、`std::stringstream`、`std::ifstream`、`std::fstream`)包装成 RapidJSON 的输入流。 129 130~~~cpp 131#include <rapidjson/document.h> 132#include <rapidjson/istreamwrapper.h> 133#include <fstream> 134 135using namespace rapidjson; 136using namespace std; 137 138ifstream ifs("test.json"); 139IStreamWrapper isw(ifs); 140 141Document d; 142d.ParseStream(isw); 143~~~ 144 145对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`。 146 147## OStreamWrapper {#OStreamWrapper} 148 149相似地,`OStreamWrapper` 把任何继承自 `std::ostream` 的类(如 `std::ostringstream`、`std::stringstream`、`std::ofstream`、`std::fstream`)包装成 RapidJSON 的输出流。 150 151~~~cpp 152#include <rapidjson/document.h> 153#include <rapidjson/ostreamwrapper.h> 154#include <rapidjson/writer.h> 155#include <fstream> 156 157using namespace rapidjson; 158using namespace std; 159 160Document d; 161d.Parse(json); 162 163// ... 164 165ofstream ofs("output.json"); 166OStreamWrapper osw(ofs); 167 168Writer<OStreamWrapper> writer(osw); 169d.Accept(writer); 170~~~ 171 172对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`。 173 174# 编码流 {#EncodedStreams} 175 176编码流(encoded streams)本身不存储 JSON,它们是通过包装字节流来提供基本的编码/解码功能。 177 178如上所述,我们可以直接读入 UTF-8 字节流。然而,UTF-16 及 UTF-32 有字节序(endian)问题。要正确地处理字节序,需要在读取时把字节转换成字符(如对 UTF-16 使用 `wchar_t`),以及在写入时把字符转换为字节。 179 180除此以外,我们也需要处理 [字节顺序标记(byte order mark, BOM)](http://en.wikipedia.org/wiki/Byte_order_mark)。当从一个字节流读取时,需要检测 BOM,或者仅仅是把存在的 BOM 消去。当把 JSON 写入字节流时,也可选择写入 BOM。 181 182若一个流的编码在编译期已知,你可使用 `EncodedInputStream` 及 `EncodedOutputStream`。若一个流可能存储 UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE 的 JSON,并且编码只能在运行时得知,你便可以使用 `AutoUTFInputStream` 及 `AutoUTFOutputStream`。这些流定义在 `rapidjson/encodedstream.h`。 183 184注意到,这些编码流可以施于文件以外的流。例如,你可以用编码流包装内存中的文件或自定义的字节流。 185 186## EncodedInputStream {#EncodedInputStream} 187 188`EncodedInputStream` 含两个模板参数。第一个是 `Encoding` 类型,例如定义于 `rapidjson/encodings.h` 的 `UTF8`、`UTF16LE`。第二个参数是被包装的流的类型。 189 190~~~~~~~~~~cpp 191#include "rapidjson/document.h" 192#include "rapidjson/filereadstream.h" // FileReadStream 193#include "rapidjson/encodedstream.h" // EncodedInputStream 194#include <cstdio> 195 196using namespace rapidjson; 197 198FILE* fp = fopen("utf16le.json", "rb"); // 非 Windows 平台使用 "r" 199 200char readBuffer[256]; 201FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); 202 203EncodedInputStream<UTF16LE<>, FileReadStream> eis(bis); // 用 eis 包装 bis 204 205Document d; // Document 为 GenericDocument<UTF8<> > 206d.ParseStream<0, UTF16LE<> >(eis); // 把 UTF-16LE 文件解析至内存中的 UTF-8 207 208fclose(fp); 209~~~~~~~~~~ 210 211## EncodedOutputStream {#EncodedOutputStream} 212 213`EncodedOutputStream` 也是相似的,但它的构造函数有一个 `bool putBOM` 参数,用于控制是否在输出字节流写入 BOM。 214 215~~~~~~~~~~cpp 216#include "rapidjson/filewritestream.h" // FileWriteStream 217#include "rapidjson/encodedstream.h" // EncodedOutputStream 218#include <cstdio> 219 220Document d; // Document 为 GenericDocument<UTF8<> > 221// ... 222 223FILE* fp = fopen("output_utf32le.json", "wb"); // 非 Windows 平台使用 "w" 224 225char writeBuffer[256]; 226FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer)); 227 228typedef EncodedOutputStream<UTF32LE<>, FileWriteStream> OutputStream; 229OutputStream eos(bos, true); // 写入 BOM 230 231Writer<OutputStream, UTF32LE<>, UTF8<>> writer(eos); 232d.Accept(writer); // 这里从内存的 UTF-8 生成 UTF32-LE 文件 233 234fclose(fp); 235~~~~~~~~~~ 236 237## AutoUTFInputStream {#AutoUTFInputStream} 238 239有时候,应用软件可能需要㲃理所有可支持的 JSON 编码。`AutoUTFInputStream` 会先使用 BOM 来检测编码。若 BOM 不存在,它便会使用合法 JSON 的特性来检测。若两种方法都失败,它就会倒退至构造函数提供的 UTF 类型。 240 241由于字符(编码单元/code unit)可能是 8 位、16 位或 32 位,`AutoUTFInputStream` 需要一个能至少储存 32 位的字符类型。我们可以使用 `unsigned` 作为模板参数: 242 243~~~~~~~~~~cpp 244#include "rapidjson/document.h" 245#include "rapidjson/filereadstream.h" // FileReadStream 246#include "rapidjson/encodedstream.h" // AutoUTFInputStream 247#include <cstdio> 248 249using namespace rapidjson; 250 251FILE* fp = fopen("any.json", "rb"); // 非 Windows 平台使用 "r" 252 253char readBuffer[256]; 254FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); 255 256AutoUTFInputStream<unsigned, FileReadStream> eis(bis); // 用 eis 包装 bis 257 258Document d; // Document 为 GenericDocument<UTF8<> > 259d.ParseStream<0, AutoUTF<unsigned> >(eis); // 把任何 UTF 编码的文件解析至内存中的 UTF-8 260 261fclose(fp); 262~~~~~~~~~~ 263 264当要指定流的编码,可使用上面例子中 `ParseStream()` 的参数 `AutoUTF<CharType>`。 265 266你可以使用 `UTFType GetType()` 去获取 UTF 类型,并且用 `HasBOM()` 检测输入流是否含有 BOM。 267 268## AutoUTFOutputStream {#AutoUTFOutputStream} 269 270相似地,要在运行时选择输出的编码,我们可使用 `AutoUTFOutputStream`。这个类本身并非「自动」。你需要在运行时指定 UTF 类型,以及是否写入 BOM。 271 272~~~~~~~~~~cpp 273using namespace rapidjson; 274 275void WriteJSONFile(FILE* fp, UTFType type, bool putBOM, const Document& d) { 276 char writeBuffer[256]; 277 FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer)); 278 279 typedef AutoUTFOutputStream<unsigned, FileWriteStream> OutputStream; 280 OutputStream eos(bos, type, putBOM); 281 282 Writer<OutputStream, UTF8<>, AutoUTF<> > writer; 283 d.Accept(writer); 284} 285~~~~~~~~~~ 286 287`AutoUTFInputStream`/`AutoUTFOutputStream` 是比 `EncodedInputStream`/`EncodedOutputStream` 方便。但前者会产生一点运行期额外开销。 288 289# 自定义流 {#CustomStream} 290 291除了内存/文件流,使用者可创建自行定义适配 RapidJSON API 的流类。例如,你可以创建网络流、从压缩文件读取的流等等。 292 293RapidJSON 利用模板结合不同的类型。只要一个类包含所有所需的接口,就可以作为一个流。流的接合定义在 `rapidjson/rapidjson.h` 的注释里: 294 295~~~~~~~~~~cpp 296concept Stream { 297 typename Ch; //!< 流的字符类型 298 299 //! 从流读取当前字符,不移动读取指针(read cursor) 300 Ch Peek() const; 301 302 //! 从流读取当前字符,移动读取指针至下一字符。 303 Ch Take(); 304 305 //! 获取读取指针。 306 //! \return 从开始以来所读过的字符数量。 307 size_t Tell(); 308 309 //! 从当前读取指针开始写入操作。 310 //! \return 返回开始写入的指针。 311 Ch* PutBegin(); 312 313 //! 写入一个字符。 314 void Put(Ch c); 315 316 //! 清空缓冲区。 317 void Flush(); 318 319 //! 完成写作操作。 320 //! \param begin PutBegin() 返回的开始写入指针。 321 //! \return 已写入的字符数量。 322 size_t PutEnd(Ch* begin); 323} 324~~~~~~~~~~ 325 326输入流必须实现 `Peek()`、`Take()` 及 `Tell()`。 327输出流必须实现 `Put()` 及 `Flush()`。 328`PutBegin()` 及 `PutEnd()` 是特殊的接口,仅用于原位(*in situ*)解析。一般的流不需实现它们。然而,即使接口不需用于某些流,仍然需要提供空实现,否则会产生编译错误。 329 330## 例子:istream 的包装类 {#ExampleIStreamWrapper} 331 332以下的简单例子是 `std::istream` 的包装类,它只需现 3 个函数。 333 334~~~~~~~~~~cpp 335class MyIStreamWrapper { 336public: 337 typedef char Ch; 338 339 MyIStreamWrapper(std::istream& is) : is_(is) { 340 } 341 342 Ch Peek() const { // 1 343 int c = is_.peek(); 344 return c == std::char_traits<char>::eof() ? '\0' : (Ch)c; 345 } 346 347 Ch Take() { // 2 348 int c = is_.get(); 349 return c == std::char_traits<char>::eof() ? '\0' : (Ch)c; 350 } 351 352 size_t Tell() const { return (size_t)is_.tellg(); } // 3 353 354 Ch* PutBegin() { assert(false); return 0; } 355 void Put(Ch) { assert(false); } 356 void Flush() { assert(false); } 357 size_t PutEnd(Ch*) { assert(false); return 0; } 358 359private: 360 MyIStreamWrapper(const MyIStreamWrapper&); 361 MyIStreamWrapper& operator=(const MyIStreamWrapper&); 362 363 std::istream& is_; 364}; 365~~~~~~~~~~ 366 367使用者能用它来包装 `std::stringstream`、`std::ifstream` 的实例。 368 369~~~~~~~~~~cpp 370const char* json = "[1,2,3,4]"; 371std::stringstream ss(json); 372MyIStreamWrapper is(ss); 373 374Document d; 375d.ParseStream(is); 376~~~~~~~~~~ 377 378但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 379 380## 例子:ostream 的包装类 {#ExampleOStreamWrapper} 381 382以下的例子是 `std::istream` 的包装类,它只需实现 2 个函数。 383 384~~~~~~~~~~cpp 385class MyOStreamWrapper { 386public: 387 typedef char Ch; 388 389 OStreamWrapper(std::ostream& os) : os_(os) { 390 } 391 392 Ch Peek() const { assert(false); return '\0'; } 393 Ch Take() { assert(false); return '\0'; } 394 size_t Tell() const { } 395 396 Ch* PutBegin() { assert(false); return 0; } 397 void Put(Ch c) { os_.put(c); } // 1 398 void Flush() { os_.flush(); } // 2 399 size_t PutEnd(Ch*) { assert(false); return 0; } 400 401private: 402 MyOStreamWrapper(const MyOStreamWrapper&); 403 MyOStreamWrapper& operator=(const MyOStreamWrapper&); 404 405 std::ostream& os_; 406}; 407~~~~~~~~~~ 408 409使用者能用它来包装 `std::stringstream`、`std::ofstream` 的实例。 410 411~~~~~~~~~~cpp 412Document d; 413// ... 414 415std::stringstream ss; 416MyOStreamWrapper os(ss); 417 418Writer<MyOStreamWrapper> writer(os); 419d.Accept(writer); 420~~~~~~~~~~ 421 422但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 423 424# 总结 {#Summary} 425 426本节描述了 RapidJSON 提供的各种流的类。内存流很简单。若 JSON 存储在文件中,文件流可减少 JSON 解析及生成所需的内存量。编码流在字节流和字符流之间作转换。最后,使用者可使用一个简单接口创建自定义的流。 427