stream.zh-cn.md 13.9 KB
Newer Older
Milo Yip's avatar
Milo Yip committed
1 2
# 流

3
在 RapidJSON 中,`rapidjson::Stream` 是用於读写 JSON 的概念(概念是指 C++ 的 concept)。在这里我们先介绍如何使用 RapidJSON 提供的各种流。然后再看看如何自行定义流。
Milo Yip's avatar
Milo Yip committed
4 5 6 7 8

[TOC]

# 内存流 {#MemoryStreams}

9
内存流把 JSON 存储在内存之中。
Milo Yip's avatar
Milo Yip committed
10 11 12

## StringStream(输入){#StringStream}

13
`StringStream` 是最基本的输入流,它表示一个完整的、只读的、存储于内存的 JSON。它在 `rapidjson/rapidjson.h` 中定义。
Milo Yip's avatar
Milo Yip committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27

~~~~~~~~~~cpp
#include "rapidjson/document.h" // 会包含 "rapidjson/rapidjson.h"

using namespace rapidjson;

// ...
const char json[] = "[1, 2, 3, 4]";
StringStream s(json);

Document d;
d.ParseStream(s);
~~~~~~~~~~

28
由于这是非常常用的用法,RapidJSON 提供 `Document::Parse(const char*)` 去做完全相同的事情:
Milo Yip's avatar
Milo Yip committed
29 30 31 32 33 34 35 36

~~~~~~~~~~cpp
// ...
const char json[] = "[1, 2, 3, 4]";
Document d;
d.Parse(json);
~~~~~~~~~~

37
需要注意,`StringStream``GenericStringStream<UTF8<> >` 的 typedef,使用者可用其他编码类去代表流所使用的字符集。
Milo Yip's avatar
Milo Yip committed
38 39 40

## StringBuffer(输出){#StringBuffer}

41
`StringBuffer` 是一个简单的输出流。它分配一个内存缓冲区,供写入整个 JSON。可使用 `GetString()` 来获取该缓冲区。
Milo Yip's avatar
Milo Yip committed
42 43 44 45 46 47 48 49 50 51 52

~~~~~~~~~~cpp
#include "rapidjson/stringbuffer.h"

StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);

const char* output = buffer.GetString();
~~~~~~~~~~

53
当缓冲区满溢,它将自动增加容量。缺省容量是 256 个字符(UTF8 是 256 字节,UTF16 是 512 字节等)。使用者能自行提供分配器及初始容量。
Milo Yip's avatar
Milo Yip committed
54 55 56 57 58 59

~~~~~~~~~~cpp
StringBuffer buffer1(0, 1024); // 使用它的分配器,初始大小 = 1024
StringBuffer buffer2(allocator, 1024);
~~~~~~~~~~

60
如无设置分配器,`StringBuffer` 会自行实例化一个内部分配器。
Milo Yip's avatar
Milo Yip committed
61

62
相似地,`StringBuffer``GenericStringBuffer<UTF8<> >` 的 typedef。
Milo Yip's avatar
Milo Yip committed
63 64 65

# 文件流 {#FileStreams}

66
当要从文件解析一个 JSON,你可以把整个 JSON 读入内存并使用上述的 `StringStream`
Milo Yip's avatar
Milo Yip committed
67

68
然而,若 JSON 很大,或是内存有限,你可以改用 `FileReadStream`。它只会从文件读取一部分至缓冲区,然后让那部分被解析。若缓冲区的字符都被读完,它会再从文件读取下一部分。
Milo Yip's avatar
Milo Yip committed
69 70 71

## FileReadStream(输入) {#FileReadStream}

72
`FileReadStream` 通过 `FILE` 指针读取文件。使用者需要提供一个缓冲区。
Milo Yip's avatar
Milo Yip committed
73 74 75 76 77 78 79

~~~~~~~~~~cpp
#include "rapidjson/filereadstream.h"
#include <cstdio>

using namespace rapidjson;

80
FILE* fp = fopen("big.json", "rb"); // 非 Windows 平台使用 "r"
Milo Yip's avatar
Milo Yip committed
81 82 83 84 85 86 87 88 89 90

char readBuffer[65536];
FileReadStream is(fp, readBuffer, sizeof(readBuffer));

