添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 1、在TCP的连接上,它传输数据的基本形式就是 二进制流 ,也就是一段一段的1和0。

  • 2、在一般编程语言或者网络框架提供的API中,传输数据的基本形式是 字节 ,也就是Byte。一个字节就是8个二进制位,8个Bit。

    • 二进制流和字节流本质上是一样的。对于我们编写的程序来说,它需要通过网络传输的数据是结构化的数据,比如,一条命令、一段文本或者一条消息。对应代码中,这些结构化的数据都可以用一个类或者一个结构体来表示。
  • 序列化的用途除了用于在网络上传输数据以外,

  • 将结构化数据保存在文件中(将对象存储于硬盘上),因为文件内保存数据的形式也是二进制序列。

问题:
在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢?

  • 内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用,指针,并不是直接数据块。内存中的对象数据应该具有语言独特性,例如表达相同业务的User对象(id/name/age字段),Java和PHP在内存中的数据格式应该不一样的,如果直接用内存中的数据,可能会造成语言不通。只要对序列化的数据格式进行了协商,任何2个语言直接都可以进行序列化传输、接收。
  • 一个数据结构,里面存储的数据是经过非常多其他数据通过非常复杂的算法生成的,因为数据量非常大,因此生成该数据结构所用数据的时间可能要非常久,生成该数据结构后又要用作其他的计算,那么你在调试阶段,每次执行个程序,就光生成数据结构就要花上这么长的时间。假设你确定生成数据结构的算法不会变或不常变,那么就能够通过序列化技术生成数据结构数据存储到磁盘上,下次又一次执行程序时仅仅须要从磁盘上读取该对象数据就可以,所花费时间也就读一个文件的时间。
  • 虽然都是二进制的数据,但是序列化的二进制数据是通过一定的协议将数据字段进行拼接。第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言。第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来。

要想使用网络框架的API来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化。

  • 简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它依据流重构对象。这两个过程结合起来,能够轻松地存储和数据传输。
    • 比如,能够序列化一个对象,然后使用HTTP 通过 Internet 在client和server之间传输该对象。

3、序列化评价指标

  • 1、可读性
    • 序列化后的数据最好是易于人类阅读的
  • 2、实现复杂度
    • 实现的复杂度是否足够低
  • 3、性能
    • 序列化和反序列化的速度越快越好
  • 4、信息密度
    • 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好
03   | 08 7a 68 61 6e 67 73 61 6e | 17 | 01
User |    z  h  a  n  g  s  a  n  | 23 | true
  • 1.首先我们需要标识一下这个对象的类型,这里面我们用一个字节来表示类型,比如用 03 表示这是一个 User 类型的对象。
  • 2.我们约定,按照 name、age、married 这个固定顺序来序列化这三个属性。按照顺序,第一个字段是 name,我们不存字段名,直接存字段值“zhangsan”就可以了,由于名字的长度不固定,我们用第一个字节 08 表示这个名字的长度是 8
    个字节,后面的 8 个字节就是 zhangsan。
  • 3.第二个字段是年龄,我们直接用一个字节表示就可以了,23 的 16 进制是 17 。
  • 4.最后一个字段是婚姻状态,我们用一个字节来表示,01 表示已婚,00 表示未婚,这里面保存一个 01。

同样的一个User对象,JSON序列化后({"name":"zhangsan","age":"23","married":"true"})

  • JSON序列化后需要47个字节,专用的序列化方法只要12个字节就够了。
  • 专用的序列化方法显然更高效,序列化出来的字节更少,在网络传输过程中的速度也更快。但缺点是,需要为每种对象类型定义专门的序列化和反序列化方法,实现起来太复杂了,大部分情况下是不划算的。

4、序列化实例

