]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
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 | ||
18 | using namespace rapidjson; | |
19 | ||
20 | // ... | |
21 | const char json[] = "[1, 2, 3, 4]"; | |
22 | StringStream s(json); | |
23 | ||
24 | Document d; | |
25 | d.ParseStream(s); | |
26 | ~~~~~~~~~~ | |
27 | ||
28 | 由于这是非常常用的用法,RapidJSON 提供 `Document::Parse(const char*)` 去做完全相同的事情: | |
29 | ||
30 | ~~~~~~~~~~cpp | |
31 | // ... | |
32 | const char json[] = "[1, 2, 3, 4]"; | |
33 | Document d; | |
34 | d.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 | ||
46 | StringBuffer buffer; | |
47 | Writer<StringBuffer> writer(buffer); | |
48 | d.Accept(writer); | |
49 | ||
50 | const char* output = buffer.GetString(); | |
51 | ~~~~~~~~~~ | |
52 | ||
53 | 当缓冲区满溢,它将自动增加容量。缺省容量是 256 个字符(UTF8 是 256 字节,UTF16 是 512 字节等)。使用者能自行提供分配器及初始容量。 | |
54 | ||
55 | ~~~~~~~~~~cpp | |
56 | StringBuffer buffer1(0, 1024); // 使用它的分配器,初始大小 = 1024 | |
57 | StringBuffer 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 | ||
78 | using namespace rapidjson; | |
79 | ||
80 | FILE* fp = fopen("big.json", "rb"); // 非 Windows 平台使用 "r" | |
81 | ||
82 | char readBuffer[65536]; | |
83 | FileReadStream is(fp, readBuffer, sizeof(readBuffer)); | |
84 | ||
85 | Document d; | |
86 | d.ParseStream(is); | |
87 | ||
88 | fclose(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 | ||
103 | using namespace rapidjson; | |
104 | ||
105 | Document d; | |
106 | d.Parse(json); | |
107 | // ... | |
108 | ||
109 | FILE* fp = fopen("output.json", "wb"); // 非 Windows 平台使用 "w" | |
110 | ||
111 | char writeBuffer[65536]; | |
112 | FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer)); | |
113 | ||
114 | Writer<FileWriteStream> writer(os); | |
115 | d.Accept(writer); | |
116 | ||
117 | fclose(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 | ||
135 | using namespace rapidjson; | |
136 | using namespace std; | |
137 | ||
138 | ifstream ifs("test.json"); | |
139 | IStreamWrapper isw(ifs); | |
140 | ||
141 | Document d; | |
142 | d.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 | ||
157 | using namespace rapidjson; | |
158 | using namespace std; | |
159 | ||
160 | Document d; | |
161 | d.Parse(json); | |
162 | ||
163 | // ... | |
164 | ||
165 | ofstream ofs("output.json"); | |
166 | OStreamWrapper osw(ofs); | |
167 | ||
168 | Writer<OStreamWrapper> writer(osw); | |
169 | d.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 | ||
196 | using namespace rapidjson; | |
197 | ||
198 | FILE* fp = fopen("utf16le.json", "rb"); // 非 Windows 平台使用 "r" | |
199 | ||
200 | char readBuffer[256]; | |
201 | FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); | |
202 | ||
203 | EncodedInputStream<UTF16LE<>, FileReadStream> eis(bis); // 用 eis 包装 bis | |
204 | ||
205 | Document d; // Document 为 GenericDocument<UTF8<> > | |
206 | d.ParseStream<0, UTF16LE<> >(eis); // 把 UTF-16LE 文件解析至内存中的 UTF-8 | |
207 | ||
208 | fclose(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 | ||
220 | Document d; // Document 为 GenericDocument<UTF8<> > | |
221 | // ... | |
222 | ||
223 | FILE* fp = fopen("output_utf32le.json", "wb"); // 非 Windows 平台使用 "w" | |
224 | ||
225 | char writeBuffer[256]; | |
226 | FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer)); | |
227 | ||
228 | typedef EncodedOutputStream<UTF32LE<>, FileWriteStream> OutputStream; | |
229 | OutputStream eos(bos, true); // 写入 BOM | |
230 | ||
231 | Writer<OutputStream, UTF32LE<>, UTF8<>> writer(eos); | |
232 | d.Accept(writer); // 这里从内存的 UTF-8 生成 UTF32-LE 文件 | |
233 | ||
234 | fclose(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 | ||
249 | using namespace rapidjson; | |
250 | ||
251 | FILE* fp = fopen("any.json", "rb"); // 非 Windows 平台使用 "r" | |
252 | ||
253 | char readBuffer[256]; | |
254 | FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); | |
255 | ||
256 | AutoUTFInputStream<unsigned, FileReadStream> eis(bis); // 用 eis 包装 bis | |
257 | ||
258 | Document d; // Document 为 GenericDocument<UTF8<> > | |
259 | d.ParseStream<0, AutoUTF<unsigned> >(eis); // 把任何 UTF 编码的文件解析至内存中的 UTF-8 | |
260 | ||
261 | fclose(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 | |
273 | using namespace rapidjson; | |
274 | ||
275 | void 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 | ||
293 | RapidJSON 利用模板结合不同的类型。只要一个类包含所有所需的接口,就可以作为一个流。流的接合定义在 `rapidjson/rapidjson.h` 的注释里: | |
294 | ||
295 | ~~~~~~~~~~cpp | |
296 | concept 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 | |
335 | class MyIStreamWrapper { | |
336 | public: | |
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 | ||
359 | private: | |
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 | |
370 | const char* json = "[1,2,3,4]"; | |
371 | std::stringstream ss(json); | |
372 | MyIStreamWrapper is(ss); | |
373 | ||
374 | Document d; | |
375 | d.ParseStream(is); | |
376 | ~~~~~~~~~~ | |
377 | ||
378 | 但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 | |
379 | ||
380 | ## 例子:ostream 的包装类 {#ExampleOStreamWrapper} | |
381 | ||
382 | 以下的例子是 `std::istream` 的包装类,它只需实现 2 个函数。 | |
383 | ||
384 | ~~~~~~~~~~cpp | |
385 | class MyOStreamWrapper { | |
386 | public: | |
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 | ||
401 | private: | |
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 | |
412 | Document d; | |
413 | // ... | |
414 | ||
415 | std::stringstream ss; | |
416 | MyOStreamWrapper os(ss); | |
417 | ||
418 | Writer<MyOStreamWrapper> writer(os); | |
419 | d.Accept(writer); | |
420 | ~~~~~~~~~~ | |
421 | ||
422 | 但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 | |
423 | ||
424 | # 总结 {#Summary} | |
425 | ||
426 | 本节描述了 RapidJSON 提供的各种流的类。内存流很简单。若 JSON 存储在文件中,文件流可减少 JSON 解析及生成所需的内存量。编码流在字节流和字符流之间作转换。最后,使用者可使用一个简单接口创建自定义的流。 |