Document d;
d.ParseStream(is);

fclose(fp);
~~~~~~~~~~

91
`StringStreams` 不一样,`FileReadStream` 是一个字节流。它不处理编码。若文件并非 UTF-8 编码,可以把字节流用 `EncodedInputStream` 包装。我们很快会讨论这个问题。
Milo Yip's avatar
Milo Yip committed
92

93
除了读取文件,使用者也可以使用 `FileReadStream` 来读取 `stdin`
Milo Yip's avatar
Milo Yip committed
94 95 96

## FileWriteStream(输出){#FileWriteStream}

97
`FileWriteStream` 是一个含缓冲功能的输出流。它的用法与 `FileReadStream` 非常相似。
Milo Yip's avatar
Milo Yip committed
98 99 100 101 102 103 104 105 106 107 108

~~~~~~~~~~cpp
#include "rapidjson/filewritestream.h"
#include <cstdio>

using namespace rapidjson;

Document d;
d.Parse(json);
// ...

109
FILE* fp = fopen("output.json", "wb"); // 非 Windows 平台使用 "w"
Milo Yip's avatar
Milo Yip committed
110 111 112 113 114 115 116 117 118 119

char writeBuffer[65536];
FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));

Writer<FileWriteStream> writer(os);
d.Accept(writer);

fclose(fp);
~~~~~~~~~~

120
它也可以把输出导向 `stdout`
Milo Yip's avatar
Milo Yip committed
121

Milo Yip's avatar
Milo Yip committed
122 123
# iostream 包装类 {#iostreamWrapper}

124
基于用户的要求,RapidJSON 提供了正式的 `std::basic_istream``std::basic_ostream` 包装类。然而,请注意其性能会大大低于以上的其他流。
Milo Yip's avatar
Milo Yip committed
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

## IStreamWrapper {#IStreamWrapper}

`IStreamWrapper` 把任何继承自 `std::istream` 的类(如 `std::istringstream``std::stringstream``std::ifstream``std::fstream`)包装成 RapidJSON 的输入流。

~~~cpp
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <fstream>

using namespace rapidjson;
using namespace std;

ifstream ifs("test.json");
IStreamWrapper isw(ifs);

Document d;
d.ParseStream(isw);
~~~

对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`

## OStreamWrapper {#OStreamWrapper}

相似地,`OStreamWrapper` 把任何继承自 `std::ostream` 的类(如 `std::ostringstream``std::stringstream``std::ofstream``std::fstream`)包装成 RapidJSON 的输出流。

~~~cpp
#include <rapidjson/document.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/writer.h>
#include <fstream>

using namespace rapidjson;
using namespace std;

Document d;
d.Parse(json);

// ...

ofstream ofs("output.json");
OStreamWrapper osw(ofs);

Writer<OStreamWrapper> writer(osw);
d.Accept(writer);
~~~

对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`

Milo Yip's avatar
Milo Yip committed
174 175
# 编码流 {#EncodedStreams}

176
编码流(encoded streams)本身不存储 JSON,它们是通过包装字节流来提供基本的编码/解码功能。
Milo Yip's avatar
Milo Yip committed
177

178
如上所述,我们可以直接读入 UTF-8 字节流。然而,UTF-16 及 UTF-32 有字节序(endian)问题。要正确地处理字节序,需要在读取时把字节转换成字符(如对 UTF-16 使用 `wchar_t`),以及在写入时把字符转换为字节。
Milo Yip's avatar
Milo Yip committed
179

180
除此以外,我们也需要处理 [字节顺序标记(byte order mark, BOM)](http://en.wikipedia.org/wiki/Byte_order_mark)。当从一个字节流读取时,需要检测 BOM,或者仅仅是把存在的 BOM 消去。当把 JSON 写入字节流时,也可选择写入 BOM。
Milo Yip's avatar
Milo Yip committed
181

182
若一个流的编码在编译期已知,你可使用 `EncodedInputStream``EncodedOutputStream`。若一个流可能存储 UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE 的 JSON,并且编码只能在运行时得知,你便可以使用 `AutoUTFInputStream``AutoUTFOutputStream`。这些流定义在 `rapidjson/encodedstream.h`
Milo Yip's avatar
Milo Yip committed
183 184 185 186 187

注意到,这些编码流可以施于文件以外的流。例如,你可以用编码流包装内存中的文件或自定义的字节流。

## EncodedInputStream {#EncodedInputStream}

188
`EncodedInputStream` 含两个模板参数。第一个是 `Encoding` 类型,例如定义于 `rapidjson/encodings.h``UTF8``UTF16LE`。第二个参数是被包装的流的类型。
Milo Yip's avatar
Milo Yip committed
189 190 191 192 193 194 195 196 197

~~~~~~~~~~cpp
#include "rapidjson/document.h"
#include "rapidjson/filereadstream.h"   // FileReadStream
#include "rapidjson/encodedstream.h"    // EncodedInputStream
#include <cstdio>

using namespace rapidjson;

198
FILE* fp = fopen("utf16le.json", "rb"); // 非 Windows 平台使用 "r"
Milo Yip's avatar
Milo Yip committed
199 200 201 202

char readBuffer[256];
FileReadStream bis(fp, readBuffer, sizeof(readBuffer));

203
EncodedInputStream<UTF16LE<>, FileReadStream> eis(bis);  // 用 eis 包装 bis
Milo Yip's avatar
Milo Yip committed
204

205 206
Document d; // Document 为 GenericDocument<UTF8<> > 
d.ParseStream<0, UTF16LE<> >(eis);  // 把 UTF-16LE 文件解析至内存中的 UTF-8
Milo Yip's avatar
Milo Yip committed
207 208 209 210 211 212

fclose(fp);
~~~~~~~~~~

## EncodedOutputStream {#EncodedOutputStream}

213
`EncodedOutputStream` 也是相似的,但它的构造函数有一个 `bool putBOM` 参数,用于控制是否在输出字节流写入 BOM。
Milo Yip's avatar
Milo Yip committed
214 215 216 217 218 219

~~~~~~~~~~cpp
#include "rapidjson/filewritestream.h"  // FileWriteStream
#include "rapidjson/encodedstream.h"    // EncodedOutputStream
#include <cstdio>

220
Document d;         // Document 为 GenericDocument<UTF8<> > 
Milo Yip's avatar
Milo Yip committed
221 222
// ...

223
FILE* fp = fopen("output_utf32le.json", "wb"); // 非 Windows 平台使用 "w"
Milo Yip's avatar
Milo Yip committed
224 225 226 227 228

char writeBuffer[256];
FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer));

typedef EncodedOutputStream<UTF32LE<>, FileWriteStream> OutputStream;
229
OutputStream eos(bos, true);   // 写入 BOM
Milo Yip's avatar
Milo Yip committed
230 231

Writer<OutputStream, UTF32LE<>, UTF8<>> writer(eos);
232
d.Accept(writer);   // 这里从内存的 UTF-8 生成 UTF32-LE 文件
Milo Yip's avatar
Milo Yip committed
233 234 235 236 237 238

fclose(fp);
~~~~~~~~~~

## AutoUTFInputStream {#AutoUTFInputStream}

239
有时候,应用软件可能需要㲃理所有可支持的 JSON 编码。`AutoUTFInputStream` 会先使用 BOM 来检测编码。若 BOM 不存在,它便会使用合法 JSON 的特性来检测。若两种方法都失败,它就会倒退至构造函数提供的 UTF 类型。
Milo Yip's avatar
Milo Yip committed
240

241
由于字符(编码单元/code unit)可能是 8 位、16 位或 32 位,`AutoUTFInputStream` 需要一个能至少储存 32 位的字符类型。我们可以使用 `unsigned` 作为模板参数:
Milo Yip's avatar
Milo Yip committed
242 243 244 245 246 247 248 249 250

~~~~~~~~~~cpp
#include "rapidjson/document.h"
#include "rapidjson/filereadstream.h"   // FileReadStream
#include "rapidjson/encodedstream.h"    // AutoUTFInputStream
#include <cstdio>

using namespace rapidjson;

251
FILE* fp = fopen("any.json", "rb"); // 非 Windows 平台使用 "r"
Milo Yip's avatar
Milo Yip committed
252 253 254 255

char readBuffer[256];
FileReadStream bis(fp, readBuffer, sizeof(readBuffer));

256
AutoUTFInputStream<unsigned, FileReadStream> eis(bis);  // 用 eis 包装 bis
Milo Yip's avatar
Milo Yip committed
257

258 259
Document d;         // Document 为 GenericDocument<UTF8<> > 
d.ParseStream<0, AutoUTF<unsigned> >(eis); // 把任何 UTF 编码的文件解析至内存中的 UTF-8
Milo Yip's avatar
Milo Yip committed
260 261 262 263

fclose(fp);
~~~~~~~~~~

264
当要指定流的编码,可使用上面例子中 `ParseStream()` 的参数 `AutoUTF<CharType>`
Milo Yip's avatar
Milo Yip committed
265

266
你可以使用 `UTFType GetType()` 去获取 UTF 类型,并且用 `HasBOM()` 检测输入流是否含有 BOM。
Milo Yip's avatar
Milo Yip committed
267 268 269

## AutoUTFOutputStream {#AutoUTFOutputStream}

270
相似地,要在运行时选择输出的编码,我们可使用 `AutoUTFOutputStream`。这个类本身并非「自动」。你需要在运行时指定 UTF 类型,以及是否写入 BOM。
Milo Yip's avatar
Milo Yip committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

~~~~~~~~~~cpp
using namespace rapidjson;

void WriteJSONFile(FILE* fp, UTFType type, bool putBOM, const Document& d) {
    char writeBuffer[256];
    FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer));

    typedef AutoUTFOutputStream<unsigned, FileWriteStream> OutputStream;
    OutputStream eos(bos, type, putBOM);
    
    Writer<OutputStream, UTF8<>, AutoUTF<> > writer;
    d.Accept(writer);
}
~~~~~~~~~~

287
`AutoUTFInputStream``AutoUTFOutputStream` 是比 `EncodedInputStream``EncodedOutputStream` 方便。但前者会产生一点运行期额外开销。
Milo Yip's avatar
Milo Yip committed
288 289 290

# 自定义流 {#CustomStream}

291
除了内存/文件流,使用者可创建自行定义适配 RapidJSON API 的流类。例如,你可以创建网络流、从压缩文件读取的流等等。
Milo Yip's avatar
Milo Yip committed
292

293
RapidJSON 利用模板结合不同的类型。只要一个类包含所有所需的接口,就可以作为一个流。流的接合定义在 `rapidjson/rapidjson.h` 的注释里:
Milo Yip's avatar
Milo Yip committed
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319

~~~~~~~~~~cpp
concept Stream {
    typename Ch;    //!< 流的字符类型

    //! 从流读取当前字符,不移动读取指针(read cursor)
    Ch Peek() const;

    //! 从流读取当前字符,移动读取指针至下一字符。
    Ch Take();

    //! 获取读取指针。
    //! \return 从开始以来所读过的字符数量。
    size_t Tell();

    //! 从当前读取指针开始写入操作。
    //! \return 返回开始写入的指针。
    Ch* PutBegin();

    //! 写入一个字符。
    void Put(Ch c);

    //! 清空缓冲区。
    void Flush();

    //! 完成写作操作。
320
    //! \param begin PutBegin() 返回的开始写入指针。
Milo Yip's avatar
Milo Yip committed
321 322 323 324 325
    //! \return 已写入的字符数量。
    size_t PutEnd(Ch* begin);
}
~~~~~~~~~~

326 327 328
输入流必须实现 `Peek()``Take()``Tell()`
输出流必须实现 `Put()``Flush()`
`PutBegin()``PutEnd()` 是特殊的接口,仅用于原位(*in situ*)解析。一般的流不需实现它们。然而,即使接口不需用于某些流,仍然需要提供空实现,否则会产生编译错误。
Milo Yip's avatar
Milo Yip committed
329

330
## 例子:istream 的包装类 {#ExampleIStreamWrapper}
Milo Yip's avatar
Milo Yip committed
331

332
以下的简单例子是 `std::istream` 的包装类,它只需现 3 个函数。
Milo Yip's avatar
Milo Yip committed
333 334

