rapidjson/doc/stream.zh-cn.md
2016-02-08 00:49:00 +08:00

14 KiB
Raw Blame History

在RapidJSON中rapidjson::Stream是用於读写JSON的概念概念是指C++的concept。在这里我们先介绍如何使用RapidJSON提供的各种流。然后再看看如何自行定义流。

[TOC]

内存流

内存流把JSON存储在内存之中。

StringStream输入

StringStream是最基本的输入流它表示一个完整的、只读的、存储于内存的JSON。它在rapidjson/rapidjson.h中定义。

#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);

由于这是非常常用的用法RapidJSON提供Document::Parse(const char*)去做完全相同的事情:

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

需要注意,StringStreamGenericStringStream<UTF8<> >的typedef使用者可用其他编码类去代表流所使用的字符集。

StringBuffer输出

StringBuffer是一个简单的输出流。它分配一个内存缓冲区供写入整个JSON。可使用GetString()来获取该缓冲区。

#include "rapidjson/stringbuffer.h"

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

const char* output = buffer.GetString();

当缓冲区满溢它将自动增加容量。缺省容量是256个字符UTF8是256字节UTF16是512字节等。使用者能自行提供分配器及初始容量。

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

如无设置分配器,StringBuffer会自行实例化一个内部分配器。

相似地,StringBufferGenericStringBuffer<UTF8<> >的typedef。

文件流

当要从文件解析一个JSON你可以把整个JSON读入内存并使用上述的StringStream

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

FileReadStream输入

FileReadStream通过FILE指针读取文件。使用者需要提供一个缓冲区。

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

using namespace rapidjson;

FILE* fp = fopen("big.json", "rb"); // 非Windows平台使用"r"

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

Document d;
d.ParseStream(is);

fclose(fp);

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

除了读取文件,使用者也可以使用FileReadStream来读取stdin

FileWriteStream输出

FileWriteStream是一个含缓冲功能的输出流。它的用法与FileReadStream非常相似。

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

using namespace rapidjson;

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

FILE* fp = fopen("output.json", "wb"); // 非Windows平台使用"w"

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

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

fclose(fp);

它也可以把输出导向stdout

iostream 包装类

基于用户的要求RapidJSON提供了正式的 std::basic_istreamstd::basic_ostream 包装类。然而,请注意其性能会大大低于以上的其他流。

IStreamWrapper

IStreamWrapper 把任何继承自 std::istream 的类(如 std::istringstreamstd::stringstreamstd::ifstreamstd::fstream)包装成 RapidJSON 的输入流。

#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 把任何继承自 std::ostream 的类(如 std::ostringstreamstd::stringstreamstd::ofstreamstd::fstream)包装成 RapidJSON 的输出流。

#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

编码流

编码流encoded streams本身不存储JSON它们是通过包装字节流来提供基本的编码解码功能。

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

除此以外,我们也需要处理字节顺序标记byte order mark, BOM。当从一个字节流读取时需要检测BOM或者仅仅是把存在的BOM消去。当把JSON写入字节流时也可选择写入BOM。

若一个流的编码在编译期已知,你可使用EncodedInputStreamEncodedOutputStream。若一个流可能存储UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE的JSON并且编码只能在运行时得知你便可以使用AutoUTFInputStreamAutoUTFOutputStream。这些流定义在rapidjson/encodedstream.h

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

EncodedInputStream

EncodedInputStream含两个模板参数。第一个是Encoding类型,例如定义于rapidjson/encodings.hUTF8UTF16LE。第二个参数是被包装的流的类型。

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

using namespace rapidjson;

FILE* fp = fopen("utf16le.json", "rb"); // 非Windows平台使用"r"

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

EncodedInputStream<UTF16LE<>, FileReadStream> eis(bis);  // 用eis包装bis

Document d; // Document为GenericDocument<UTF8<> > 
d.ParseStream<0, UTF16LE<> >(eis);  // 把UTF-16LE文件解析至内存中的UTF-8

fclose(fp);

EncodedOutputStream

EncodedOutputStream也是相似的,但它的构造函数有一个bool putBOM参数用于控制是否在输出字节流写入BOM。

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

Document d;         // Document为GenericDocument<UTF8<> > 
// ...

FILE* fp = fopen("output_utf32le.json", "wb"); // 非Windows平台使用"w"

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

typedef EncodedOutputStream<UTF32LE<>, FileWriteStream> OutputStream;
OutputStream eos(bos, true);   // 写入BOM

Writer<OutputStream, UTF32LE<>, UTF8<>> writer(eos);
d.Accept(writer);   // 这里从内存的UTF-8生成UTF32-LE文件

fclose(fp);

AutoUTFInputStream

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

由于字符编码单元code unit可能是8位、16位或32位AutoUTFInputStream 需要一个能至少储存32位的字符类型。我们可以使用unsigned作为模板参数:

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

using namespace rapidjson;

FILE* fp = fopen("any.json", "rb"); // 非Windows平台使用"r"

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

AutoUTFInputStream<unsigned, FileReadStream> eis(bis);  // 用eis包装bis

Document d;         // Document为GenericDocument<UTF8<> > 
d.ParseStream<0, AutoUTF<unsigned> >(eis); // 把任何UTF编码的文件解析至内存中的UTF-8

fclose(fp);

当要指定流的编码,可使用上面例子中ParseStream()的参数AutoUTF<CharType>

你可以使用UTFType GetType()去获取UTF类型并且用HasBOM()检测输入流是否含有BOM。

AutoUTFOutputStream

相似地,要在运行时选择输出的编码,我们可使用AutoUTFOutputStream。这个类本身并非「自动」。你需要在运行时指定UTF类型以及是否写入BOM。

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);
}

AutoUTFInputStreamAutoUTFOutputStream是比EncodedInputStreamEncodedOutputStream方便。但前者会产生一点运行期额外开销。

自定义流

除了内存文件流使用者可创建自行定义适配RapidJSON API的流类。例如你可以创建网络流、从压缩文件读取的流等等。

RapidJSON利用模板结合不同的类型。只要一个类包含所有所需的接口就可以作为一个流。流的接合定义在rapidjson/rapidjson.h的注释里:

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

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

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

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

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

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

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

    //! 完成写作操作。
    //! \param begin PutBegin()返回的开始写入指针。
    //! \return 已写入的字符数量。
    size_t PutEnd(Ch* begin);
}

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

例子istream的包装类

以下的简单例子是std::istream的包装类它只需现3个函数。

class MyIStreamWrapper {
public:
    typedef char Ch;

    MyIStreamWrapper(std::istream& is) : is_(is) {
    }

    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:
    MyIStreamWrapper(const MyIStreamWrapper&);
    MyIStreamWrapper& operator=(const MyIStreamWrapper&);

    std::istream& is_;
};

使用者能用它来包装std::stringstreamstd::ifstream的实例。

const char* json = "[1,2,3,4]";
std::stringstream ss(json);
MyIStreamWrapper is(ss);

Document d;
d.ParseStream(is);

但要注意由于标准库的内部开销问此实现的性能可能不如RapidJSON的内存文件流。

例子ostream的包装类

以下的例子是std::istream的包装类它只需实现2个函数。

class MyOStreamWrapper {
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:
    MyOStreamWrapper(const MyOStreamWrapper&);
    MyOStreamWrapper& operator=(const MyOStreamWrapper&);

    std::ostream& os_;
};

使用者能用它来包装std::stringstreamstd::ofstream的实例。

Document d;
// ...

std::stringstream ss;
MyOStreamWrapper os(ss);

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

但要注意由于标准库的内部开销问此实现的性能可能不如RapidJSON的内存文件流。

总结

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