Boost搜索引擎的实现

目录

  • Boost搜索引擎项目
    • 1.项目的相关背景
    • 2.搜索引擎的相关宏观原理
    • 3.搜索引擎技术栈和项目环境
    • 4.正排索引vs倒排索引 -搜索引擎具体原理
          • 正排索引:就是从文档ID找到文档内容(文档内的关键字)
          • 倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案
    • 5.编写数据去标签与数据清洗的模块 Parser(分析器)
        • 理解什么是标签,以及去标签的目标
      • 编写Parser
        • boost开发库的安装
        • EnumFile的实现
        • Parser File的实现
          • 打开对应的文件
          • 提取title
          • 提取content,本质就是去标签
          • 构建url
          • Parser File实现代码
        • Save Data的实现
    • 6.编写建立索引的模块 Index
      • Index的基本代码结构
        • Build Index的实现
          • 正排索引函数的实现
          • 倒排索引的原理
          • jieba分词的使用
          • 倒排索引的实现
    • 7.编写搜索引擎模块Searcher
      • InitSearcher函数的实现
        • index改为单例模式
      • Search函数的实现
        • 摘要函数GetDesc的实现
    • 8.编写http server模块
      • 升级gcc
      • 安装cpp-httplib
      • 使用cpp-httplib
    • 9.编写前端模块
      • 编写前端代码
        • 了解html css js
        • 编写html
        • 编写css
        • 编写js
    • 10.细节优化
      • 文档重复显示问题
      • 添加一些日志信息
      • 添加去暂停词代码
    • 11.将其部署到云服务器上
      • 部署

Boost搜索引擎项目

1.项目的相关背景

  • 这里是基于boost文档所实现的站内搜索引擎,站内搜索它的数据更垂直,数据量更小
  • 一般搜索引擎搜索出来的相关数据一般含有:网页标签(title),网页内容的概要,即将要跳转的网页URL
  • Boost的官方库中是没有站内搜索功能的

2.搜索引擎的相关宏观原理

Boost搜索引擎的实现_第1张图片

3.搜索引擎技术栈和项目环境

  • 技术栈:C/C++,STL,准标准库,Boost库,Jsoncpp(客户端和服务器端进行交互的时候所需要进行序列化和反序列化),cppjieba(给搜索关键字进行分词),cpp-httplib(一个开源的http的开源库,可以直接构建http服务器) html5,css,js,jQuery,Ajax
  • 项目环境:Centos 7云服务器vim/g++/Makefile,vscode/vs2019

4.正排索引vs倒排索引 -搜索引擎具体原理

正排索引:就是从文档ID找到文档内容(文档内的关键字)
文档ID 文档内容
1 新海诚上映了新电影
2 新海诚的新电影是铃芽户缔

对目标文档进行分词(目的:方便建立倒排索引和查找):

  • 文档1:新海诚/上映/了新电影
  • 文档2:新海诚/的新电影/是/铃芽户缔

停止词:了,的,吗,a,the,一般我们在分词的时候可以不考虑

倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案
关键字(具有唯一性) 文档ID,weight(权重高的在前)
新海诚 文档1,2
上映 文档1
新电影 文档1,2
铃芽户缔 文档2

这样就可以模拟一次查找的过程:

用户输入:新海诚-->倒排索引中查找-->找到之后提取出文档ID(1,2)-->根据正排索引--->找到文档内容 -->title+conent(desc) +url 文档结果进行摘要--->构建响应结果

5.编写数据去标签与数据清洗的模块 Parser(分析器)

boost官网:https://www.boost.org/
//目前只需要boost_1_81_0/doc/html目录下的html文件,用它来建立索引

在这里插入图片描述
之所以选doc/html文件也是因为官网之中绝大部分使用的都是这里面的文件

理解什么是标签,以及去标签的目标
[xifeng@VM-16-14-centos MyBoostSearch]$ touch parser.cc
//想要做数据处理就需要有原始数据-->去标签之后的数据
//标签也就是<>之中包含的,标签对搜索没有价值,需要被去除
//一般标签都是成对出现的

Boost搜索引擎的实现_第2张图片

//html放的是原始文档
[xifeng@VM-16-14-centos data]$ html
[xifeng@VM-16-14-centos data]$ purify_html
//去除掉标签之后的html我们保存在puritf_html中(puritf提纯/去除)
[xifeng@VM-16-14-centos html]$ ls -Rl | grep -E '*.html' | wc -l
8429 //一共有8429个html文件
目标: 把每个文档都去标签,然后写入到同一个文件中,文档内容不需要换行(\n),文档和文档之间用\3区分
类似于:xxxxxxx\3xxxxxxxxx\3xxxxxxxxxxxx\3 但是这种方法虽然可行,可是后续处理的时候会比较麻烦,因为还需要去区分title,content,url的内容
---------------------------------------------------
所以我选择的是这种操作:title\3content\3url \n title\3content\3url \n....
就是同一个文档之间不同的数据之间采用\3来区分,文档文档之间采用\n来区分,这样既可以通过getline获取到一个文档的全部信息,又方便区分文档中的不同信息

Boost搜索引擎的实现_第3张图片

之所以选择\3也是有一定的理由的:首先是因为它是控制字符,不会显示到文件中,其次就是\3也有正文结束的意思

编写Parser

  • 首先在purify_html中创建了一个文本文件purify.txt用来保存清洗之后的数据

主要有三大块:

  1. 首先要将所有的文件给拿到,可以将带路径的文件名存放到一个数组中(Enum File通过这个函数来枚举各个文件的路径)

  2. 其次就是对文件进行读取和解析(Parser File通过这个函数来解析)

    • 解析数据要解析成什么样子的呢

    • 可以定义一个结构体

      typedef struct Docinfo
      {
      	std::string title;//标签
          std::string content;//内容
          std::string url;//官网所对应的url
      }Docinfo_t;
      
  3. 最后就是将解析到的数据存放在purity.txt中(Save Data通过这个函数来存储数据)

//代码的大致框架
#include
#include
#include
#include
const std::string src_path = "./data/html";
const std::string save_path= "./data/purify_html/purify.txt";
typedef struct Docinfo
{
  std::string title;//文档的标签
  std::string content;//文件的内容
  std::string url; //文件对应官网中的url
}Docinfo_t;
//注意:输入型参数我使用:const &
//输出型参数:*
//输入输出型参数:&
bool EnumFile(const std::string& src_path,std::vector* file_list);
bool ParserFile(const std::vector& file_list,std::vector* data_list);
bool SaveData(const std::string& save_path,const std::vector& data_list);
int main()
{
  //第一步:获取到文件中的所有的html路径,并将其存放到一个数组当中
  std::vector file_list;//文件列表
  if(!EnumFile(src_path,&file_list))
  {
    //如果遍历失败,后续就没有意义了,直接退出
    std::cerr<<"EnumFile error"< data_list;//数据列表
  if(!ParserFile(file_list,&data_list))
  {
    //同样解析失败也就是退出
    std::cerr<<"ParserFile error"<
boost开发库的安装

sudo yum install -y boost-devel,我安装的是1.53版本的boost库,这意味的是用1.53的库去写代码,我搜索的文档还是1.81的文档,这两个并不冲突

EnumFile的实现
  • 首先需要通过boost库了解一些相关的接口,这里对文件的操作使用的是boost库中提供的filesystem(头文件:)

  • 在官方文档的使用样例中,对于命名空间这一块他使用的是namespace fs = boost::filesystem;,所以在我的代码中也就不全局放开了,与文档保持一致,在使用的时候通过域作用限定符去写,不去无脑放开还是很有好处的,能很大程度的减少接口上的冲突

    //一下是需要了解的部分类或者接口
    //path类里面定义了一个root_path
    path root_path() const;
    Returns: root_name() / root_directory()
    -----------------------------------------
    path operator/ (const path& lhs, const path& rhs);
    Returns: path(lhs) /= rhs.
    // /=是被重载了的,作用:将优选目录分隔符附加到包含的路径名,除非:
    //一个添加的分离器将是多余的,或者会将相对路径更改为绝对路径,或P.Empty()或*p.native()。cbegin()是目录分离器。
    //root_name() 如果包含根名则返回根名
    //root_directory() 如果包含根目录返回根目录 
    ------------------------------------------
    //exists用来判断文件是否存在
    bool exists(const path& p);
    //递归遍历
    Class recursive_directory_iterator
    //类型的对象提供标准库 对目录内容进行合规迭代,包括递归到其子目录
    //进行迭代的时候需要考虑要遍历的文件必须是普通文件,必须是.html的,所以这就需要对文件名进行筛选
    //判断是否是常规文件
    bool is_regular_file(const path& p);
    //用来判断后缀的是path类内部的一个成员函数
    path extension(const path& p) const;
    //可以看一下文档举的例子
    std::cout << path("/foo/bar.txt").extension(); // outputs ".txt"
    //这里可以发现调用返回的是".txt",这样就可以进行判断
    //当判断完之后我们就需要将其push到存放地址的数组中去,path类中提供了一个string方法
    template 
    String string(const codecvt_type& cvt=codecvt()) const;
    //返回的是string类型的路径
    

EnumFile具体的实现:

#Makefile编写时,如果不加 -lboost_system和-lboost_filesystem会报错的
#因为boost不是标准库,第三方库链接时需要指明库名称
cc=g++
parser:parser.cc
  $(cc) -o $@ $^ -std=c++11 -lboost_system -lboost_filesystem            .PHONY:clean                                                  
clean:
  rm -rf parser
//作用类似于typedef,跟文档样例保持一致,写代码时最好不要遇到命名空间就全部展开
namespace fs = boost::filesystem;
bool EnumFile(const std::string& src_path,std::vector* file_list)
{
  //boost库中定义的成员,简单理解就是将我们传入的字符串转换成boost能够识别的路径
  fs::path root_path(src_path);
  if(!exists(root_path))
  {
    //代表目标文件不存在
    return false;
  }
  //到这里就是已经找到了目标文件,可以进行迭代查找了
  fs::recursive_directory_iterator end;//定义一个结束的迭代器
  //构建一个从root_path开始的迭代器
  for(fs::recursive_directory_iterator it(root_path);it!= end;++it)
  {
    //要查找首先得是一个普通文件
    if(!fs::is_regular_file(*it))
    {
      continue;
    }
    //其次需要的是.html后缀的文件
    if(!(it->path().extension()==".html"))
    {
      continue;
    }
    //到这里就是后缀名为.html的普通文件了,将其存放进vector即可
    //如果直接push_back(*it)是不正确的,因为那个已经不是string类型了
    //我们可以通过boost提供的string接口来将其转化一下
    file_list->push_back((it->path()).string());
  }
  return true;
}
Parser File的实现

总体思路:首先已经拿到了所有的带路径的文件名,接下来要做的就是对这些文件进行遍历,打开每一个文件进行读写,将其解析成Docinfo_t这样的结构将其插入到vector中

打开对应的文件
  • 首先需要了解几个相关函数,在c++中对于文件操作的头文件是#include
  • ifstream是输入流,其构造函数explicit ifstream (const char* filename, ios_base::openmode mode = ios_base::in); 注意一下ios和ios_base没有区别,都可以用
  • open和close分别对应着打开文件和关闭文件;void open (const char* filename, ios_base::openmode mode = ios_base::in); void close();
  • is_open是判断文件是否被打开bool is_open();
  • getline是按行读取,需要将读到的字符拼接成一个长字符串istream& getline (istream& is, string& str);
//以下是具体代码
//这里对c++提供的接口进行了简单的封装
bool Open(const std::string &file_path, std::string *out)
{
     std::ifstream in(file_path, std::ios_base::in);
     if (!in.is_open()) // 如果文件没有被打开则返回false
     {
        // 文件如果打开失败打印失败的文件名
        std::cerr << "Open file error:" << file_path << std::endl;
        return false;
     }
    //文件能够被成功打开
    std::string str;
    //理解getline读取到文件结尾:getline的返回值是一个&,while判断的是一个bool类型,本质就是返回的类型中重载了强制类型转换
    while(std::getline(in,str))//按行读,最后拼接在一起
   	{
       *out +=str;
    }
    in.close();
   	return true;
}
提取title

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADIrHGnD-1679760493442)(C:/Users/yangyr0206/AppData/Roaming/Typora/typora-user-images/image-20230314143742011.png)]

  • 通过STL容器string提供的find函数可以去查找字符串""和"</titile>"</code></p> <pre><code class="prism language-c++">size_t find (const string& str, size_t pos = 0) const; //如果找不到就会返回std::npos; //找到就返回找到的第一个字符对应的位置下标,比如<title>找到就返回<的下标 </code></pre> </li> <li> <p>通过substr去获取中间的片段</p> <pre><code class="prism language-c++">string substr (size_t pos = 0, size_t len = npos) const; //pos是从什么位置开始,len是取多长 </code></pre> </li> </ul> <pre><code class="prism language-c++">//实现 bool ParserTitle(const std::string &result, std::string *title) { // 要提取的就是<title>之间的数据 size_t begin = result.find(""); size_t end = result.find(""); if (begin == std::string::npos || end == std::string::npos || begin > end) { std::cerr << "find error" << std::endl; return false; } // 两个都找到了,且begin < end begin += 7;//为了跳过 *title = result.substr(begin, end - begin); // 左闭右开区间 return true; } </code></pre> <h6>提取content,本质就是去标签</h6> <p>在进行遍历时,无论是单标签还是双标签,只要碰到了<code>></code>,就意味着,当前的标签被处理完毕;只要读到了<code><</code>意味着新的标签开始了</p> <pre><code class="prism language-c++">//实现 bool ParserContent(const std::string &result, std::string *content) { enum Status // 状态机的状态码 { START, // 开始 OVER // 结束 }; // 去标签,当遇到>我们认为一个标签结束了,遇到<表示一个标签刚刚开始 // 由此可以设置一个简单的状态机 enum Status st = START; // 默认是标签的开始 for (char c : result) { switch (st) { case START: if (c == '>') st = OVER; break; case OVER: if (c == '<') st = START; else // 正文内容 { if (c == '\n') c = ' '; content->push_back(c); } break; default: break; } } return true; } </code></pre> <h6>构建url</h6> <p>boost库的官方文档,和我们下载下来的文档,是有路径的对应关系的</p> <pre><code class="prism language-url">官网的URL样例:https://www.boost.org/doc/libs/1_81_0/doc/html/accumulators.html 下载下来的URL样例:boost_1_81_0/doc/html/accumulators.html 我项目中的URL样例:MyBoostSearch/data/html/accumulators.html //本质就是我将下载下来doc/html/* cp data/html/ ---------------------------------------------------------------- url_head = "https://www.boost.org/doc/libs/1_81_0/doc/html"; url_tail = [data/html](delete) /accumulators.html--->"/accumulators.html"; url = url_head + url_tail;//相当于形成了一个官网链接 </code></pre> <pre><code class="prism language-c++">const std::string head_url = "https://www.boost.org/doc/libs/1_81_0/doc/html"; bool ParserUrl(const std::string &head_url, const std::string &file, std::string *url) { // 就是进行平接只需要把我的路径中的./data/html-->也就是之前定义的src_path去除与head_url拼接即可 std::string tail_url = file.substr(src_path.size()); *url = head_url + tail_url; return true; } </code></pre> <h6>Parser File实现代码</h6> <pre><code class="prism language-c++">bool ParserFile(const std::vector<std::string> &file_list, std::vector<Docinfo_t> *data_list) { for (const std::string &file : file_list) { // 1.打开文件 // result(结果)用来存放文件读出来的数据 std::string result; if (!Tool::Open(file, &result)) { continue; } // 2.解析成Docinfo_t类型的数据 // 提取标签,提取内容,拼接url Docinfo_t doc; // 获取标签 if (!ParserTitle(result, &doc.title)) { continue; } // 获取内容 if (!ParserContent(result, &doc.content)) { continue; } const std::string head_url = "https://www.boost.org/doc/libs/1_81_0/doc/html"; // 拼接url if (!ParserUrl(head_url, file, &doc.url)) { continue; } //move函数主要作用就是数据的移动,也就是说moved-from对象处于有效但未指定的状态。这意味着,在这样的操作之后,移出对象的值只应被销毁或分配一个新值;否则,访问它会生成未指定的值。在这里就是doc里面的数据就不再是有效数据了 //如果不加move会发生拷贝,而一个网页数据有的还是挺大的,拷贝的话需要浪费很多时间 data_list->push_back(std::move(doc)); } return true; } </code></pre> <h5>Save Data的实现</h5> <p>就是将前面得到的data_list里面的数据全部按照制定的规则存放到purify.txt文件中即可</p> <pre><code class="prism language-c++">bool SaveData(const std::string &save_path, const std::vector<Docinfo_t> &data_list) { std::ofstream out(save_path, std::ios_base::out | std::ios_base::binary); //要实现的就是同一网页的title,content,url通过\3分隔,不同网页通过\n分隔 //在文件中'\3'是以^C的形式体现的 for(auto & data : data_list) { //打开文件,以二进制的形式写入 if(!out.is_open()) { std::cerr<<"open savefail error"<<std::endl; return false; } std::string str; str+=data.title; str+='\3'; str+=data.content; str+='\3'; str+=data.url; str+='\n'; out.write(str.c_str(),str.size());//读哪里,读多长 } out.close(); return true; } //之后查看save_path路径下的文件 cat purify.txt | wc -l ---->8429行也就是说里面有8429个文件 </code></pre> <h3>6.编写建立索引的模块 Index</h3> <ul> <li> <p>首先需要建立index.hpp文件,在其中定义一个数据的数据结构Docinfo</p> </li> <li> <p>需要建立倒排索引的节点(文档id,权重,关键字)</p> </li> <li> <p>正排索引的数据结构选择数组,这样的话当知道文档id的话就可以根据下表直接找到(时间复杂度就为O(1))</p> </li> <li> <p>倒排索引一定会存在一个关键字和多个文档id(一个)有关联,所以倒排索引天然的就适合使用unordered_map</p> </li> <li> <p>字符串切分虽然用stl容器提供的接口也能够去写,可是比较繁琐,可以使用boost库中的split(<boost/algorithm/string.hpp>)</p> <pre><code class="prism language-c++">boost::split(type,str,boost::is_any_of("\3"),boost::token_compress_on) //第一个参数type就是一个用来存放切分后数据的数据结构 //str就是要切分的字符串 //boost::is_any_of()里面设置的是分隔符 //boost::tocken_compress_on:将连续多个分隔符压缩成一个,默认没打开,一般用的时候打开 </code></pre> </li> </ul> <h4>Index的基本代码结构</h4> <pre><code class="prism language-c++">// 这里是构建索引模块 #pragma once #include <iostream> #include <string> #include <unordered_map> #include <vector> namespace MyIndex { // 文档信息 struct Docinfo { std::string title; std::string content; std::string url; uint64_t file_id; // 因为有正排和倒排索引,所以文档id是不可或缺的 }; // 倒排元素 struct InvertedElement { std::string key_word; uint64_t file_id; int weight; // 权重,之后显示的先后顺序需要与权重挂钩 }; //重命名为倒排拉链 typedef std::vector<InvertedElement> InvertedList; class Index { private: // 正排索引的数据结构 // 因为正排索引需要的是根据文档id去找文档内容,文档id可以当做数组下标能够实现O(1)的查找 std::vector<Docinfo> forward_index; // 倒排索引的数据结构 // 根据关键字去找文件id,这天然就是一对多(一对一)的关系 // 所以用unordered_map最为合适 std::unordered_map<std::string, InvertedList> inverted_index; public: Index(); ~Index(); public: // 获取正排-->返回的是文档信息 Docinfo *GetForward(const uint64_t &id) { //这类比较简单就直接写了 if(id > forward_index.size()) { //id越界 std::cerr<<"id cross the border error"<<std::endl; return nullptr; } return &forward_index[id]; } // 获取倒排 -->返回的是倒排拉链,也就是一个关键字对应的一组文件id InvertedList* GetInverted(const std::string &key_word) { auto it = inverted_index.find(key_word); if(it==inverted_index.end()) { std::cerr<<"not found"<<std::endl; return nullptr; } return &(it->second); } // 构建索引 --->根据的就是解析后的./data/purify_html/purify.txt里面的内容 //const std::string target_path = "./data/purify_html/purify.txt"; // 目标文件的路径 bool BuildIndex(const std::string &input)//在这里面去构建对应的索引模块 { return true; } }; }; </code></pre> <p>接下来就是对上述函数的实现</p> <h5>Build Index的实现</h5> <ul> <li> <p>要构建索引就需要数据,而这数据就是Parser解析出来的存放在<code>./data/purify_html/purify.txt里面的内容</code></p> </li> <li> <p>所以理所应当的在Build Index中需要进行文件操作</p> <pre><code class="prism language-c++"> bool BuildIndex(const std::string &input) { // 之前是二进制形式写,这里也是二进制形式读 std::ifstream in(input.c_str(), std::ios_base::in | std::ios_base::binary); if (!in.is_open()) { std::cerr << "open error" << std::endl; return false; } std::string line; while (std::getline(in, line)) { // 构建正排-->返回的是Docinfo然后需要通过返回值去构建倒排索引 Docinfo *doc = BuildForward(line); if (doc == nullptr) { std::cerr << "BuildForward error" << std::endl; continue; // 一个文档的正排构建失败就没有必要再去构建倒排了 } // 构建倒排 BuildInverted(*doc); } in.close(); return true; } </code></pre> </li> </ul> <h6>正排索引函数的实现</h6> <ul> <li> <p>需要使用到之前提到的boost库中提供的split函数</p> <pre><code class="prism language-c++">void SlicingString(const std::string& line,std::vector<std::string>*result, const std::string& separator) { //可以通过stl提供的容器接口来实现,可是实现起来比较繁琐 //所以这里直接使用boost库中的<boost/algorithm/string.hpp> split boost::split(*result,line,boost::is_any_of(separator),boost::token_compress_on); //*result 存放切分的数据的结构vector<string> //line 要切分的数据 //is_any_of() 构建切分符 //token_compress_on将连续的分隔符压缩成一个分隔符(默认关闭,需要手动打开) } </code></pre> </li> </ul> <pre><code class="prism language-c++">Docinfo *BuildForward(const std::string &line) { std::vector<std::string> result; const std::string separator = "\3"; MyTool::StringTool::SlicingString(line, &result, separator); if (result.size() != 3) { // 如果切分后的数据没有三部分则切分出错 return nullptr; // 切分失败返回空 } // 将切分的数据填充到doc中 Docinfo doc; doc.title = result[0]; doc.content = result[1]; doc.url = result[2]; doc.file_id = forward_index.size(); // 先保存在push,这样可以让id和数组下标对应,比如一开始什么都没有size=0,文档id=0,当这个doc push进去之后它所在的下标也是0 // 将doc 插入到正排索引的vector中 forward_index.push_back(std::move(doc));// 通过拷贝效率太低,直接move可以提升效率 return &forward_index.back(); } </code></pre> <h6>倒排索引的原理</h6> <pre><code class="prism language-c++">//原理:就是需要构建出多个这样的结构,之前已经拿到了Docinfo的数据 struct InvertedElement { std::string key_word; uint64_t doc_id; int weight; // 权重,之后显示的先后顺序需要与权重挂钩 }; //倒排拉链 typedef std::vector<InvertedElement> InvertedList; //倒排索引一定是一个key_word和一组(一个)InvertedElement对应 //通过正排索引可以拿到的内容 struct Docinfo { std::string title; std::string content; std::string url; uint64_t file_id; }; //举例文档: title:新电影铃芽户缔 content:新海诚出了一部新的电影名叫铃芽户缔 url:http://XXX doc_id: 324 根据文档内容形成一个或多个InvertendElement(倒排拉链) 因为是一个一个的对文档进行处理的,一个文档会包含多个词,都对应到当前的doc_id 1. 需要对title和content进行分词 --这里使用jieba分词-->CutForSearch(s,words) titile:新/电影/新电影/铃芽/户缔/铃芽户缔 title_words content:新/海诚/新海诚/出/一部/新/电影/电影名/叫/铃芽/户缔/铃芽户缔 content_words 词和文档的相关性(相关性的实现并不是一个简单的技术,这里用最简单的方式去实现) 2. 词频统计 这里我实现相关性的方案就是根据词频来设置,同时我认为在标题中出现的词相关性会更高一些,在内容中出现相关性低一些 struct word_count{ int title_count;//标题中key_word出现的次数 int content_count;///内容中key_word出现的次数 }; //建立关键字和出现次数的映射关系 unordered_map<std::string,word_count> words_count; for &word : title_word{ word_count[word].title_count++; } for &word : content_word{ word_count[word].content_count++; } 知道了文档内容中词的出现次数 3. 自定义相关性 for &word : words_count{ InvertendElement elem; //因为处理的是同一个文档的内容,所以文档的id是可知的 elem.doc_id = 324; elem.key_word = word.first; elem.weight = 10*word.second.title.count + 1* word.second.content_count; inverted_index[word.first].push_back(elem); } //s要切部分 //words切后存储的结构 //string s; //boost::to_lower(s)将字符串转化成小写 //c也提供了一些函数接口:toupper()//转换为大写,tolower()//转换为小写 //c++提供的接口可以用:transform(s.begin(),s.end(),s.begin(),::toupper); //几个参数简单理解就是从哪开始,到哪结束,结果储存到哪,转成大写还是小写 </code></pre> <h6>jieba分词的使用</h6> <ul> <li> <p>获取链接:git clone https://gitcode.net/mirrors/yangyiwu/cppjieba.git</p> </li> <li> <p>如何使用:需要的是include/cppjieba/Jieba.hpp</p> </li> <li> <p>文件中有个test/demo.cpp的文件里面是jieba分词的用法</p> </li> <li> <p>如果想要正确编过,需要将deps/limonp里面的所有文件都拷贝进include/cppjieba中,我这里使用切词的函数是里面的:CutForSearch(s,words) ---->s表示要切分的数据,words表示存放的数据</p> <pre><code class="prism language-c++">//在下载后的cppjieba文件中,有一个test文件里面有个demo.cpp文件,里面记录了各种接口函数的用法以及样例 //接下来来看看CutForSearch(s,words)函数在文档中的用法 #include "../include/cppjieba/Jieba.hpp" #include<iostream> #include<string> #include<vector> using namespace std; //这些const定义的是词库的所在路径,自己使用的时候需要注意调整这些路径 const char* const DICT_PATH = "../dict/jieba.dict.utf8"; const char* const HMM_PATH = "../dict/hmm_model.utf8"; const char* const USER_DICT_PATH = "../dict/user.dict.utf8"; const char* const IDF_PATH = "../dict/idf.utf8"; const char* const STOP_WORD_PATH = "../dict/stop_words.utf8"; int main(int argc, char** argv) { cppjieba::Jieba jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH); string s; vector<string> words; s = "小明硕士毕业于中国科学院计算所,后在日本京都大学深造"; cout << s << endl; cout << "[demo] CutForSearch" << endl; jieba.CutForSearch(s, words); cout << limonp::Join(words.begin(), words.end(), "/") << endl; return 0; } //我这里采用ln -s软链接的方式去调整路径 //结果: //小明硕士毕业于中国科学院计算所,后在日本京都大学深造 //[demo] CutForSearch ///小明/硕士/毕业/于/中国/科学/学院/科学院/中国科学院/计算/计算所/,/后/在/日本/京都/大学/日本京都大学/深造 </code></pre> <pre><code class="prism language-c++">#include "./cppjieba/Jieba.hpp" //我在当前目录设置了一个cppjieba的软连接 //lrwxrwxrwx cppjieba -> data/cppjieba/include/cppjieba const char *const DICT_PATH = "./dict/jieba.dict.utf8"; const char *const HMM_PATH = "./dict/hmm_model.utf8"; const char *const USER_DICT_PATH = "./dict/user.dict.utf8"; const char *const IDF_PATH = "./dict/idf.utf8"; const char *const STOP_WORD_PATH = "./dict/stop_words.utf8"; class Cppjieba { private: static cppjieba::Jieba jieba;//要定一个全局的静态成员变量,这样不需要每一次调用CutForSearch都去先创建一个jieba对象,大大的节省了时间 //因为jieba分词在index建立索引中会非常频繁的使用,所以一旦在函数里面定义jieba对象就会让整个程序变的很慢 public: static void CutForSearch(const std::string &s, std::vector<std::string> *words) { jieba.CutForSearch(s, *words); } }; cppjieba::Jieba Cppjieba::jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH); </code></pre> </li> </ul> <h6>倒排索引的实现</h6> <pre><code class="prism language-c++">bool BuildInverted(const Docinfo &doc) { #define T_weight 10 #define C_weight 1 struct word_count{ int title_cnt;//标题key_word出现次数 int content_cnt;//内容key_word出现次数 }; //1.需要对标题和内容进行切分 && 2.进行词频统计 std::vector<std::string> title_result;//存放切分后的数据 std::unordered_map<std::string,word_count> word_cnt;//建立key_word和词频的映射 MyTool::Cppjieba::CutForSearch(doc.title,&title_result); for(auto word : title_result)//遍历标题分词 { //这里有个细节,Hello hello HELLO这些词是算一个,还是算三个-->通过百度浏览器的搜索结果可以发现搜索时是不区分大小写的 //这里就定个规定:文档的标题和正文分词全部按照小写来分词,同时用户输入之后也将其转换成小写 //可以用c提供的toupper()--->转换为大写,tolower()---->转换为小写 /c++ <algorithm>中的transform等等 //我选择的就是boost库提供的一个接口to_lower() boost::to_lower(word);//因为我不想改变doc里面的数据,所以没有用引用 word_cnt[word].title_cnt++;//unordered_map重载了[],如果key存在时返回的就是value的引用,如果不存在就插入key } std::vector<std::string> content_result; MyTool::Cppjieba::CutForSearch(doc.content,&content_result); for(auto word : content_result) { boost::to_lower(word); word_cnt[word].content_cnt++; } //3.构建相关性 for(auto& word : word_cnt) { //word 在这里是unordered_map<string,vector<word_count>> InvertedElement elem; //这里处理的都是一个文档的分词,所以id就是doc里面的file_id elem.file_id = doc.file_id; elem.key_word = word.first; elem.weight = T_weight* word.second.title_cnt + C_weight* word.second.content_cnt;//设置相关性 //unordered_map<string,vector<InvertedList>> inverted_index; InvertedList& inverted_list = inverted_index[word.first]; inverted_list.push_back(std::move(elem));//这里加不加move都还好,里面数据比较小 } return true; } </code></pre> <h3>7.编写搜索引擎模块Searcher</h3> <p>基本代码结构:</p> <pre><code class="prism language-c++">//安装jsoncpp:yum install -y jsoncpp-devel void InitSearcher(const std::string &path) { // 1.构建/获取index对象--->因为对于index,建立完之后主要就是查找,并不会修改里面的内容,所以index只需要一份即可 // 也就是把index设计成一个单例即可 } //这个就是通过用户的搜索信息去索引中去查找相关文档,并输出序列化之后的字符串 void Search(const std::string &Inquire, std::string *json_string) { // 1.分词:将搜索Inquire进行分词 // 2.查询:分词之后用关键词去索引表中查找,如果有会拿到倒排拉链InvertedList-->vector<InvertedElement> // 3.合并排序:将查找后的结果通过weight权重进行降序排序 // 4.构建:根据查询结果构建json字符串--->也就是序列化,需要jsoncpp } </code></pre> <h4>InitSearcher函数的实现</h4> <h5>index改为单例模式</h5> <ul> <li> <p>原因:我所实现的搜索引擎只是对boost进行搜索,所以理论上并不需要多份索引,构建索引也需要消耗资源,只需要构建一份之后其他的共用即可,这里就需要将index改为单例,具体改法如下:</p> <pre><code class="prism language-c++">//在index类中设置一个staitc的index对象,和一把锁(解决线程安全问题,如果是多线程不加锁,对单例的获取存在线程安全问题) static Index *singleton_index; static std::mutex mtx; // 创建一把锁---->#include<mutex> //之后就是说构造函数私有化-->必须要有,因为这样才能够在类内new一个index对象 //禁止构造和拷贝构造函数 Index(const Index &in) = delete; // 禁止拷贝构造 Index &operator=(const Index &in) = delete; // 禁止赋值拷贝 //之后就是在public中定义一个静态的成员函数 static Index *GetIndex()// 静态成员函数才能访问静态成员变量 { // 两个if是为了提高效率,多个线程竞争锁也是需要消耗资源的, if (singleton_index == nullptr) { // 不加锁的话多线程情况下不是线程安全的 mtx.lock(); // 加锁 if (singleton_index == nullptr) { singleton_index = new Index; } mtx.unlock(); } return singleton_index; } </code></pre> </li> </ul> <pre><code class="prism language-c++">//构建好单例之后直接使用接口即可 index = MyIndex::Index::GetIndex(); index->BuildIndex(path); </code></pre> <h4>Search函数的实现</h4> <pre><code class="prism language-c++">struct Compare//定义一个降序的仿函数用于sort的第三个参数 { bool operator()(const MyIndex::InvertedElement &e1, const MyIndex::InvertedElement &e2) { return e1.weight > e2.weight; } }; void Search(const std::string &Inquire, std::string *json_string) { // 1.分词:将搜索Inquire进行分词 std::vector<std::string> words; MyTool::Cppjieba::CutForSearch(Inquire, &words); // 2.查询:分词之后用关键词去索引表中查找,如果有会拿到倒排拉链InvertedList-->vector<InvertedElement> MyIndex::InvertedList result;//different for (std::string key_word : words) { boost::to_lower(key_word); // 建立索引的时候默认全是小写,搜索的时候也同样需要 // 先查倒排,再根据查询后的结果查正排 MyIndex::InvertedList *inverted_list = index->GetInverted(key_word); if (inverted_list == nullptr) // 如果词不存在,就继续下一次查找 { continue; } // 这个就是通过迭代器将InvertedList里面的InvertedElement全部插入到result中 result.insert(result.end(), inverted_list->begin(), inverted_list->end()); } // 3.合并排序:将查找后的结果通过weight权重进行降序排序 std::sort(result.begin(), result.end(), Compare()); // 4.构建:根据查询结果构建json字符串--->也就是序列化,需要jsoncpp Json::Value root; //Value是json里面的万能类 //json序列化通常使用Wright类(FastWriter,StyledWriter) 反序列化通常是Reader类 for(auto& elem : result) { //根据result里面的数据,去正排查找文档并且把查找到的文档序列化 MyIndex::Docinfo* doc = index->GetForward(elem.file_id); Json::Value tmp; tmp["title"] = doc->title;//标题 tmp["desc"] = GetDesc(doc->content, elem.key_word);//描述 tmp["url"] = doc->url;//要跳转的网页 root.append(tmp); } //要注意无论是FastWriter还是StylenWriter它们都是类,而write是它们的成员函数,所以需要先构建一个匿名对象来使用它的成员函数 //*json_string = Json::FastWriter().write(root);//StyleWriter方便调试,所以先用,后面没有问题之后再用这个 *json_string = Json::StyledWriter().write(root);//这样用户就获得了通过权重排序之后的文档信息 } </code></pre> <p><strong>注意:</strong><code>jsoncpp</code>是第三方库所以使用g++编译的时候需要指定库<code>-ljsoncpp</code></p> <h5>摘要函数GetDesc的实现</h5> <pre><code class="prism language-c++"> struct GetCompare//定义了一个仿函数 { bool operator()(int x, int y) { return std::tolower(x) == std::tolower(y); } }; std::string GetDesc(const std::string &content, const std::string &key_word) { // 获取描述---->这里就简易实现一下 // 获取第一次出现的key_word的前50个字符,获取key_word的后100个字符,如果不够就从头开始,或者截取到尾部 const int prev = 50; // 因为size_t是无符号整数,所以为了方便就直接设置为int const int next = 100; // int pos = content.find(key_word); // 这里查找会出现None1的情况,主要是因为在之前的代码中我对切分后的数据统一to_lower了,但是find函数在查找的时候不会自己进行大小写转化 // 这里使用C++的<algorithm>提供的算法search auto it = std::search(content.begin(), content.end(), key_word.begin(), key_word.end(), GetCompare()); if (it == content.end()) return "None"; // 如果到结尾都没有查到就返回None---不会发生 int pos = std::distance(content.begin(), it); int start = 0; int end = content.size() - 1; // 代表前面有50个字符 if (pos - prev > start) start = pos - prev; // 代表后面有100个字符 if (end - next > pos) end = pos + next; if (start >= end) // 同理 return "None"; std::string desc = content.substr(pos, end - start); desc += "......"; return desc; } </code></pre> <ul> <li> <p>C++的<code>#include<algorithm></code>提供的search函数</p> <pre><code class="prism language-c++">template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 search (ForwardIterator1 first1, ForwardIterator1 last1,ForwardIterator2 first2, ForwardIterator2 last2,BinaryPredicate pred); //简单点理解就是1,2两个参数是要查找数据的迭代器:意味着从哪开始查到哪结束比如:string str = "hello word"; //3,4两个参数是查找目标的迭代器string key = "word"; 在str中去查找key //第5个参数可以理解为传入查找方法,我这里是传入的一个GetCompare的仿函数 </code></pre> </li> <li> <p><code>#include <iterator></code>提供的函数<code>destance</code>可以用来查看迭代器相比于begin走了多远</p> <pre><code class="prism language-c++">template<class InputIterator> typename iterator_traits<InputIterator>::difference_type distance (InputIterator first, InputIterator last); //用法 std::list<int> mylist; for (int i=0; i<10; i++) mylist.push_back (i*10); std::list<int>::iterator first = mylist.begin(); std::list<int>::iterator last = mylist.end(); std::cout << "The distance is: " << std::distance(first,last) << '\n'; return 0; //result:The distance is 10 </code></pre> </li> </ul> <h3>8.编写http server模块</h3> <h4>升级gcc</h4> <pre><code class="prism language-c++">如果想使用cpp-httplib库 注意: centos 7 默认的gcc编译器的版本是4.8.5版本的,如果想要使用cpp-httplib是需要更新gcc编译器的,如果用老的会编译不通过,或者运行时报错 更新gcc方法,搜索关键字: scl gcc devsettool升级gcc 1.安装scl源: yum install centos-release-scl scl-utils-build 2.安装新版本gcc sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++ 安装好之后,工具集在ls /opt/rh中 //启动新版本的gcc:命令行启动只能在本会话有效 scl enable devtoolset-7 bash //建议将上面的启动命令添加到~/.bash_profile这个登录脚本里面 # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:$HOME/.local/bin:$HOME/bin export PATH #每次启动的时候都会执行scl这个命令 scl enable devtoolset-7 bash </code></pre> <h4>安装cpp-httplib</h4> <pre><code class="prism language-c++">最新的cpp-httplib在使用的时候,如果gcc不是特别新的话可能会有运行时错误 我这里使用的是cpp-httplib 0.7.15版本 通过gitee搜索cpp-httplib,下载zip文件上传到服务器即可 </code></pre> <h4>使用cpp-httplib</h4> <pre><code class="prism language-c++"> inline bool Request::has_param(const char *key) const { return params.find(key) != params.end();//他这里面的params是一个multimap<std::string, std::string>类型 } //Server端的使用 //http httplib::Server svr; svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.lesten("0.0.0.0",8080); </code></pre> <pre><code class="prism language-c++">#include "./cpp-httplib/httplib.h"//安装的cpp-httplib库的位置 #include "searcher.hpp" const std::string input = "./data/purify_html/purify.txt";//建立索引的数据源 const std::string root_path = "./wwwroot";//web根目录 int main() { MySearcher::Searcher searcher; searcher.InitSearcher(input); httplib::Server svr; //设置web根目录 svr.set_base_dir(root_path.c_str()); //Lambda表达式想要引用外部的对象,需要在[] 中&+对象---->[&searcher] svr.Get("/s",[&searcher](const httplib::Request& req, httplib::Response& res){ //res.set_content("hello word!","text/plain");//里面是页面的显示内容,以文本形式显示 if(!req.has_param("word"))//has_param--->判断是否有参数 { res.set_content("需要有搜索关键字!", "text/plain; charset=utf-8"); //设置返回内容"text/plain" 对应的是http中的Content-Type设置charset=utf-8的时候中间不能带空格 return ; } std::string key_word = req.get_param_value("word");//获取参数 std::cout<<"用户在搜索:"<<key_word<<std::endl; std::string json_string; searcher.Search(key_word,&json_string); //到这里就是执行完搜索服务了,需要给用户返回搜索结果---->json的Content-Type是application/json res.set_content(json_string,"application/json"); }); //将其设置为listen状态 svr.listen("0.0.0.0",8080); return 0; } </code></pre> <h3>9.编写前端模块</h3> <p><strong>了解<code>vscode</code></strong></p> <pre><code class="prism language-c++">它是一个编辑器 </code></pre> <p><strong>安装插件</strong></p> <pre><code class="prism language-c++">1.Chinese(汉化) 2.open in browser//写好的网页可以直接单击右键用浏览器打开 3.Remote -SSH //用来链接Linux 在命令行输入remote -ssh之后就开始登录了跟xshell的登录是一样的 </code></pre> <h4>编写前端代码</h4> <h5>了解html css js</h5> <pre><code>html: 是网页的骨骼---负责网页结构 css: 负责网页美观 js(javascript):网页的灵魂--网页的动态效果,前后端交互 //我是对着教程:w3cschool这个网站去写的一些前端模块 </code></pre> <h5>编写html</h5> <ol> <li> <p>div 元素是块级元素,它是可用于组合其他 HTML 元素的容器。div 元素没有特定的含义</p> </li> <li> <p>input 是设置表单数据</p> </li> <li> <p>button元素定义可点击的按钮</p> </li> <li> <p>a标签,href 属性规定链接的目标。开始标签和结束标签之间的文字被作为超级链接来显示。</p> <pre><code class="prism language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3school.com.cn/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Visit W3School<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span> <span class="token comment"><!--上面这行代码显示为:Visit W3School--></span> </code></pre> </li> <li> <pre><code class="prism language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>这个就是一个普通的标签<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>i</span><span class="token punctuation">></span></span>这个是斜体标签<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>i</span><span class="token punctuation">></span></span> </code></pre> </li> </ol> <h5>编写css</h5> <p>有很多种方法将html和css进行关联起来,这里我就直接采用style将其内联到html中</p> <pre><code class="prism language-html">设置样式的本质:找到要设置的标签,设置它的属性 1.选择特定的标签:类选择器,标签选择器,复合选择器 2.设置指定标签的属性:具体见代码(很多属性也可以去参考已有的网页他们的属性设置) <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"> <span class="token comment">/*去掉网页中的所有默认内外边距*/</span> <span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token comment">/*设置外边距*/</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token comment">/*设置内边距*/</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*设置body内的内容和html的呈现是1:1的*/</span> <span class="token selector">html, body</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*.开头的一般叫类选择器*/</span> <span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token comment">/*设置div的宽度*/</span> <span class="token property">width</span><span class="token punctuation">:</span> 800px<span class="token punctuation">;</span> <span class="token comment">/*通过设置外边距达到居中效果*/</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0px auto<span class="token punctuation">;</span> <span class="token comment">/* 设置外边距的上边距,保持元素和网页的上部距离 */</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* 复合选择器,选择container下的search */</span> <span class="token selector">.container .search</span> <span class="token punctuation">{</span> <span class="token comment">/* 宽度与父标签一致 */</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token comment">/* 高度设置为52xp */</span> <span class="token property">height</span><span class="token punctuation">:</span> 52px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* 选中input标签 单看input就是标签选择器,不需要加.*/</span> <span class="token selector">.container .search input</span> <span class="token punctuation">{</span> <span class="token property">float</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span> <span class="token comment">/*给input和button设置left浮动就可以让两个盒子之间的边距清零就可以拼在一起了*/</span> <span class="token property">width</span><span class="token punctuation">:</span> 600px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span> <span class="token comment">/* input在设置的时候没有考虑边框的问题,所以同height的情况下button会比input小一点 */</span> <span class="token comment">/* 设置边框宽度(1px),边框的样式(实线) ,边框颜色(black) */</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid black<span class="token punctuation">;</span> <span class="token comment">/* 右边框给去除 */</span> <span class="token property">border-right</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* 设置内边距,让默认文字不要紧贴左侧边框 */</span> <span class="token property">padding-left</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token comment">/* 设置input内部字体的颜色和字体大小、样式*/</span> <span class="token property">color</span><span class="token punctuation">:</span> #ccc<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 17px<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Georgia<span class="token punctuation">,</span> serif<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.container .search button</span> <span class="token punctuation">{</span> <span class="token property">float</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 150px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span> <span class="token comment">/*设置button的背景颜色,可以通过f12去查看百度或者其他浏览器的颜色设置*/</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #4e6ef2<span class="token punctuation">;</span> <span class="token comment">/*设置button中的字体颜色*/</span> <span class="token property">color</span><span class="token punctuation">:</span> #FFF<span class="token punctuation">;</span> <span class="token comment">/*设置button字体大小*/</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token comment">/* 设置字体样式 */</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Georgia<span class="token punctuation">,</span> serif<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.container .result</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.container .result .docinfo</span> <span class="token punctuation">{</span> <span class="token comment">/*上边距*/</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*a标签的设置,a和i属于行内元素,为了防止显示错误需要加上display:block*/</span> <span class="token selector">.container .result .docinfo a</span> <span class="token punctuation">{</span> <span class="token comment">/*设置块级元素,可以独占一行*/</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token comment">/*去除a标签的下划线*/</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/*设置a标签的字体大小,颜色*/</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #4e6ef2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*a标签的光标事件*/</span> <span class="token selector">.container .result .docinfo a:hover</span> <span class="token punctuation">{</span> <span class="token comment">/*设置鼠标放在a标签上的动态效果*/</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*p标签属性设置*/</span> <span class="token selector">.container .result .docinfo p</span> <span class="token punctuation">{</span> <span class="token comment">/*就是让p标签内容与a标签内容有点空位*/</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 3px<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Georgia<span class="token punctuation">,</span> serif<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/*i标签属性设置*/</span> <span class="token selector">.container .result .docinfo i</span> <span class="token punctuation">{</span> <span class="token comment">/*设置块级元素,可以独占一行*/</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token comment">/*取消标签斜体风格*/</span> <span class="token property">font-style</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 13<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Georgia<span class="token punctuation">,</span> serif<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> greenyellow<span class="token punctuation">;</span> <span class="token punctuation">}</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span> </code></pre> <h5>编写js</h5> <pre><code class="prism language-c++">直接使用原生的js成本比较高,这里我使用的是JQuery //有点像c++语言和STL库之间的关系 //这里我直接网页引入微软的CDN <head> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script> </head> </code></pre> <p><code>JQuery</code>中会遇到的一些函数或者用法:</p> <ul> <li> <pre><code class="prism language-js"><span class="token comment">//这样就可以提取input里面的value数据</span> <span class="token comment">//()里面填写的是要提取谁里面的数据,提input里面的value</span> len query <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">".container .search input"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">val</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> </li> <li> <pre><code class="prism language-js">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"query = "</span> <span class="token operator">+</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//console 是浏览器的对话框,可以用来查看js数据</span> <span class="token comment">//具体可以通过F12然后选择控制器去查看</span> </code></pre> </li> <li> <pre><code class="prism language-js"><span class="token comment">//发送http请求,ajax:属于一个和后端进行数据交互的函数,JQuery中的</span> <span class="token function">ajax</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//具体的使用见下面的代码</span> </code></pre> </li> </ul> <p>还有一些的使用不好单独举例,见下面的代码注释</p> <pre><code class="prism language-js"><span class="token operator"><</span><span class="token operator">!</span><span class="token operator">--</span>js代码<span class="token operator">--</span><span class="token operator">></span> <span class="token operator"><</span>script<span class="token operator">></span> <span class="token keyword">function</span> <span class="token function">Search</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">//是浏览器的弹出框,当点击搜索一下会弹出alert的内容</span> <span class="token comment">//alert("hello js");</span> <span class="token comment">//1.提取数据,$可以理解成JQuery的别称</span> <span class="token comment">//()里面填写的是要提取谁里面的数据,提input里面的value</span> <span class="token keyword">let</span> query <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">".container .search input"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">val</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"query = "</span> <span class="token operator">+</span> query<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//console 是浏览器的对话框,可以用来查看js数据</span> <span class="token comment">//2.发送http请求,ajax:属于一个和后端进行数据交互的函数</span> <span class="token comment">//type是请求的方法(GET,POSE)这里用GET,url就是自己的url后面加上/s?word=query</span> <span class="token comment">//success:function(data)请求成功返回的参数就存在data中,相当于设置了一个回调</span> $<span class="token punctuation">.</span><span class="token function">ajax</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"GET"</span><span class="token punctuation">,</span> <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">"/s?word="</span> <span class="token operator">+</span> query<span class="token punctuation">,</span> <span class="token function-variable function">success</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">BuildHtml</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">BuildHtml</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">//想重新构建一个网页信息,data是从http_server中返回的json_string</span> <span class="token keyword">let</span> result_tag <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">".container .result"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取网页中的result标签</span> result_tag<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//清空历史搜索结果</span> <span class="token comment">//类似于c++里面的for(auto e : result)</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> elem <span class="token keyword">of</span> data<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">let</span> a_tag <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"<a>"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">text</span><span class="token operator">:</span> elem<span class="token punctuation">.</span>title<span class="token punctuation">,</span> <span class="token comment">//text就代表标签内容</span> <span class="token literal-property property">href</span><span class="token operator">:</span> elem<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token comment">//设置跳转网页的url</span> <span class="token literal-property property">target</span><span class="token operator">:</span> <span class="token string">"_blank"</span> <span class="token comment">//设置跳转,即跳转到一个新的网页中</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> p_tag <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"<p>"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">text</span><span class="token operator">:</span> elem<span class="token punctuation">.</span>desc <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> i_tag <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"<i>"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">text</span><span class="token operator">:</span> elem<span class="token punctuation">.</span>url <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> doc_tag <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"<div>"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token keyword">class</span><span class="token operator">:</span> <span class="token string">"docinfo"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> a_tag<span class="token punctuation">.</span><span class="token function">appendTo</span><span class="token punctuation">(</span>doc_tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//表示a_tag添加到doc_tag中</span> p_tag<span class="token punctuation">.</span><span class="token function">appendTo</span><span class="token punctuation">(</span>doc_tag<span class="token punctuation">)</span><span class="token punctuation">;</span> i_tag<span class="token punctuation">.</span><span class="token function">appendTo</span><span class="token punctuation">(</span>doc_tag<span class="token punctuation">)</span><span class="token punctuation">;</span> doc_tag<span class="token punctuation">.</span><span class="token function">appendTo</span><span class="token punctuation">(</span>result_tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//doc_tag要添加到result标签中</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>script<span class="token operator">></span> </code></pre> <h3>10.细节优化</h3> <h4>文档重复显示问题</h4> <p>首先是之前在searcher.hpp中有一处的问题具体暴露出来就是以下问题,当一个文档同时出现几个关键词时,会重复的将其打印出来<a href="http://img.e-com-net.com/image/info8/a7d30d839c0245e88db7b0deda4ff76e.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/a7d30d839c0245e88db7b0deda4ff76e.jpg" alt="Boost搜索引擎的实现_第4张图片" width="650" height="231" style="border:1px solid black;"></a><a href="http://img.e-com-net.com/image/info8/ccb74a3b587949ea9732650e683f0fba.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/ccb74a3b587949ea9732650e683f0fba.jpg" alt="Boost搜索引擎的实现_第5张图片" width="650" height="380" style="border:1px solid black;"></a></p> <p>接下来就是去优化这段代码,其实也很简单,我这里选择重新定义一个类</p> <pre><code class="prism language-c++">struct DedupElem { uint64_t doc_id; int wight;//权值进行加和 vector<string> key_word;//存放一组关键字 }; </code></pre> <p>具体实现见下面代码:</p> <pre><code class="prism language-c++">void Search(const std::string &Inquire, std::string *json_string) { std::vector<std::string> words; MyTool::Cppjieba::CutForSearch(Inquire, &words); //-------------------------begin--------------------------------- 这是需要的两个变量 std::unordered_map<uint64_t, DedupElem> dedup_map; // 建立一个文档id与Dedupmlem的映射 std::vector<DedupElem> list_all;//用来存dedup_map中的second //--------------------------end-------------------------------- for (std::string key_word : words) { boost::to_lower(key_word); MyIndex::InvertedList *inverted_list = _index->GetInverted(key_word); if (inverted_list == nullptr) { continue; } //------------------------------begin-----------------------------------------这之间的就是优化的代码 // 优化代码,遍历InvertedList for (const auto &elem : *inverted_list) { auto &item = dedup_map[elem.file_id]; // 如果文档id没有就构建,有了就返回second的引用 item.doc_id = elem.file_id; // 构建文档id item.weight += elem.weight; // 将关键字的权重全部相加 item.key_words.push_back(elem.key_word); // 同一文档的关键字全部插入到vector中 } } for (auto &elem : dedup_map) { list_all.push_back(std::move(elem.second)); } //-------------------------------end-------------------------------------------到这结束 std::sort(list_all.begin(), list_all.end(), Compare()); Json::Value root; for (auto &elem : list_all) { // 根据result里面的数据,去正排查找文档并且把查找到的文档序列化 MyIndex::Docinfo *doc = _index->GetForward(elem.doc_id); Json::Value tmp; tmp["title"] = doc->title; // 标题 tmp["desc"] = GetDesc(doc->content, elem.key_words[0]); // 描述--->这里就取vector<string>里面的第一个词作为关键字了 tmp["url"] = doc->url; // 要跳转的网页 root.append(tmp); } *json_string = Json::FastWriter().write(root); } </code></pre> <h4>添加一些日志信息</h4> <p>具体见代码,这个主要目的就是为了看着方便,只要是之前代码用<code>cerr</code>或者<code>cout</code>打印的都可以换成LOG();</p> <pre><code class="prism language-c++">#pragma once #include<iostream> #include<string> #include<cstring> #include<vector> #include<ctime> #define NORMAL 0 //正常 #define WARNING 1 //警告 #define DEBUG 3 //调试 #define CRITICAL 4 //严重错误 #define LOG(STATE,MESSAGE) log(#STATE,MESSAGE,__FILE__,__LINE__) void log(const std::string &state,const std::string& message,const std::string& file,int line) { //看日志的话一般看处于什么状态是正常还是错误以及输入的信息,其次看什么文件出问题了,在哪一行 time_t t = time(nullptr);//获取当前的时间戳 //下面是将时间戳转换成北京时间 //struct tm *gmtime(const time_t *timep); struct tm* my_tm = gmtime(&t); //因为有时差,所以小时需要加上8 my_tm->tm_hour+=8; std::string format = "%Y-%m-%d:%H:%M:%S"; //size_t strftime(char *s, size_t max, const char *format,const struct tm *tm); //char* s 输出型参数 //size_t max 是s的大小 //format就是要按照什么形式输出 char buff[25]; memset(buff,0,sizeof(buff)); strftime(buff,sizeof(buff),format.c_str(),my_tm); std::cout<<"["<<state<<"]"<<"["<< buff <<"]" <<"[" << message <<"]"<<"["<<file<<"]"<<"["<< line <<"]"<<std::endl; } </code></pre> <h4>添加去暂停词代码</h4> <pre><code class="prism language-c++"> const char *const DICT_PATH = "./dict/jieba.dict.utf8"; const char *const HMM_PATH = "./dict/hmm_model.utf8"; const char *const USER_DICT_PATH = "./dict/user.dict.utf8"; const char *const IDF_PATH = "./dict/idf.utf8"; const char *const STOP_WORD_PATH = "./dict/stop_words.utf8"; class Cppjieba { private: // 增加一个去暂停词的功能---->这个功能会让创建变的非常的慢 cppjieba::Jieba jieba; std::unordered_map<std::string, bool> stop_word; // 同时这些只需要同时存在一份,所以直接设置为单例 static Cppjieba *singloten; private: Cppjieba() : jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH){} Cppjieba(const Cppjieba &ba) = delete; Cppjieba &operator=(const Cppjieba &ba) = delete; public: static Cppjieba *GetJieba() // 获取单例 { static std::mutex mtx; if (singloten == nullptr) { mtx.lock(); if (singloten == nullptr) { singloten = new Cppjieba(); singloten->InitJieba(); // 构建完进行初始化 } mtx.unlock(); } return singloten; } bool InitJieba() { std::ifstream in(STOP_WORD_PATH); if (!in.is_open()) { std::string st = "open file error:"; LOG(CRITICAL, st += STOP_WORD_PATH); return false; } std::string line; while (std::getline(in, line)) { stop_word[line] = true; // 插入暂停词到stop_word中 } in.close(); return true; } void CutForSearchHelper(const std::string &s, std::vector<std::string> *words) { jieba.CutForSearch(s, *words); for (auto item = words->begin(); item != words->end();) // 就是遍历words然后里面的每个元素都去stop_find里面去找 { auto it = stop_word.find(*item); if (it != stop_word.end()) { // item是暂停词需要去除,同时需要考虑迭代器失效问题 item = words->erase(item); } else { item++; } } } public: static void CutForSearch(const std::string &s, std::vector<std::string> *words) { MyTool::Cppjieba::GetJieba()->CutForSearchHelper(s, words); // 直接调用上面的分词助手 // 好处就是无需修改其他的代码 } }; Cppjieba *Cppjieba::singloten = nullptr; }; </code></pre> <h3>11.将其部署到云服务器上</h3> <h4>部署</h4> <p>部署其实很简单</p> <pre><code class="prism language-shell">将日志信息输出到log.txt中 ,把标准错误重定向到标准输出 最后的<span class="token operator">&</span>就是以守护进程的方式 ---这样即使关闭xshell,这个服务依旧是存在的 <span class="token function">nohup</span> ./Http_Server <span class="token operator">></span> log.txt <span class="token operator"><span class="token file-descriptor important">2</span>></span><span class="token file-descriptor important">&1</span> <span class="token operator">&</span> 如果想要关闭服务,可以通过ps axj <span class="token operator">|</span> <span class="token function">grep</span> Http_Server 查看pid然后通过 kill命令将其关闭 </code></pre> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1705977245878792192"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(项目笔记,搜索引擎,c++,STL,linux,正排/倒排索引)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1891120277924933632.htm" title="C++学习指南" target="_blank">C++学习指南</a> <span class="text-muted">月眠老师</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>一、引言C++是一种功能强大的高级编程语言,它融合了面向过程编程和面向对象编程的特性。由于其效率高、可移植性强等优点,广泛应用于系统开发、游戏编程、嵌入式系统等诸多领域。对于想要深入学习C++的人来说,需要全面掌握其语法、编程范式、数据结构、算法以及相关的开发工具等多方面的知识。二、C++基础语法(一)基本数据类型整型(Integer)在C++中有多种整型类型,如int(通常为32位有符号整数)、</div> </li> <li><a href="/article/1891118761587568640.htm" title="Visual Studio Code支持WSL,直接修改linux/ubuntu中的文件" target="_blank">Visual Studio Code支持WSL,直接修改linux/ubuntu中的文件</a> <span class="text-muted">柳鲲鹏</span> <a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/vscode/1.htm">vscode</a><a class="tag" taget="_blank" href="/search/ide/1.htm">ide</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E8%BE%91%E5%99%A8/1.htm">编辑器</a> <div>步骤1开始通过WSL使用VSCode|MicrosoftLearn点击远程开发扩展包。步骤2RemoteDevelopment-VisualStudioMarketplace点击install,允许打开VisualStudioCode。步骤3共有4项,一齐安装。步骤4在WSLLinux(Ubuntu)中:sudoapt-getinstallwgetca-certificates打开步骤1准备工作完</div> </li> <li><a href="/article/1891114090902908928.htm" title="linux下使用mysql(上)" target="_blank">linux下使用mysql(上)</a> <span class="text-muted">It塔塔开</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>1、安装mysqlaptsearchmysql-server...#查询结果mysql-server-8.0/focal-security,focal-updates8.0.41-0ubuntu0.20.04.1amd64MySQLdatabaseserverbinariesandsystemdatabasesetup...aptinstallmysql-server-8.0...2、连接数据库m</div> </li> <li><a href="/article/1891114092291223552.htm" title="OpenEuler22.03 LTS SP3 系统优化" target="_blank">OpenEuler22.03 LTS SP3 系统优化</a> <span class="text-muted">运维小弟| srebro.cn</span> <a class="tag" taget="_blank" href="/search/openeuler/1.htm">openeuler</a><a class="tag" taget="_blank" href="/search/%E7%B3%BB%E7%BB%9F%E4%BC%98%E5%8C%96/1.htm">系统优化</a> <div>OpenEuler22.03LTSSP3系统优化1、关闭selinuxsetenforce0sed-i"s#SELINUX=enforcing#SELINUX=disabled#g"/etc/selinux/config2、禁用swapswapoff-ased-ri's/.*swap.*/#&/'/etc/fstab3、公有云机器,必须安全加固(1)、设置禁止root通过ssh远程登录【修改之前请</div> </li> <li><a href="/article/1891106647942688768.htm" title="推荐开源备份神器:BackupPC,企业级数据守护者" target="_blank">推荐开源备份神器:BackupPC,企业级数据守护者</a> <span class="text-muted">徐耘馨</span> <div>推荐开源备份神器:BackupPC,企业级数据守护者项目地址:https://gitcode.com/gh_mirrors/ba/backuppc在数字时代,数据如同企业的生命线,重要性不言而喻。为了保障这份“生命线”的安全,今天我们来探讨一个高效且成熟的备份解决方案——BackupPC。项目介绍BackupPC是一款基于Perl编写的高性价比企业级备份系统,专为Linux、Windows和Mac</div> </li> <li><a href="/article/1891091771623010304.htm" title="VS中C/C++的编译流程(非常详细)" target="_blank">VS中C/C++的编译流程(非常详细)</a> <span class="text-muted">清泓y</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/qt/1.htm">qt</a><a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a> <div>文章目录前言一、.CPP文件和.OBJ文件1..CPP文件2..obj文件二、编译过程三、链接过程四、C/C++中提供的一些特性简要说明编译流程前言编译过程就是将源文件中的源代码翻译成机器语言,保存到目标文件中。如果编译通过,就会把CPP文件转换成OBJ文件一、.CPP文件和.OBJ文件1..CPP文件每个cpp就是一个编译单元,每个编译单元相互之间是独立且相互不知的。一个编译单元是指一个.CPP</div> </li> <li><a href="/article/1891090887568584704.htm" title="Open3D C++系列教程 (七)继承窗口类" target="_blank">Open3D C++系列教程 (七)继承窗口类</a> <span class="text-muted">吉拉尔</span> <a class="tag" taget="_blank" href="/search/Open3D-GUI/1.htm">Open3D-GUI</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/gui/1.htm">gui</a><a class="tag" taget="_blank" href="/search/open3d/1.htm">open3d</a> <div>Open3DC++系列教程(七)继承窗口类前置:Open3DC++系列教程(一)环境搭建Open3DC++系列教程(二)第一个GUI窗口Open3DC++系列教程(三)关于程序异常退出的探讨Open3DC++系列教程(四)动画Tick事件Open3DC++系列教程(五)创建菜单栏Open3DC++系列教程(六)菜单栏-文件拾取在之前的几节中介绍了直接在main中使用gui::Window和gui:</div> </li> <li><a href="/article/1891090380359790592.htm" title="【C++】STL之string类源码剖析" target="_blank">【C++】STL之string类源码剖析</a> <span class="text-muted">AllinTome</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/STL/1.htm">STL</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1/1.htm">类与对象</a><a class="tag" taget="_blank" href="/search/string/1.htm">string</a> <div>目录概述源码MyString.htest.cpp概述string是字符串类,出现早于STL,不过string完全符合STL标准库的语法规则,故将string类也归于STL中string类实现的功能有字符串元素的随机访问、迭代器遍历、字符串追加/删减/查找、字符串随机插入、字符串扩容与修改长度、重载输入/输出运算符算法设计:利用构造临时对象、自定义swap函数,完成string对象的拷贝、赋值构造,</div> </li> <li><a href="/article/1891079794955710464.htm" title="解决Deepseek服务器繁忙的两种高效方案:本地部署与平替平台实测" target="_blank">解决Deepseek服务器繁忙的两种高效方案:本地部署与平替平台实测</a> <span class="text-muted">小真—</span> <a class="tag" taget="_blank" href="/search/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/1.htm">自然语言处理</a><a class="tag" taget="_blank" href="/search/ai/1.htm">ai</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a> <div>近期爆火的Deepseek访问量激增频繁出现服务器繁忙提示,严重影响工作效率。本人实测了两种有效解决方案,整理了出这份保姆级指南。方案一:本地化部署核心优势说白了就是模型部署在自己本地,只有自己一个人用了没人挤了,但是对电脑硬件要求高部署步骤详解环境准备系统要求:Linux/Windows10+(推荐Ubuntu20.04)硬件配置:NVIDIA显卡(显存≥8GB)、内存≥16GB安装依赖:Pyt</div> </li> <li><a href="/article/1891078660161597440.htm" title="Python 第三方库 PyQt5 的安装" target="_blank">Python 第三方库 PyQt5 的安装</a> <span class="text-muted">狐凄</span> <a class="tag" taget="_blank" href="/search/%E5%AE%9E%E4%BE%8B/1.htm">实例</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>目录前言PyQt5安装不同操作系统PyQt5安装一、Windows系统二、macOS系统三、Linux系统(以Ubuntu为例)安装PyQt5可能会遇到的问题一、环境相关问题二、依赖问题三、网络问题四、安装工具问题五、运行时问题六、环境配置问题七、安装源问题八、检查错误信息九、运行时错误十、尝试不同的安装方法问题解决环境相关问题一、Python版本兼容性问题二、操作系统特定问题三、依赖库问题四、环</div> </li> <li><a href="/article/1891078408083927040.htm" title="C++效率掌握之STL库:string底层剖析" target="_blank">C++效率掌握之STL库:string底层剖析</a> <span class="text-muted">DARLING Zero two♡</span> <a class="tag" taget="_blank" href="/search/C%2B%2B%E5%88%9D%E9%98%B6/1.htm">C++初阶</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/stl/1.htm">stl</a><a class="tag" taget="_blank" href="/search/string/1.htm">string</a> <div>文章目录1.学习string底层的必要性2.string类对象基本函数实现3.string类对象的遍历4.string类对象的扩容追加5.string类对象的插入、删除6.string类对象的查找、提取、大小调整7.string类对象的流输出、流提取希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力!了解完string函数的主要用法,很有必要对string进行深层次的剖析,进一步了解其</div> </li> <li><a href="/article/1891077274405498880.htm" title="Linux 磁盘扩容" target="_blank">Linux 磁盘扩容</a> <span class="text-muted">zzq100zzq</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>一、查看系统磁盘1、使用df-hl,查看系统的磁盘使用情况二、linux磁盘扩容当LVM分区空间不足的时候,可以进行扩容,主要的扩容方法有:1、通过空余的磁盘进行扩容,这个方法比较简单,不会对原有数据有影响。2、将其他LVM分区空间取出来一部分给需要扩容的LVM分区。下面介绍两种方法三、利用空余磁盘扩容1、使用fdisk-l查看磁盘情况,看系统有几个硬盘。一般为:/dev/sda,/dev/sdb</div> </li> <li><a href="/article/1891076014545629184.htm" title="PVE 网络配置详解:双网卡聚合与 Linux 网络管理技巧" target="_blank">PVE 网络配置详解:双网卡聚合与 Linux 网络管理技巧</a> <span class="text-muted">The god of big data</span> <a class="tag" taget="_blank" href="/search/%E8%99%9A%E6%8B%9F%E7%B3%BB%E7%BB%9F/1.htm">虚拟系统</a><a class="tag" taget="_blank" href="/search/%E7%A5%9E%E5%99%A8%EF%BC%9F%E4%B8%89%E5%8F%89%E6%88%9F%EF%BC%9F/1.htm">神器?三叉戟?</a><a class="tag" taget="_blank" href="/search/%E6%95%99%E7%A8%8B/1.htm">教程</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>ProxmoxVE(PVE)作为基于Linux的虚拟化平台,其网络配置逻辑与Windows存在显著差异,尤其在多网卡管理、链路聚合(Bonding)等方面。本文将以双网卡聚合为核心,详解PVE的网络配置方法,并对比Windows帮助用户快速上手。一、Linux与Windows网络配置的核心区别1.网卡命名规则Windows:网卡名称为“以太网”“本地连接”等,可自定义。PVE(Linux):网卡默</div> </li> <li><a href="/article/1891074499709825024.htm" title="中国第一本介绍企业级开发工具Zend的书籍" target="_blank">中国第一本介绍企业级开发工具Zend的书籍</a> <span class="text-muted">weixin_34245749</span> <a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/1.htm">开发工具</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/ViewUI/1.htm">ViewUI</a> <div>【书名】大道PHP:LAMP+Zend+开源框架整合开发与实战【作者】三扬科技【ISBN】978-7-121-09171-1【出版社】电子工业出版社【出版日期】2009年9月【定价】99.00元(含视频DVD1张)【宣传语】第一本介绍企业级开发工具Zend的书籍最完整的PHP5开发技术与实战代码万用大全基于Linux+Apache+MySQL+PHP黄金组合,Socket、WebService、A</div> </li> <li><a href="/article/1891074247338553344.htm" title="-bash-3.2#怎么办?" target="_blank">-bash-3.2#怎么办?</a> <span class="text-muted">weixin_33910460</span> <a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/1.htm">操作系统</a> <div>系统登录后变成了这样,这样看着可不好看,其实是个小问题:原创博文来自:www.51niux.com博主:忙碌的柴少下面是造成这种问题的原因:我们平时在linux下切换用户后命令行为什么会变成-bash-3.2$呢,我们来分析一下,这就是跟linux的机制有关联了,因为在linux下每次通过useradd创建新的用户时,都会将所有的配置文件从/etc/skel复制到新用户的主目录下,一般默认在hom</div> </li> <li><a href="/article/1891073240051281920.htm" title="如何使用Java来编译运行C文件(一)" target="_blank">如何使用Java来编译运行C文件(一)</a> <span class="text-muted">FunriLy</span> <a class="tag" taget="_blank" href="/search/%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%AF%91/1.htm">在线编译</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%A0%81%E5%86%9C/1.htm">码农</a><a class="tag" taget="_blank" href="/search/cmd/1.htm">cmd</a> <div>如何使用Java来编译运行C文件(一)前言码农的小日子过得好好的,指导老师一个兴起要求搞一个自己的在线编译网站,我们这种做小弟的只能老老实实地去搞。还好刚刚结束了考试与比赛,因为各种原因导致原定于寒假开工的项目延迟到下学期了,刚好趁这段空闲的时间来搞一搞。其实,自己感觉搞这个的话也挺好玩的~前期技术准备部分1.调用cmd编译C文件先说明一下,我的操作系统是Win10,Linux环境下会有所不同;而</div> </li> <li><a href="/article/1891072862001885184.htm" title="国产编辑器EverEdit - 二进制模式下观察Window/Linux/MacOs换行符差异" target="_blank">国产编辑器EverEdit - 二进制模式下观察Window/Linux/MacOs换行符差异</a> <span class="text-muted">彩虹小黑馬</span> <a class="tag" taget="_blank" href="/search/%E5%A6%99%E7%94%A8%E7%BC%96%E8%BE%91%E5%99%A8/1.htm">妙用编辑器</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E8%BE%91%E5%99%A8/1.htm">编辑器</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/macos/1.htm">macos</a><a class="tag" taget="_blank" href="/search/EverEdit/1.htm">EverEdit</a><a class="tag" taget="_blank" href="/search/EmEditor/1.htm">EmEditor</a><a class="tag" taget="_blank" href="/search/Notepad/1.htm">Notepad</a> <div>1换行符格式1.1应用场景  稍微了解计算机历史的人都知道,计算机3大操作系统:Windows、Linux/Unix、MacOS,这3大系统对文本换行的定义各不相同,且互不相让,导致在文件的兼容性方面存在一些问题,比如它们对换行的定义:Windows:回车符(CR)+换行符(LF)Linux/Unix:换行符(LF)MacOS:回车符(CR)在Windows上编写的代码,默认使用CR+LF表示换行</div> </li> <li><a href="/article/1891070338968645632.htm" title="【Unix/Linux】$bash-3.2是什么" target="_blank">【Unix/Linux】$bash-3.2是什么</a> <span class="text-muted">程序员赵大宝</span> <a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/unix/1.htm">unix</a> <div>bash-3.2指的是BourneAgainShell(Bash)的3.2版本。Bash是一个广泛使用的Unixshell和命令语言,是GNU项目的一部分,也是许多Linux发行版和Unix系统的默认shell。以下是一些关于Bash3.2的要点:1.兼容性:Bash3.2是Bash的一个较旧版本,但它仍然提供了与早期Unixshell(如BourneShell)的兼容性。2.特性:尽管Bash3</div> </li> <li><a href="/article/1891067942339145728.htm" title="Linux 磁盘扩容:常见问题及解决方案详解(附操作步骤与代码)" target="_blank">Linux 磁盘扩容:常见问题及解决方案详解(附操作步骤与代码)</a> <span class="text-muted">梦想编码家</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/5G/1.htm">5G</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>前言在Linux运维过程中,磁盘空间不足是常见的运维问题之一,特别是在服务器长时间运行后,磁盘的分区可能无法满足新的业务需求。因此,合理地扩容磁盘显得尤为重要。本文将针对Linux系统中的磁盘扩容问题,提供一套完整的、详细的操作步骤,并附带相关代码,帮助您轻松应对这一运维难题。常见问题描述1.磁盘空间不足导致服务中断系统运行一段时间后,磁盘空间被消耗殆尽,导致无法再写入数据,从而影响服务的正常运行</div> </li> <li><a href="/article/1891058366046859264.htm" title="3.2 > Bash" target="_blank">3.2 > Bash</a> <span class="text-muted">irisart</span> <a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A5%E5%9C%9F/1.htm">从入门到入土</a><a class="tag" taget="_blank" href="/search/bash/1.htm">bash</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>概览在上一节中我们了解了关于Shell的执行流程,知道了在Linux环境中一般有哪些常用的Shell。而在本节中,将会学习到Linux中最常见的一个Shell——Bash,了解到bash的相关知识和用法。本节目录概览相关知识bash命令提示符bash基本格式bash常用特殊符号特殊字符:通配符:逻辑运算符:重定向符:命令替换符:变量和环境符:引号符:其他符号:bash有哪些常用配置文件可能常用的系</div> </li> <li><a href="/article/1891058239622148096.htm" title="轻量级CAD编辑器CADEditorX发布15.2新版本,新增3D模型爆炸视图新工具等" target="_blank">轻量级CAD编辑器CADEditorX发布15.2新版本,新增3D模型爆炸视图新工具等</a> <span class="text-muted">CodeCraft Studio</span> <a class="tag" taget="_blank" href="/search/3D%2F2D/1.htm">3D/2D</a><a class="tag" taget="_blank" href="/search/CAD/1.htm">CAD</a><a class="tag" taget="_blank" href="/search/%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86/1.htm">图像处理</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E8%BE%91%E5%99%A8/1.htm">编辑器</a><a class="tag" taget="_blank" href="/search/3d/1.htm">3d</a><a class="tag" taget="_blank" href="/search/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/1.htm">计算机视觉</a> <div>CADEditorX是一个ActiveX组件,用于在支持ActiveX和COM技术的任何开发环境中,将CAD功能添加到网页或正在开发的应用程序中。同时可以查看、编辑、转换、打印和测量DWG、DXF、SVG、HPGL、PDF、STEP、IGES、STL和其他CAD文件。CADEditorX15.2版本现已全新发布,包含许多增强功能和有价值的新功能。下面,让我们看看新版本都有哪些更新:CADEdito</div> </li> <li><a href="/article/1891050798163488768.htm" title="内核的驱动模块化编程框架" target="_blank">内核的驱动模块化编程框架</a> <span class="text-muted">Andy.w</span> <a class="tag" taget="_blank" href="/search/%E5%9F%BA%E4%BA%8ELinux%E7%9A%84%E9%A9%B1%E5%8A%A8%E5%AD%A6%E4%B9%A0/1.htm">基于Linux的驱动学习</a><a class="tag" taget="_blank" href="/search/%E5%86%85%E6%A0%B8%E6%A8%A1%E5%9D%97%E5%8C%96%E7%BC%96%E7%A8%8B/1.htm">内核模块化编程</a> <div>1、程序的两大空间:内核层和用户层正常写的代码都是运行在用户层,很难或者根本接触不到内核层。并且用户层看到的地址都是虚拟地址,所以用户层访问不了硬件。内核层离硬件近,虽然它的地址也不是真是的物理地址,但是它能间接访问操作真实的物理地址。其中真实的物理地址代表着硬件。2、内核层驱动的框架#include"linux/kernel.h"#include"linux/module.h"//就相当于标准头</div> </li> <li><a href="/article/1891047770710667264.htm" title="C++结构体" target="_blank">C++结构体</a> <span class="text-muted">饼干帅成渣</span> <a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>注:代码为测试代码,不可运行什么是结构体?在C++中,结构体(struct)是一种用户自定义的数据类型,它允许将不同类型的数据组合在一起形成一个整体。通过结构体,可以创建复杂的数据结构来表示现实世界中的对象或概念,这使得程序设计更加直观和易于理解。与类不同的是,结构体默认是公有继承(public),并且成员变量默认也是公开的(public),而类则默认为私有继承(private)。结构体的声明要定</div> </li> <li><a href="/article/1891045880455294976.htm" title="AFL QEMU模式安装报错(afl-2.52b & qemu-2.10.0)[已解决]" target="_blank">AFL QEMU模式安装报错(afl-2.52b & qemu-2.10.0)[已解决]</a> <span class="text-muted">geniusle201</span> <a class="tag" taget="_blank" href="/search/afl/1.htm">afl</a><a class="tag" taget="_blank" href="/search/qemu/1.htm">qemu</a><a class="tag" taget="_blank" href="/search/afl-qemu/1.htm">afl-qemu</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E7%B3%8A%E6%B5%8B%E8%AF%95/1.htm">模糊测试</a><a class="tag" taget="_blank" href="/search/patch/1.htm">patch</a> <div>搭建afl-qemu模块的时候,碰到了如下2个报错:xxxxxxxx/qemu/linux-user/syscall.c:253:16:error:staticdeclarationof‘gettid’followsnon-staticdeclaration253|_syscall0(int,gettid)^~~~~~xxxxxxxxx/qemu/linux-user/syscall.c:184:</div> </li> <li><a href="/article/1891043230867320832.htm" title="WSL中安装python环境详解" target="_blank">WSL中安装python环境详解</a> <span class="text-muted">小蘑菇二号</span> <a class="tag" taget="_blank" href="/search/python/1.htm">python</a> <div>在WindowsSubsystemforLinux(WSL)上安装Python环境,按照以下步骤操作:确认WSL已安装并更新:首先,确保已经安装了WSL,并且您的WSL环境已经更新至最新状态。打开WSL终端:打开Windows的开始菜单,找到并启动WSL发行版(例如Ubuntu)。确认Python已安装:在WSL终端中,输入python3--version来确认Python是否已安装,并查看其版本</div> </li> <li><a href="/article/1891042726078640128.htm" title="Linux技巧:修复硬盘坏道" target="_blank">Linux技巧:修复硬盘坏道</a> <span class="text-muted">Hacker_Oldv</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a> <div>首先要验证硬盘是否有坏道,可以使用smartctl工具、sata-fsck命令以及Linuxsmartmontools软件包来查看硬盘状态。通过以下命令,可以检查硬盘硬件是否存在错误:$sudosmartctl-a/dev/sda如果检查结果显示出来有坏道,则可以尝试一下硬盘坏道修复方法:使用fdisk-l/dev/sda来检查硬盘坏道;如果显示出来接近空闲空间的坏道,可以使用下面的命令来修复:s</div> </li> <li><a href="/article/1891036805206175744.htm" title="LibreOffice转换word文档" target="_blank">LibreOffice转换word文档</a> <span class="text-muted">DreamBoy_W.W.Y</span> <a class="tag" taget="_blank" href="/search/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/1.htm">知识图谱</a><a class="tag" taget="_blank" href="/search/word/1.htm">word</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>目录一、前言二、LibreOffice转换的核心代码三、转换后的Pdf提取解析情况一、前言    对于word文档,这里介绍一种解析文本、提取图片的方式。具体技术方案:LibreOffice将docx转换为pdf+再pdfplumber按页提取文本。该方案说明:    LibreOffice是一个跨平台的开源办公套件,支持Linux、Windows和macOS操作系统。目前为止,LibreOffi</div> </li> <li><a href="/article/1891033021033803776.htm" title="go hive skynet_MMORPG游戏服务器技术选型参考-Go语言中文社区" target="_blank">go hive skynet_MMORPG游戏服务器技术选型参考-Go语言中文社区</a> <span class="text-muted">weixin_39908948</span> <a class="tag" taget="_blank" href="/search/go/1.htm">go</a><a class="tag" taget="_blank" href="/search/hive/1.htm">hive</a><a class="tag" taget="_blank" href="/search/skynet/1.htm">skynet</a> <div>游戏服务器一般追求稳定和效率,所以偏向于保守,使用的技术手段也是以已经过验证、开发人员最熟悉、能HOLD为主要前提。1、典型按场景分服设计开发语言:c++数据库:mysql架构:多个网关:维持与玩家间的SOCKET连接,可处理广播、断线重连等逻辑。一个或多个账号登陆验证服务器:处理登陆、排队等逻辑。多个场景服务器:处理在本地图上能解决的逻辑,如:打怪、玩家间战斗、接任务、完成任务等各种不需要跨地图</div> </li> <li><a href="/article/1891033020358520832.htm" title="linux mesa 教程,Ubuntu 13.04使用Mesa" target="_blank">linux mesa 教程,Ubuntu 13.04使用Mesa</a> <span class="text-muted">weixin_39799565</span> <a class="tag" taget="_blank" href="/search/Ubuntu/1.htm">Ubuntu</a><a class="tag" taget="_blank" href="/search/OpenGL/1.htm">OpenGL</a><a class="tag" taget="_blank" href="/search/CMake/1.htm">CMake</a><a class="tag" taget="_blank" href="/search/%E6%98%BE%E5%8D%A1%E4%BF%A1%E6%81%AF/1.htm">显卡信息</a><a class="tag" taget="_blank" href="/search/Mesa/1.htm">Mesa</a> <div>以前写过一些关于如何使用Mesa的文章,如今再试。Ubuntu13.04下有些东西已经变了。首先安装:sudoapt-getinstalllibgl1-mesa-devsudoapt-getinstalllibglu1-mesa-devsudoapt-getinstallfreeglut3-dev现在不用NetBeans了,用CMake创建工程。根目录下的CMakeLists.txt内容:cmak</div> </li> <li><a href="/article/1891026212617252864.htm" title="计数排序c++详解(看这一篇就够了)" target="_blank">计数排序c++详解(看这一篇就够了)</a> <span class="text-muted">Lucas55555555</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a> <div>计数排序(CountingSort)是一种非比较型的整数排序算法,适用于整数范围不大的数据排序。其基本思想是统计待排序数组中每个元素出现的次数,然后通过累加计数信息,将元素放回排序数组中。由于它是基于元素的出现频率来排序的,因此时间复杂度通常可以达到O(n),但它对元素的范围(即最大值)有要求。定义:计数排序通过统计每个元素出现的次数来实现排序,然后根据这些统计结果重建排序后的数组。它是一种稳定的</div> </li> <li><a href="/article/49.htm" title="辗转相处求最大公约数" target="_blank">辗转相处求最大公约数</a> <span class="text-muted">沐刃青蛟</span> <a class="tag" taget="_blank" href="/search/C%2B%2B/1.htm">C++</a><a class="tag" taget="_blank" href="/search/%E6%BC%8F%E6%B4%9E/1.htm">漏洞</a> <div>无言面对”江东父老“了,接触编程一年了,今天发现还不会辗转相除法求最大公约数。惭愧惭愧!   为此,总结一下以方便日后忘了好查找。   1.输入要比较的两个数a,b   忽略:2.比较大小(因为后面要的是大的数对小的数做%操作)   3.辗转相除(用循环不停的取余,如a%b,直至b=0)   4.最后的a为两数的最大公约数 &</div> </li> <li><a href="/article/176.htm" title="F5负载均衡会话保持技术及原理技术白皮书" target="_blank">F5负载均衡会话保持技术及原理技术白皮书</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/F5/1.htm">F5</a><a class="tag" taget="_blank" href="/search/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/1.htm">负载均衡</a> <div>一.什么是会话保持?        在大多数电子商务的应用系统或者需要进行用户身份认证的在线系统中,一个客户与服务器经常经过好几次的交互过程才能完成一笔交易或者是一个请求的完成。由于这几次交互过程是密切相关的,服务器在进行这些交互过程的某一个交互步骤时,往往需要了解上一次交互过程的处理结果,或者上几步的交互过程结果,服务器进行下</div> </li> <li><a href="/article/303.htm" title="Object.equals方法:重载还是覆盖" target="_blank">Object.equals方法:重载还是覆盖</a> <span class="text-muted">Cwind</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/generics/1.htm">generics</a><a class="tag" taget="_blank" href="/search/override/1.htm">override</a><a class="tag" taget="_blank" href="/search/overload/1.htm">overload</a> <div>本文译自StackOverflow上对此问题的讨论。 原问题链接   在阅读Joshua Bloch的《Effective Java(第二版)》第8条“覆盖equals时请遵守通用约定”时对如下论述有疑问: “不要将equals声明中的Object对象替换为其他的类型。程序员编写出下面这样的equals方法并不鲜见,这会使程序员花上数个小时都搞不清它为什么不能正常工作:” pu</div> </li> <li><a href="/article/430.htm" title="初始线程" target="_blank">初始线程</a> <span class="text-muted">15700786134</span> <div>      暑假学习的第一课是讲线程,任务是是界面上的一条线运动起来。            既然是在界面上,那必定得先有一个界面,所以第一步就是,自己的类继承JAVA中的JFrame,在新建的类中写一个界面,代码如下: public class ShapeFr</div> </li> <li><a href="/article/557.htm" title="Linux的tcpdump" target="_blank">Linux的tcpdump</a> <span class="text-muted">被触发</span> <a class="tag" taget="_blank" href="/search/tcpdump/1.htm">tcpdump</a> <div>用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具。 tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支 持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。 实用命令实例 默认启动 tcpdump 普通情况下,直</div> </li> <li><a href="/article/684.htm" title="安卓程序listview优化后还是卡顿" target="_blank">安卓程序listview优化后还是卡顿</a> <span class="text-muted">肆无忌惮_</span> <a class="tag" taget="_blank" href="/search/ListView/1.htm">ListView</a> <div>最近用eclipse开发一个安卓app,listview使用baseadapter,里面有一个ImageView和两个TextView。使用了Holder内部类进行优化了还是很卡顿。后来发现是图片资源的问题。把一张分辨率高的图片放在了drawable-mdpi文件夹下,当我在每个item中显示,他都要进行缩放,导致很卡顿。解决办法是把这个高分辨率图片放到drawable-xxhdpi下。 &nb</div> </li> <li><a href="/article/811.htm" title="扩展easyUI tab控件,添加加载遮罩效果" target="_blank">扩展easyUI tab控件,添加加载遮罩效果</a> <span class="text-muted">知了ing</span> <a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a> <div>(function () { $.extend($.fn.tabs.methods, { //显示遮罩 loading: function (jq, msg) { return jq.each(function () { var panel = $(this).tabs(&</div> </li> <li><a href="/article/938.htm" title="gradle上传jar到nexus" target="_blank">gradle上传jar到nexus</a> <span class="text-muted">矮蛋蛋</span> <a class="tag" taget="_blank" href="/search/gradle/1.htm">gradle</a> <div>原文地址: https://docs.gradle.org/current/userguide/maven_plugin.html configurations {     deployerJars } dependencies {     deployerJars "org.apache.maven.wagon</div> </li> <li><a href="/article/1065.htm" title="千万条数据外网导入数据库的解决方案。" target="_blank">千万条数据外网导入数据库的解决方案。</a> <span class="text-muted">alleni123</span> <a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>从某网上爬了数千万的数据,存在文本中。 然后要导入mysql数据库。 悲剧的是数据库和我存数据的服务器不在一个内网里面。。 ping了一下, 19ms的延迟。 于是下面的代码是没用的。 ps = con.prepareStatement(sql); ps.setString(1, info.getYear())............; ps.exec</div> </li> <li><a href="/article/1192.htm" title="JAVA IO InputStreamReader和OutputStreamReader" target="_blank">JAVA IO InputStreamReader和OutputStreamReader</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/JAVA.io%E6%93%8D%E4%BD%9C+%E5%AD%97%E7%AC%A6%E6%B5%81/1.htm">JAVA.io操作 字符流</a> <div>这是第三篇关于java.io的文章了,从开始对io的不了解-->熟悉--->模糊,是这几天来对文件操作中最大的感受,本来自己认为的熟悉了的,刚刚在回想起前面学的好像又不是很清晰了,模糊对我现在或许是最好的鼓励 我会更加的去学 加油!: JAVA的API提供了另外一种数据保存途径,使用字符流来保存的,字符流只能保存字符形式的流   字节流和字符的难点:a,怎么将读到的数据</div> </li> <li><a href="/article/1319.htm" title="MO、MT解读" target="_blank">MO、MT解读</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/GSM/1.htm">GSM</a> <div>MO= Mobile originate,上行,即用户上发给SP的信息。MT= Mobile Terminate,下行,即SP端下发给用户的信息; 上行:mo提交短信到短信中心下行:mt短信中心向特定的用户转发短信,你的短信是这样的,你所提交的短信,投递的地址是短信中心。短信中心收到你的短信后,存储转发,转发的时候就会根据你填写的接收方号码寻找路由,下发。在彩信领域是一样的道理。下行业务:由SP</div> </li> <li><a href="/article/1446.htm" title="五个JavaScript基础问题" target="_blank">五个JavaScript基础问题</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/call/1.htm">call</a><a class="tag" taget="_blank" href="/search/apply/1.htm">apply</a><a class="tag" taget="_blank" href="/search/this/1.htm">this</a><a class="tag" taget="_blank" href="/search/Hoisting/1.htm">Hoisting</a> <div>下面是五个关于前端相关的基础问题,但却很能体现JavaScript的基本功底。 问题1:Scope作用范围 考虑下面的代码:  (function() { var a = b = 5; })(); console.log(b); 什么会被打印在控制台上?  回答:         上面的代码会打印 5。 &nbs</div> </li> <li><a href="/article/1573.htm" title="【Thrift二】Thrift Hello World" target="_blank">【Thrift二】Thrift Hello World</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/Hello+world/1.htm">Hello world</a> <div>本篇,不考虑细节问题和为什么,先照葫芦画瓢写一个Thrift版本的Hello World,了解Thrift RPC服务开发的基本流程   1. 在Intellij中创建一个Maven模块,加入对Thrift的依赖,同时还要加上slf4j依赖,如果不加slf4j依赖,在后面启动Thrift Server时会报错 <dependency> </div> </li> <li><a href="/article/1700.htm" title="【Avro一】Avro入门" target="_blank">【Avro一】Avro入门</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/%E5%85%A5%E9%97%A8/1.htm">入门</a> <div>本文的目的主要是总结下基于Avro Schema代码生成,然后进行序列化和反序列化开发的基本流程。需要指出的是,Avro并不要求一定得根据Schema文件生成代码,这对于动态类型语言很有用。   1. 添加Maven依赖   <?xml version="1.0" encoding="UTF-8"?> <proj</div> </li> <li><a href="/article/1827.htm" title="安装nginx+ngx_lua支持WAF防护功能" target="_blank">安装nginx+ngx_lua支持WAF防护功能</a> <span class="text-muted">ronin47</span> <div>需要的软件:LuaJIT-2.0.0.tar.gz                   nginx-1.4.4.tar.gz          &nb</div> </li> <li><a href="/article/1954.htm" title="java-5.查找最小的K个元素-使用最大堆" target="_blank">java-5.查找最小的K个元素-使用最大堆</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div> import java.util.Arrays; import java.util.Random; public class MinKElement { /** * 5.最小的K个元素 * I would like to use MaxHeap. * using QuickSort is also OK */ public static void</div> </li> <li><a href="/article/2081.htm" title="TCP的TIME-WAIT" target="_blank">TCP的TIME-WAIT</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/socket/1.htm">socket</a> <div>原文连接: http://vincent.bernat.im/en/blog/2014-tcp-time-wait-state-linux.html 以下为对原文的阅读笔记 说明: 主动关闭的一方称为local end,被动关闭的一方称为remote end 本地IP、本地端口、远端IP、远端端口这一“四元组”称为quadruplet,也称为socket 1、TIME_WA</div> </li> <li><a href="/article/2208.htm" title="jquery ajax 序列化表单" target="_blank">jquery ajax 序列化表单</a> <span class="text-muted">coder_xpf</span> <a class="tag" taget="_blank" href="/search/Jquery+ajax+%E5%BA%8F%E5%88%97%E5%8C%96/1.htm">Jquery ajax 序列化</a> <div>   checkbox 如果不设定值,默认选中值为on;设定值之后,选中则为设定的值   <input type="checkbox" name="favor" id="favor" checked="checked"/> $("#favor&quo</div> </li> <li><a href="/article/2335.htm" title="Apache集群乱码和最高并发控制" target="_blank">Apache集群乱码和最高并发控制</a> <span class="text-muted">cuisuqiang</span> <a class="tag" taget="_blank" href="/search/apache/1.htm">apache</a><a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a><a class="tag" taget="_blank" href="/search/%E5%B9%B6%E5%8F%91/1.htm">并发</a><a class="tag" taget="_blank" href="/search/%E9%9B%86%E7%BE%A4/1.htm">集群</a><a class="tag" taget="_blank" href="/search/%E4%B9%B1%E7%A0%81/1.htm">乱码</a> <div>都知道如果使用Http访问,那么在Connector中增加URIEncoding即可,其实使用AJP时也一样,增加useBodyEncodingForURI和URIEncoding即可。 最大连接数也是一样的,增加maxThreads属性即可,如下,配置如下: <Connector maxThreads="300" port="8019" prot</div> </li> <li><a href="/article/2462.htm" title="websocket" target="_blank">websocket</a> <span class="text-muted">dalan_123</span> <a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a> <div>一、低延迟的客户端-服务器 和 服务器-客户端的连接 很多时候所谓的http的请求、响应的模式,都是客户端加载一个网页,直到用户在进行下一次点击的时候,什么都不会发生。并且所有的http的通信都是客户端控制的,这时候就需要用户的互动或定期轮训的,以便从服务器端加载新的数据。   通常采用的技术比如推送和comet(使用http长连接、无需安装浏览器安装插件的两种方式:基于ajax的长</div> </li> <li><a href="/article/2589.htm" title="菜鸟分析网络执法官" target="_blank">菜鸟分析网络执法官</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a> <div>  最近在论坛上看到很多贴子在讨论网络执法官的问题。菜鸟我正好知道这回事情.人道"人之患好为人师" 手里忍不住,就写点东西吧. 我也很忙.又没有MM,又没有MONEY....晕倒有点跑题. OK,闲话少说,切如正题. 要了解网络执法官的原理. 就要先了解局域网的通信的原理. 前面我们看到了.在以太网上传输的都是具有以太网头的数据包. </div> </li> <li><a href="/article/2716.htm" title="Android相对布局属性全集" target="_blank">Android相对布局属性全集</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/android/1.htm">android</a> <div>RelativeLayout布局android:layout_marginTop="25dip" //顶部距离android:gravity="left" //空间布局位置android:layout_marginLeft="15dip //距离左边距 // 相对于给定ID控件android:layout_above 将该控件的底部置于给定ID的</div> </li> <li><a href="/article/2843.htm" title="Tomcat内存设置详解" target="_blank">Tomcat内存设置详解</a> <span class="text-muted">eksliang</span> <a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a><a class="tag" taget="_blank" href="/search/tomcat%E5%86%85%E5%AD%98%E8%AE%BE%E7%BD%AE/1.htm">tomcat内存设置</a> <div>Java内存溢出详解   一、常见的Java内存溢出有以下三种:   1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出JVM在启动的时候会自动设置JVM Heap的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。 可以利用JVM提</div> </li> <li><a href="/article/2970.htm" title="Java6 JVM参数选项" target="_blank">Java6 JVM参数选项</a> <span class="text-muted">greatwqs</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/HotSpot/1.htm">HotSpot</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/jvm%E5%8F%82%E6%95%B0/1.htm">jvm参数</a><a class="tag" taget="_blank" href="/search/JVM+Options/1.htm">JVM Options</a> <div>Java 6 JVM参数选项大全(中文版)   作者:Ken Wu Email: ken.wug@gmail.com 转载本文档请注明原文链接 http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm!   本文是基于最新的SUN官方文档Java SE 6 Hotspot VM Opt</div> </li> <li><a href="/article/3097.htm" title="weblogic创建JMC" target="_blank">weblogic创建JMC</a> <span class="text-muted">i5land</span> <a class="tag" taget="_blank" href="/search/weblogic/1.htm">weblogic</a><a class="tag" taget="_blank" href="/search/jms/1.htm">jms</a> <div>进入 weblogic控制太 1.创建持久化存储 --Services--Persistant Stores--new--Create FileStores--name随便起--target默认--Directory写入在本机建立的文件夹的路径--ok 2.创建JMS服务器 --Services--Messaging--JMS Servers--new--name随便起--Pers</div> </li> <li><a href="/article/3224.htm" title="基于 DHT 网络的磁力链接和BT种子的搜索引擎架构" target="_blank">基于 DHT 网络的磁力链接和BT种子的搜索引擎架构</a> <span class="text-muted">justjavac</span> <a class="tag" taget="_blank" href="/search/DHT/1.htm">DHT</a> <div>上周开发了一个磁力链接和 BT 种子的搜索引擎 {Magnet & Torrent},本文简单介绍一下主要的系统功能和用到的技术。 系统包括几个独立的部分: 使用 Python 的 Scrapy 框架开发的网络爬虫,用来爬取磁力链接和种子; 使用 PHP CI 框架开发的简易网站; 搜索引擎目前直接使用的 MySQL,将来可以考虑使</div> </li> <li><a href="/article/3351.htm" title="sql添加、删除表中的列" target="_blank">sql添加、删除表中的列</a> <span class="text-muted">macroli</span> <a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a> <div>添加没有默认值:alter table Test add BazaarType char(1) 有默认值的添加列:alter table Test add BazaarType char(1) default(0) 删除没有默认值的列:alter table Test drop COLUMN BazaarType 删除有默认值的列:先删除约束(默认值)alter table Test DRO</div> </li> <li><a href="/article/3478.htm" title="PHP中二维数组的排序方法" target="_blank">PHP中二维数组的排序方法</a> <span class="text-muted">abc123456789cba</span> <a class="tag" taget="_blank" href="/search/%E6%8E%92%E5%BA%8F/1.htm">排序</a><a class="tag" taget="_blank" href="/search/%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84/1.htm">二维数组</a><a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div><?php/*** @package     BugFree* @version     $Id: FunctionsMain.inc.php,v 1.32 2005/09/24 11:38:37 wwccss Exp $*** Sort an two-dimension array by some level </div> </li> <li><a href="/article/3605.htm" title="hive优化之------控制hive任务中的map数和reduce数" target="_blank">hive优化之------控制hive任务中的map数和reduce数</a> <span class="text-muted">superlxw1234</span> <a class="tag" taget="_blank" href="/search/hive/1.htm">hive</a><a class="tag" taget="_blank" href="/search/hive%E4%BC%98%E5%8C%96/1.htm">hive优化</a> <div>一、    控制hive任务中的map数: 1.    通常情况下,作业会通过input的目录产生一个或者多个map任务。 主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改);2. </div> </li> <li><a href="/article/3732.htm" title="Spring Boot 1.2.4 发布" target="_blank">Spring Boot 1.2.4 发布</a> <span class="text-muted">wiselyman</span> <a class="tag" taget="_blank" href="/search/spring+boot/1.htm">spring boot</a> <div>Spring Boot 1.2.4已于6.4日发布,repo.spring.io and Maven Central可以下载(推荐使用maven或者gradle构建下载)。   这是一个维护版本,包含了一些修复small number of fixes,建议所有的用户升级。   Spring Boot 1.3的第一个里程碑版本将在几天后发布,包含许多</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>