~~~~~~~~~~cpp
Milo Yip's avatar
Milo Yip committed
335
class MyIStreamWrapper {
Milo Yip's avatar
Milo Yip committed
336 337 338
public:
    typedef char Ch;

Milo Yip's avatar
Milo Yip committed
339
    MyIStreamWrapper(std::istream& is) : is_(is) {
Milo Yip's avatar
Milo Yip committed
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    }

    Ch Peek() const { // 1
        int c = is_.peek();
        return c == std::char_traits<char>::eof() ? '\0' : (Ch)c;
    }

    Ch Take() { // 2
        int c = is_.get();
        return c == std::char_traits<char>::eof() ? '\0' : (Ch)c;
    }

    size_t Tell() const { return (size_t)is_.tellg(); } // 3

    Ch* PutBegin() { assert(false); return 0; }
    void Put(Ch) { assert(false); }
    void Flush() { assert(false); }
    size_t PutEnd(Ch*) { assert(false); return 0; }

private:
Milo Yip's avatar
Milo Yip committed
360 361
    MyIStreamWrapper(const MyIStreamWrapper&);
    MyIStreamWrapper& operator=(const MyIStreamWrapper&);
Milo Yip's avatar
Milo Yip committed
362 363 364 365 366

    std::istream& is_;
};
~~~~~~~~~~

367
使用者能用它来包装 `std::stringstream``std::ifstream` 的实例。
Milo Yip's avatar
Milo Yip committed
368 369 370 371

~~~~~~~~~~cpp
const char* json = "[1,2,3,4]";
std::stringstream ss(json);
Milo Yip's avatar
Milo Yip committed
372
MyIStreamWrapper is(ss);
Milo Yip's avatar
Milo Yip committed
373 374

Document d;
375
d.ParseStream(is);
Milo Yip's avatar
Milo Yip committed
376 377
~~~~~~~~~~

378
但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。
Milo Yip's avatar
Milo Yip committed
379

380
## 例子:ostream 的包装类 {#ExampleOStreamWrapper}
Milo Yip's avatar
Milo Yip committed
381

382
以下的例子是 `std::istream` 的包装类,它只需实现 2 个函数。
Milo Yip's avatar
Milo Yip committed
383 384

~~~~~~~~~~cpp
Milo Yip's avatar
Milo Yip committed
385
class MyOStreamWrapper {
Milo Yip's avatar
Milo Yip committed
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
public:
    typedef char Ch;

    OStreamWrapper(std::ostream& os) : os_(os) {
    }

    Ch Peek() const { assert(false); return '\0'; }
    Ch Take() { assert(false); return '\0'; }
    size_t Tell() const {  }

    Ch* PutBegin() { assert(false); return 0; }
    void Put(Ch c) { os_.put(c); }                  // 1
    void Flush() { os_.flush(); }                   // 2
    size_t PutEnd(Ch*) { assert(false); return 0; }

private:
Milo Yip's avatar
Milo Yip committed
402 403
    MyOStreamWrapper(const MyOStreamWrapper&);
    MyOStreamWrapper& operator=(const MyOStreamWrapper&);
Milo Yip's avatar
Milo Yip committed
404 405 406 407 408

    std::ostream& os_;
};
~~~~~~~~~~

409
使用者能用它来包装 `std::stringstream``std::ofstream` 的实例。
Milo Yip's avatar
Milo Yip committed
410 411 412 413 414 415

~~~~~~~~~~cpp
Document d;
// ...

std::stringstream ss;
Milo Yip's avatar
Milo Yip committed
416
MyOStreamWrapper os(ss);
Milo Yip's avatar
Milo Yip committed
417

Milo Yip's avatar
Milo Yip committed
418
Writer<MyOStreamWrapper> writer(os);
Milo Yip's avatar
Milo Yip committed
419 420 421
d.Accept(writer);
~~~~~~~~~~

422
但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。
Milo Yip's avatar
Milo Yip committed
423 424 425

# 总结 {#Summary}

426
本节描述了 RapidJSON 提供的各种流的类。内存流很简单。若 JSON 存储在文件中,文件流可减少 JSON 解析及生成所需的内存量。编码流在字节流和字符流之间作转换。最后,使用者可使用一个简单接口创建自定义的流。