//Srlz1.cpp: 将一个类的一个对象序列化到文件
   #include <iostream>
   #include <fcntl.h>
   #include <vector>
   #include <stdio.h>
   using namespace std;
 //定义类CA
   //数据成员:int x;
  //成员函数:Serialize:进行序列化函数
  //             Deserialize反序列化函数
  //             Show:数据成员输出函数
  class CA
      private:
          int x;  //定义一个类的数据成员。       public:
          CA()    //默认构造函数
              x = ;
          CA(int y):x(y)    //定义构造函数,用初始化列表初始化数据成员
          virtual ~CA()    //析构函数
    public:
          //序列化函数:Serialize
          //成功,返回0,失败,返回0;
          int Serialize(const char* pFilePath) const
              int isSrlzed = -;
              FILE* fp;   //define a file pointer
              //以读写方式打开文件,并判断是否打开;
         if ((fp = fopen(pFilePath, "w+")) == NULL)
                  printf("file opened failure\n");
                  return -;  //若打开失败,则返回-1;
              //调用fwrite函数,将对象写入文件;
         isSrlzed = fwrite(&x, sizeof(int), , fp);           //判断写入是否成功;
         if ((- == isSrlzed) || ( == isSrlzed))
                  printf("Serialize failure\n");
                  return -;  //若写入失败,则返回-1;
              if(fclose(fp) != ) //关闭文件
                  printf("Serialize file closed failure.\n");
                  return -;
              printf("Serialize succeed.\n");
              return ;   //序列化成功,返回0;
          }           //反序列化函数:
          //成功,返回0,失败,返回-1;
          int Deserialize(const char* pFilePath)
              int isDsrlzed = -;
              FILE* fp;
              //以读写方式打开文件,并判断是否打开;
              if ((fp = fopen(pFilePath, "r+")) == NULL)
                  printf("file opened failure.\n");
                  return -;
                 //调用fread函数,读取文件中的对象 ;
         isDsrlzed = fread(&x, sizeof(int), , fp);
         //判断是否成功读入
              if ((- == isDsrlzed)||( == isDsrlzed))
                  printf("Deserialize failure.\n");
                  return -;    //若读取失败,则返回-1;
              if(fclose(fp) != )
                  printf("Deserialize file closed failure.\n");
                  return -;
              printf("Deserialize succeed.\n");
              return ;        //反序列化成功,则返回0;
       }           //成员对象输出显示函数
          void Show()
              cout<< "in Show():"<< x << endl;
 int main(int argc, char const *argv[])
   CA as();    //定义一个类对象,并初始化;     
   //调用序列化函数,将对象as序列化到文件data.txt中;
   as.Serialize("data.txt");   
   CA ad;        //定义一个类对象,用来记录反序列化对象
   //调用反序列化函数,将文件data.txt中的对象反序列化到ad对象;
   ad.Deserialize("data.txt");    
   ad.Show();    //调用输出显示函数;    
 	return ;  

更详细的案例,简单注释:
1、CharVec.h : 定义一个 vector < char> 类型字节数组,也就是一个定义的容器,为下面的DataStream中存放数据提供接口:

#ifndef CHARVEC_H
#define CHARVEC_H 
#include <memory>
class CharVec{
public:
    CharVec();    
    CharVec(const CharVec &vec);   
    CharVec &operator =(const CharVec &vec);   
    ~CharVec();
    bool operator ==(const CharVec &vec) const;
    size_t size() const;//vector里实际存放数据的大小
    size_t capacity() const;//capacity是vector的内存分配大小
    char *begin() const;
    char *end() const;
    void push(const char *data, int len);
    void push(const std::string &str);
    void push(char c);
    void removeFromFront(int len);
    void clear();
private:
    void checkAndAlloc();
    void reallocate();
    void free();
    std::pair<char *, char *> allocAndCopy(char *begin, char *end);
private:
    char *m_Elements;   // 首元素
    char *m_FirstFree;  // 最后一个实际元素之后的位置
    char *m_Cap;        // 分配内存末尾之后的位置
    std::allocator<char> m_Allocator;  // 内存分配器
#endif // CHARVEC_H

2、CharVec.cpp :对CharVec.h 声明的函数进行定义

// CharVec.cpp
#include "CharVec.h"
CharVec::CharVec() :m_Elements(nullptr), m_FirstFree(nullptr),m_Cap(nullptr)
{}//构造函数
CharVec::CharVec(const CharVec &vec)//拷贝构造
    auto newData = allocAndCopy(vec.begin(), vec.end());//allocAndCopy分配空间,并且初始化
    m_Elements  = newData.first;
    m_FirstFree = newData.second;
    m_Cap       = newData.second;
CharVec &CharVec::operator =(const CharVec &vec)//=重载
    auto newData = allocAndCopy(vec.begin(), vec.end());
    free();
    m_Elements  = newData.first;
    m_FirstFree = newData.second;
    m_Cap       = newData.second;
    return *this;
CharVec::~CharVec()//析构
    free();
bool CharVec::operator ==(const CharVec &vec) const//==重载
    if (m_Elements == vec.m_Elements &&
            m_FirstFree == vec.m_FirstFree &&
            m_Cap == vec.m_Cap) {
        return true;
    return false;
size_t CharVec::size() const//当前元素数目
    return m_FirstFree - m_Elements;
size_t CharVec::capacity() const//容器总的空间大小
    return m_Cap - m_Elements;
char *CharVec::begin() const
    return m_Elements;
char *CharVec::end() const
    return m_FirstFree;
void CharVec::push(const char *data, int len)
    if (len <= 0) {
        return ;
    for (int i = 0; i < len; ++i) {
        push(data[i]);
void CharVec::push(const std::string &str)
    push(str.c_str(), str.size());
void CharVec::push(char c)
    checkAndAlloc();
    m_Allocator.construct(m_FirstFree++, c);
void CharVec::removeFromFront(int len)//从m_Element开始释放掉len长度的数据。
    if (len > size()) {
        return ;
    char *from = m_Elements;
    char *to = m_Elements + len;
    m_Elements += len;
    for (int i = 0; i < len; ++i) {
        m_Allocator.destroy(--to);
    m_Allocator.deallocate(from, m_Elements - from);
void CharVec::clear()//容器清空操作
    free();
    m_Elements = nullptr;
    m_FirstFree = nullptr;
    m_Cap = nullptr;
//checkAndAlloc()会先判断size是不是和capacity相等,
//然后调用reallocate进行内存的分配,重新分配的空间是原来的2倍,
//然后数据转移,使用std::move而不是拷贝可以提高效率。
void CharVec::checkAndAlloc()
    if (size() == capacity()) {
        reallocate();
void CharVec::reallocate()//类似vector的扩容操作
    auto newCapacity = size() ? 2 * size() : 1;//重新分配的空间是原来的2倍
    auto newData = m_Allocator.allocate(newCapacity);//allocate分配空间
    auto dest = newData;
    auto ele = m_Elements;
   for (size_t i = 0; i != size(); ++i) {
        m_Allocator.construct(dest++, std::move(*ele++));//construct初始构造
    free();
    m_Elements  = newData;
    m_FirstFree = dest;
    m_Cap       = m_Elements + newCapacity;
void CharVec::free()
    if (m_Elements) {
        for (auto p = m_FirstFree; p != m_Elements;) {
            m_Allocator.destroy(--p);//destroy析构对象,此时空间还是可以使用
        m_Allocator.deallocate(m_Elements, m_Cap - m_Elements);//deallocate回收空间
std::pair<char *, char *> CharVec::allocAndCopy(char *begin, char *end)
   auto startPos = m_Allocator.allocate(end - begin);
   return {startPos, std::uninitialized_copy(begin, end, startPos)};

3、DataHeader类的声明:定义id,及headerlen,totalLen相关客户属性

// DataHeader.h
#ifndef DATAHEADER_H
#define DATAHEADER_H
struct DataHeader
    DataHeader(int id = 0);
    bool operator==(const DataHeader &header);
    void reset();
    const static int s_HeaderLen = 3 * sizeof(int);
    int m_Id;
    int m_HeaderLen;
    int m_TotalLen;
#endif // DATAHEADER_H

3、DataStream.h:

  • 支持序列化和反序列化操作,
    • 枚举继承char类型意思是说这个枚举里的枚举值底层是用char来存储的
  • 支持是序列化相关的数据类型,
  • 往这个类的写数据和读数据都是提供了两种形式,
  • 流式操作符(<< 或者 >> )和函数(readVal,writeVal)
// DataStream.h
#ifndef DATASTREAM_H
#define DATASTREAM_H
#include <memory>
#include <map>
#include <list>
#include <vector>
#include <set>
#include "DataHeader.h"
#include "CharVec.h"
class CustomTypeInterface;//前向声明
class DataStream
public:
    DataStream(std::unique_ptr<DataHeader> *header = nullptr);
    DataStream(const DataStream &stream);
    DataStream& operator =(const DataStream &stream);
    enum class DataType : char {
        UnKnown,
        Boolean,
        Char,
        WChar,
        Int,
        UInt,
        Int64,
        Double,
        String,
        WString,
        Vector,
        List,
        Map,
        Set,
        CustomType,
    bool operator == (const DataStream &stream) const;
    // 指数组里存放的数据
    int totalSize() const { return m_Header->m_TotalLen; }//数据的总长
    int headerSize() const { return m_Header->m_HeaderLen; }//头部的长度
    int dataSize() const {return m_Header->m_TotalLen - m_Header->m_HeaderLen;}//内容数据的长度
    void clear();
    // write
    void writeHeader();
    void writeData(const char *data, int len);
    //这里是写入不同数据类型的数据
    DataStream& operator<<(char val);
    void writeVal(char val);
    DataStream& operator<<(wchar_t val);
    void writeVal(wchar_t val);
    DataStream& operator <<(bool val);
    void writeVal(bool val);
    DataStream& operator <<(int val);
    void writeVal(int val);
    DataStream& operator <<(unsigned int val);
    void writeVal(unsigned int val);
    DataStream& operator <<(int64_t val);
    void writeVal(int64_t val);
    DataStream& operator <<(double val);
    void writeVal(double val);
    DataStream& operator <<(const std::string &val);
    void writeVal(const std::string &val);
    DataStream& operator <<(const std::wstring &val);
    void writeVal(const std::wstring &val);
    DataStream& operator <<(CustomTypeInterface *val);
    void writeVal(CustomTypeInterface *val);
   //这里是往不同的STL容器中写入模板类型的数据
    template<typename T>
    DataStream& operator <<(const std::vector<T>& val);
    template<typename T>
    void writeVal(const std::vector<T>& val);
    template<typename T>
    DataStream& operator <<(const std::list<T>& val);
    template<typename T>
    void writeVal(const std::list<T>& val);
    template<typename T1, typename T2>
    DataStream& operator <<(const std::map<T1, T2>& val);
    template<typename T1, typename T2>
    void writeVal(const std::map<T1, T2>& val);
    template<typename T>
    DataStream& operator <<(const std::set<T>& val);
    template<typename T>
    void writeVal(const std::set<T>& val);
    // read
    void readHeader(const char *data);
    template<typename T>
    bool readData(T *val);
    bool operator>>(char &val);
    bool readVal(char &val);
    bool operator>>(wchar_t& val);
    bool readVal(wchar_t &val);
    bool operator>>(bool &val);
    bool readVal(bool &val);
    bool operator>>(int &val);
    bool readVal(int &val);
    bool operator>>(unsigned int &val);
    bool readVal(unsigned int &val);
    bool operator>>(int64_t &val);
    bool readVal(int64_t &val);
    bool operator>>(double &val);
    bool readVal(double &val);
    bool operator>>(std::string &val);
    bool readVal(std::string &val);
    bool operator>>(std::wstring &val);
    bool readVal(std::wstring &val);
    bool operator>>(CustomTypeInterface *val);
    bool readVal(CustomTypeInterface *val);
    template<typename T>
    bool operator>>(std::vector<T> &val);
    template<typename T>
    bool readVal(std::vector<T> &val);
    template<typename T>
    bool operator>>(std::list<T> &val);
    template<typename T>
    bool readVal(std::list<T> &val);
    template<typename T1, typename T2>
    bool operator>>(std::map<T1, T2> &val);
    template<typename T1, typename T2>
    bool readVal(std::map<T1, T2> &val);
    template<typename T>
    bool operator>>(std::set<T> &val);
    template<typename T>
    bool readVal(std::set<T> &val);
    // Serialize and Deserialize
    int Serialize(char *buf) const;
    bool Deserialize(const char *buf, int len);
private:
    std::unique_ptr<DataHeader> m_Header;//存储的客户类型指针
    CharVec  m_DataBuffer;//存储的容器
    int  m_IsFirstWrite;//判断是否为第一次写入

4、DataStream.cpp:DataStream.h文件相关函数的实现

#include "DataStream.h"
#include "CustomTypeInterface.h"
DataStream::DataStream(std::unique_ptr<DataHeader> *header) :
    m_IsFirstWrite(true)//构造函数的实现
    if (header == nullptr) {//header对象为空指针,重置新的对象指针
        m_Header.reset(new DataHeader);
    else {
        m_Header.reset(header->release());//release()释放关联的原始指针,unique_ptr相关的函数
DataStream::DataStream(const DataStream &stream)//拷贝构造
    operator =(stream);
DataStream &DataStream::operator =(const DataStream &stream)//=重载
    if (&stream == this) {//比较对象和原对象相同,没有赋值的必要了
        return *this;
    m_Header.reset(new DataHeader);//重载并且初始化
    *m_Header = *stream.m_Header;//相关赋值操作
    m_DataBuffer = stream.m_DataBuffer;
    m_IsFirstWrite = stream.m_IsFirstWrite;
    return *this;
bool DataStream::operator ==(const DataStream &stream) const//==重载
    if (&stream == this) {
        return true;
    if (m_Header.get() == stream.m_Header.get() &&
            m_DataBuffer == stream.m_DataBuffer) {
        return true;
    return false;
void DataStream::clear()
    m_IsFirstWrite = true;
    m_DataBuffer.clear();
    m_Header->reset();
void DataStream::writeHeader()
    int headerLen = DataHeader::s_HeaderLen;
    writeData((char *)&(m_Header->m_TotalLen), sizeof(int));
    writeData((char *)&headerLen, sizeof(int));
    writeData((char *)&m_Header->m_Id, sizeof(int));
    m_Header->m_HeaderLen = headerLen;
void DataStream::writeData(const char *data, int len)
    if (len == 0) {
        return ;
    //把他的type写入,
    //如果是第一写入,先把header写入, 然后再写数据,更新totalLen
   if (m_IsFirstWrite) {
        m_IsFirstWrite = false;
        writeHeader();
    //然后在把数据写入
    m_DataBuffer.push(data, len);
    m_Header->m_TotalLen += len;//更新totalLen
    memcpy(m_DataBuffer.begin(), &m_Header->m_TotalLen, sizeof(int));
void DataStream::writeVal(char val)
    char type = (char)DataType::Char;
    writeData((char *)&(type), sizeof(char));
    writeData(&val, sizeof(char));
void DataStream::writeVal(const std::string &val)
    char type = (char)DataType::String;
    writeData((char *)&(type), sizeof(char));
    int size = val.size();
    writeVal(size);
    writeData(val.c_str(), size);
void DataStream::writeVal(CustomTypeInterface *val)
    val->serialize(*this, (char)DataType::CustomType);
void DataStream::readHeader(const char *data)
    int *p = (int *)data;
    m_Header->m_TotalLen  = *p++;
    m_Header->m_HeaderLen = *p++;
    m_Header->m_Id        = *p++;
    m_Header->m_TotalLen -= m_Header->m_HeaderLen;
    m_Header->m_HeaderLen = 0;
//从dataBuffer的数据取出来,然后更新totalLen.
//由于这个函数是模板函数,所以我们把他放在了头文件。
/*template<typename T>
bool DataStream::readData(T *val)
    int size = m_DataBuffer.size();
    int count = sizeof(T);
    if (size < count) {
       return false;
    *val = *((T*)m_DataBuffer.begin());
    m_DataBuffer.removeFromFront(count);
    m_Header->m_TotalLen -= count;
    return true;
//先读取出来类型,然后读取数据
bool DataStream::readVal(char &val)
    char type = 0;
    if (readData(&type) && type == (char)DataType::Char) {
        return readData(&val);
   return false;
bool DataStream::readVal(std::string &val)
    char type = 0;
    if (readData(&type) && type == (char)DataType::String) {
        int len = 0;
        if (readVal(len) && len > 0) {
            val.assign(m_DataBuffer.begin(), len);
            m_DataBuffer.removeFromFront(len);
            m_Header->m_TotalLen -= len;
       return true;
    return false;
bool DataStream::readVal(CustomTypeInterface *val)
    return val->deserialize(*this, (char)DataType::CustomType);
int DataStream::Serialize(char *buf) const//序列化
    int totalLen = m_Header->m_TotalLen;
    int size = m_DataBuffer.size();
    if (size <= 0 || totalLen == 0 || size != totalLen) {
        return 0;
    memcpy(buf, m_DataBuffer.begin(), totalLen);
    return totalLen;
bool DataStream::Deserialize(const char *buf, int len)//反序列化
    if (buf == nullptr || len <= 0) {
        return false;
   readHeader(buf);
   m_DataBuffer.clear();
   m_DataBuffer.push(buf + DataHeader::s_HeaderLen, len - DataHeader::s_HeaderLen);
    return true;

5、CustomTypeInterface:自定义类型,比如你自己定义了一个结构体,怎样传输它呢。我们为自定义的结构体定义一个接口类。

class CustomTypeInterface
public:
    virtual ~CustomTypeInterface() = default;
    virtual void serialize(DataStream &stream, char type) const = 0;
    virtual bool deserialize(DataStream &stream, char type) = 0;
#include <iostream>
#include "DataStream.h"
#include "CustomTypeInterface.h"
class Test : public CustomTypeInterface
public:
    SerializeAndDeserialize(Test, m_A * m_B);
public:
    int  m_A;
    bool m_B;
int main(int argc, char *argv[])
    char c1 = 'c';
    Test t;
    t.m_A = 1;
    t.m_B = false;
    DataStream stream;
    stream.writeVal(c1);
    stream.writeVal(&t);
    int size = stream.totalSize();
    char *data = new char[size];
    stream.Serialize(data); 
    DataStream stream2;
    stream2.Deserialize(data, size);
    char c2;
    Test t2;
    stream2.readVal(c2);
    stream2.readVal(&t2);
    std::cout << c2 << t2.m_A << t2.m_B;
    return 0;

在这里插入图片描述
代码地址:https://download.csdn.net/download/leapmotion/10762437

1、 https://www.cnblogs.com/chjxbt/p/11458815.html
2、 https://www.bbsmax.com/A/kmzLo1jYdG/
3、https://blog.csdn.net/leapmotion/article/details/83687517

    个人感觉序列化简单来说就是按一定规则组包。反序列化就是按组包时的规则来接包。正常来说。序列化不会很难。不会很复杂。因为过于复杂的序列化协议会导致较长的解析时间,这可能会使得序列化反序列化阶段成为整个系统的瓶颈。就像压缩文件、解压文件,会占用大量cpu时间。     所以正常的序列化会在时间和空间上考虑。个人感觉对于电商业务时间应该是相对重要些。毕竟用户没有那么多...
C++应用程序中,经常会涉及到对一些数据进行序列化反序列化的处理。序列化可以将一个对象转换为一串字节流,这样就可以将其存储在硬盘上或者通过网络传输到其他设备上。而反序列化则是将这些字节流解析成原始的对象。 在Qt中,数据的序列化反序列化可以使用QDataStream类来完成。QDataStream是一个方便的Qt类,它可以将基本数据类型、Qt数据类型以及用户定义的数据类型都进行序列化反序列化。在使用QDataStream进行序列化时,需要指定一个QIODevice类的子类(例如QF
程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯。这些过程将会涉及到程序数据转化成能被存储并传输的格式,因此被称为“序列化”(Serialization),而它的逆过程则可被称为“反序列化” (Deserialization) 简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它根据流重构对象。这两个过程结合起来,可以轻松地存储和传输数据。例如,可以序列化一个对象,然后使用 http://stackoverflow.com/questions/3637581/fastest-c-serialization Boost: Fast, assorted C++ library including serialization protobuf: Fast cross-platform, cross-language serialization wit...
Scrapy是一个强大的Python爬虫框架,用于快速、高效地从网页中提取数据。下面是一个使用Scrapy的爬虫案例。 假设目标是从一个电子商务网站上获取商品的名称、价格和评论。首先,需要创建一个Scrapy项目。在命令行中输入以下命令: scrapy startproject ecommerce 会生成一个名为ecommerce的文件夹,其中包含Scrapy项目的基本结构。接下来,进入项目文件夹并创建一个爬虫: cd ecommerce scrapy genspider products example.com 这里创建了一个名为products的爬虫,爬取的网站是example.com。进入爬虫文件`products.py`,可以看到Scrapy自动生成的代码。在`parse`方法中,可以编写代码来提取需要的数据。 首先,需要确定要提取数据的网页结构。使用Chrome浏览器的开发者工具,可以查看网页的HTML结构。根据需要提取的数据,可以使用XPath或CSS选择器来定位元素。比如,要提取商品的名称和价格,可以使用以下代码: def parse(self, response): products = response.xpath('//div[@class="product"]') for product in products: name = product.xpath('.//h2/text()').get() price = product.xpath('.//span[@class="price"]/text()').get() yield { 'name': name, 'price': price 这个代码会在网页中查找所有`class`属性为`product`的`div`元素,并提取每个商品的名称和价格。 接下来,需要配置爬虫的URL和启动设置。在项目文件夹中的`settings.py`文件中,可以设置爬虫的起始URL和其他的配置参数。 最后,运行爬虫并保存数据。在命令行中输入以下命令: scrapy crawl products -o data.json 这个命令会运行名为products的爬虫,并将提取的数据保存在data.json文件中。 以上就是一个使用Scrapy爬虫框架的简单案例。通过编写适当的代码,可以根据需要从网页中提取所需的数据。Scrapy提供了丰富的功能和灵活的配置选项,使得爬虫开发变得更加容易和高效。
Linux下的I/O复用技术 — epoll如何使用(epoll_create、epoll_ctl、epoll_wait) 以及 LT/ET 使用过程解析 weixin_42783087: 但是它这里是混着的,F_SETFL F_GETFD,实际应该是 GETFL Linux下的I/O复用技术 — epoll如何使用(epoll_create、epoll_ctl、epoll_wait) 以及 LT/ET 使用过程解析 m0_70654327: 设置对应套接字为非阻塞的 C++ const关键字的总结(全局/局部变量、修饰指针和引用、成员函数和数据成员、修饰类对象、const与宏定义的区别、Static与Const的区别) 2301_79503623: