[项目详解][boost搜索引擎#2] 建立index | 安装分词工具cppjieba | 实现倒排索引

目录

编写建立索引的模块 Index

1. 设计节点

2.基本结构

3.(难点) 构建索引

1. 构建正排索引(BuildForwardIndex)

2.❗构建倒排索引

3.1 cppjieba分词工具的安装和使用

3.2 引入cppjieba到项目中

倒排索引代码


本篇文章,我们将继续项目的部分讲解,思路如下

在讲解 index 前,我们先 接着上篇文章 来看一下保存 html 文件

采用下面的方案:

  • 写入文件中,一定要考虑下一次在读取的时候,也要方便操作!
  • 类似:title\3 content\3url \n title\3content\3url \n title\3content\3url \n ...
  • 方便我们getline(ifsream, line),直接获取文档的全部内容:title\3content\3url

为什么使用‘\3’?

  • \3 在ASSCII码表中是不可以显示的字符,我们将title、content、url用\3进行区分,不会污染我们的文档,当然你也可以使用\4等
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
    #define SEP '\3'//分割符---区分标题、内容和网址

    // 打开文件,在里面进行写入
    // 按照二进制的方式进行写入 -- 你写的是什么文档就保存什么
    std::ofstream out(output, std::ios::out | std::ios::binary);
    if(!out.is_open())
    {
        std::cerr << "open " << output << " failed!" << std::endl;
        return false;
    }

    // 到这里就可以进行文件内容的写入了
    for(auto &item : results)
        {
            std::string out_string;
            out_string = item.title;//标题
            out_string += SEP;//分割符
            out_string += item.content;//内容
            out_string += SEP;//分割符
            out_string += item.url;//网址
            out_string += '\n';//换行,表示区分每一个文件

            // 将字符串内容写入文件中
            out.write(out_string.c_str(), out_string.size());
        }

    out.close();
    return true;
}
  • 接下来我们做一下测试
  • /data/raw_html目录下的 raw.txt 就会填入所有的处理完的 html 文档

尝试查看

至此,我们的parser(去标签+数据清)模块就完成了,为了大家能够更好的理解,下面是一张关系图:


编写建立索引的模块 Index

首先创建 一个 Index.hpp 文件,用来编写 索引模块

该文件主要负责三件事:① 构建索引② 正排索引③ 倒排索引

构建思路框图:

搜索引擎逻辑:
用户输入【关键字】搜索 【倒排索引】搜出 【倒排拉链】 倒排拉链中包含所有关键字有关的文档ID及其权重 → 【正派索引】文档ID,得到文档三元素 , 并按照权重排列呈现给用户

1. 设计节点

  • 正排索引是构建倒排索引的基础
  • 通过给到的关键字,去倒排索引里查找出文档ID,再根据文档ID,找到对应的文档内容
  • 两个节点结构,一个是文档信息的节点,一个是倒排对应的节点;
namespace ns_index
{
    struct DocInfo //文档信息节点
    {
        std::string title;    //文档的标题
        std::string content;  //文档对应的去标签后的内容
        std::string url;      //官网文档的url
        uint64_t doc_id;      //文档的ID
    };
 
    struct InvertedElem //倒排对应的节点
    {
        uint64_t doc_id;      //文档ID
        std::string word;     //关键字(通过关键字可以找到对应的ID)
        int weight;           //权重---根据权重对文档进行排序展示
    };
}

说明:

  • doc_id、wordweight;我们可以通过 word 关键字找到对应的文档ID
  • 有文档的信息节点,通过倒排找到的文档ID,就能够在文档信息节点中找到对应的文档所有内容
  • 这两个节点都有doc_id,就像MySQL中外键,相当于两张表产生了关联;

2.基本结构

索引模块最大的两个部分当然是构建正排索引和构建倒排索引,其主要接口如下:

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>
#include <mutex>
#include "log.hpp"

namespace ns_index
{
    struct DocInfo // 文档信息节点
    {
        std::string title;   // 文档的标题
        std::string content; // 文档对应的去标签后的内容
        std::string url;     // 官网文档的url
        uint64_t doc_id;     // 文档的ID
    };

    struct InvertedElem // 倒排对应的节点
    {
        uint64_t doc_id;  // 文档ID
        std::string word; // 关键字(通过关键字可以找到对应的ID)
        int weight;       // 权重---根据权重对文档进行排序展示
    };
    // 倒排拉链--一个关键字对应了一组文档
    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    private:
        std::vector<DocInfo> forward_index; // 正排索引
        // 倒排索引
        std::unordered_map<std::string, InvertedList> inverted_index;

    public:
        Index() {}
        ~Index() {}

    public:
        // 根据去标签,格式化后的文档,构建正排和倒排索引
        // 将数据源的路径:data/raw_html/raw.txt传给input即可,这个函数用来构建索引
        bool BuildIndex(const std::string &input)
        {
            return true;
        }
        // 根据倒排索引的关键字word,获得倒排拉链
        InvertedList *GetInvertedList(const std::string &word)
        {
            //...
            return nullptr;
        }
        // 根据doc_id找到正排索引对应doc_id的文档内容
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            //...
            return nullptr;
        }
    };
}
  • GetInvertedList函数:根据倒排索引的关键字word,获得倒排拉链(和上面类似)
  • 返回第二个元素
//根据倒排索引的关键字word,获得倒排拉链
InvertedList* GetInvertedList(const std::string &word)
{
    // word关键字不是在 unordered_map 中,直接去里面找对应的倒排拉链即可
    auto iter = inverted_index.find(word);
    if(iter == inverted_index.end()) // 判断是否越界
    {
        std::cerr << " have no InvertedList" << std::endl;
        return nullptr;
    }
    // 返回 unordered_map 中的第二个元素--- 倒排拉链
    return &(iter->second);
}
  • GetForwardIndex函数:根据【正排索引】 doc_id 找到文档内容
  • 数组下标就是 doc_id
//根据doc_id找到正排索引对应doc_id的文档内容
DocInfo* GetForwardIndex(uint64_t doc_id)
{
    //如果这个doc_id已经大于正排索引的元素个数,则索引失败
    if(doc_id >= forward_index.size())
    {                                                                                                                                                         
          std::cout << "doc_id out range, error!" << std::endl;
          return nullptr;
    }
    return &forward_index[doc_id];//否则返回相应doc_id的文档内容
}

3.(难点) 构建索引

  • 构建索引的思路正好和用户使用搜索功能的过程正好相反。
  • 思路:一个一个文档正排遍历,为其每个构建先正排索引后构建倒排索引。

步骤:

  • 先把处理干净的文档读取上来,是按行读取,这样就能读到每个html文档
  • 按行读上来每个html文档后,我们就可以开始构建正排索引和倒排索引,此时就要提供两个函数,
  • 分别为BuildForwardIndex(构建正排索引)和 BuildInvertedIndex(构建倒排索引)

基本代码如下:

//根据去标签,格式化后的文档,构建正排和倒排索引
//将数据源的路径:data/raw_html/raw.txt传给input即可,这个函数用来构建索引
bool BuildIndex(const std::string &input)
{
    //在上面SaveHtml函数中,我们是以二进制的方式进行保存的,那么读取的时候也要按照二进制的方式读取,读取失败给出提示
    std::ifstream in(input, std::ios::in | std::ios::binary);
    if(!in.is_open())
    {
        std::cerr << "sory, " << input << " open error" << std::endl;
        return false;
    }
 
    std::string line;
    int count = 0;
    while(std::getline(in, line))
    {
        DocInfo* doc = BuildForwardIndex(line);//构建正排索引
 
        if(nullptr == doc)
        {
            std::cerr << "build " << line << " error" << std::endl;
            continue;
        }
 
        BuildInvertedIndex(*doc);//有了正排索引才能构建倒排索引
        count++;    
        if(count % 50 == 0)    
        {    
            std::cout << "当前已经建立的索引文档:" << count << "个" << std::endl;     
        }
    }
    return true;
}
  • vscode 下的搜索,ctrl+f
1. 构建正排索引(BuildForwardIndex)
  • 在编写构建正排索引的代码前,我们要知道,在构建索引的函数中,我们是按行读取了每个html文件的(每个文件都是这种格式:title\3content\3url...)构建正排索引,就是将DocInfo结构体内的字段进行填充,
  • 这里我们就需要给一个字符串切分的函数,我们写到util.hpp中,
  • 这里我们又要引入一个新的方法——boost库当中的切分字符串函数split;

代码如下:

class StringUtil
    {
    public:
        //切分字符串
        static void Splist(const std::string &target, std::vector<std::string> *out, const std::string &sep)
        {
            //boost库中的split函数
            boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
        }
};

boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);

  • 第一个参数:表示你要将切分的字符串放到哪里
  • 第二个参数:表示你要切分的字符串
  • 第三个参数:表示分割符是什么,不管是多个还是一个
  • 第四个参数:它是默认可以不传,即切分的时候不压缩,不压缩就是保留空格

如:字符串为aaaa\3\3bbbb\3\3cccc\3\3d

  • 如果不传第四个参数 结果为aaaa bbbb cccc d
  • 如果传第四个参数为boost::token_compress_on 结果为aaaabbbbccccd
  • 如果传第四个参数为boost::token_compress_off 结果为aaaa bbbb cccc d

构建正排索引的编写:

//构建正排索引
DocInfo* BuildForwardIndex(const std::string &line)
{
    // 1.解析line,字符串切分
    // 将line中的内容且分为3段:原始为title\3content\3url\3
    // 切分后:title content url
    std::vector<std::string> results;
    std::string sep = "\3"; //行内分隔符
    ns_util::StringUtil::Splist(line, &results, sep);//字符串切分                                                                                             
    if(results.size() != 3)                                             
    {                                                                   
        return nullptr;                                                 
    }                                                                   
                                                                                
    // 2.字符串进行填充到DocInfo                                        
    DocInfo doc;                                                        
    doc.title = results[0];                                             
    doc.content = results[1];                                           
    doc.url = results[2];                                               
    doc.doc_id = forward_index.size(); 
    //先进行保存id,在插入,对应的id就是当前doc在vector中的下标
                                                                                
    // 3.插入到正排索引的vector                                         
    forward_index.push_back(std::move(doc)); //使用move可以减少拷贝带来的效率降低
    return &forward_index.back();                                       
}
2.❗构建倒排索引

原理图:

总的思路:

  • 对 title 和 content 进行分词(使用cppjieba)
  • 在分词的时候,必然会有某些词在 title 和 content 中出现过;我们这里还需要做一个处理,就是对每个词进行词频统计(你可想一下,你在搜索某个关键字的时候,为什么有些文档排在前面,而有些文档排在最后)这主要是词和文档的相关性
  • 自定义相关性:我们有了词和文档的相关性的认识后,就要来自己设计这个相关性;我们把出现在title中的词,其权重更高,在content中,其权重低一些(如:让出现在title中的词的词频x10,出现在content中的词的词频x1,两者相加的结果称之为该词在整个文档中的权重)
  • 根据这个权重,我们就可以对所有文档进行权重排序,进行展示,权重高的排在前面展示,权重低的排在后面展示

我们之前的基本结构代码:

//倒排拉链节点
struct InvertedElem{
    uint64_t doc_id;  //文档的ID
    std::string word; //关键词
    int weight;       //权重
};
 
//倒排拉链
typedef std::vector<InvertedElem> InvertedList;
 
//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
std::unordered_map<std::string, InvertedList> inverted_index;
 
//文档信息节点
struct DocInfo{
    std::string title;   //文档的标题
    std::string content; //文档对应的去标签之后的内容
    std::string url;     //官网文档url
    uint64_t doc_id;     //文档的ID
};
  1. 需要对 title && content都要先分词 -- 使用jieba分词
  • title: 吃/葡萄/吃葡萄(title_word)
    content:吃/葡萄/不吐/葡萄皮(content_word)
  1. 词频统计 它是包含标题和内容的,我们就需要有一个结构体,来存储每一篇文档中每个词出现在title和content中的次数,伪代码如下:
//词频统计的结点
struct word_cnt
{
    int title_cnt;  //词在标题中出现的次数
    int content_cnt;//词在内容中出现的次数
}
  • 统计这些次数之后
  • 我们还需要将词频和关键词进行关联,文档中的每个词都要对应一个词频结构体,
  • 这样我们通过关键字就能找到其对应的词频结构体,
  • 通过这个结构体就能知道该关键字在文档中的title和content中分别出现了多少次,
  • 下一步就可以进行权重的计算。
  • 这里我们就可以使用数据结构unordered_map来进行存储。

伪代码如下:

//关键字和词频结构体的映射
std::unordered_map<std::string, word_cnt> word_map;
//关键字就能找到其对应的词频结构体
for(auto& word : title_word)
{
    // 一个关键词 对应 标题 中出现的次数
    word_map[word].title_cnt++; //吃(1)/葡萄(1)/吃葡萄(1)
}

//范围for进行遍历,对content中的词进行词频统计
for(auto& word : content_word)
{
    // 一个关键词 对应 内容 中出现的次数
    word_map[word].content_cnt++; //吃(1)/葡萄(1)/不吐(1)/葡萄皮(1)
}
  1. 自定义相关性

知道了在文档中,标题 和 内容 每个词出现的次数,接下来就需要我们自己来设计相关性了,伪代码如下:

//遍历刚才那个unordered_map<std::string, word_cnt> word_map;
for(auto& word : word_map)
{
    struct InvertedElem elem;//定义一个倒排拉链节点,然后填写相应的字段
    elem.doc_id = 123;
    elem.word = word.first;  // word.first-> 关键字
    elem.weight = 10*word.second.title_cnt + word.second.content_cnt ;//权重计算
    // 将关键字 对应的 倒排拉链节点 保存到 对应的倒排拉链这个 数组中
    inverted_index[word.first].push_back(elem);//最后保存到倒排索引的数据结构中
}
 
//倒排索引结构如下: 一个关键字 对应的 倒排拉链(一个或一组倒排节点)
//std::unordered_map<std::string, InvertedList> inverted_index;
 
//倒排索引结构体  -- 一个倒排拉链节点
struct InvertedElem
{
    uint64_t doc_id;   // 文档ID
    std::string word; // 文档相关关键字
    int weight;        // 文档权重
};
 
//倒排拉链
typedef std::vector<InvertedElem> InvertedList;

至此就是倒排索引比较完善的原理介绍和代码思路

3.1 cppjieba分词工具的安装和使用

获取链接:cppjieba 下载链接 里面有详细的教程

  • 我们这里可以使用 git clone,如下
  • git clone https://github.com/yanyiwu/cppjieba

  • 查看 cppjieba 目录,里面包含如下:

这是别人的写好的一个开源项目,我们来测试一下~

要注意头文件的路径,我们先来修改一下头文件的路径,它本身是要使用cppjieba/Jieba.hpp的,我们看一下这个头文件的具体路径:

路径是:cppjieba/include/cppjieba/Jieba.hpp

我们发现

我们可以找到开源项目中的测试文件来进行 test

引入这个头文件,我们不能直接引入,需要使用软连接:

尝试编译后发现

此时,我们还是需要对这个头文件进行软连接,我们通过查找,发现有这么一个路径:

但是里面什么东西都没有,这是我在联系项目中出现的问题,经过我去GitHub查找一番后,发现它在另外一个压缩包里:

下载好如下:

进入它里面,看是否有我们需要的内容

此时我们只需要将它 拷贝到 include/cppjieba/ 即可

cp -r limonp/include/limonp/ test/cppjieba/include/cppjieba/

我们将路径都完善之后,接下来,我们 选取局部测试代码 编译运行一下

int main(int argc, char** argv) 
{
  cppjieba::Jieba jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH);  
    vector<string> words;  // 接收切分的词
    string s;
    s = "小明硕士毕业于中国科学院计算所";
    cout << "原句:" << s << endl;
    jieba.CutForSearch(s, words);
    cout << "分词后:";
    for (auto& word : words)
    {
        cout << word << " | ";
    }
    cout << endl;
}

熟悉完上面的操作后,就可以在我们的项目中引入头文件,来使用cppjieba分词工具啦~

3.2 引入cppjieba到项目中

将软链接建立好之后,我们在util.hpp中编写一个jieba分词的类,主要是为了方便后期其他地方需要使用的时候,可以直接调用。

首先我们先看一下之前的测试代码: 可以发现分词用到的 词库和函数

我们在util.hpp中创建一个 JiebaUtil的分词工具类,建立软连接

util.hpp代码如下:

#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <vector>

#include <boost/algorithm/string.hpp>
#include "cppjieba/Jieba.hpp" 

 
namespace ns_util
{
    class FileUtil
    {                                                                                                                                                                                                                                                                                                                                                                        
    public:
        //输入文件名,将文件内容读取到out中
        static bool ReadFile(const std::string &file_path, std::string *out)
        {
            // 读取 file_path(一个.html文件) 中的内容  -- 打开文件
            std::ifstream in(file_path, std::ios::in);
            //文件打开失败检查
            if(!in.is_open())
            {
                std::cerr << "open file " << file_path << " error" << std::endl;
                return false;
            }
            //读取文件内容
            std::string line;
            //while(bool),getline的返回值istream会重载操作符bool,读到文件尾eofset被设置并返回false
            //如何理解getline读取到文件结束呢??getline的返回值是一个&,while(bool), 本质是因为重载了强制类型转化
            while(std::getline(in, line)) // 每循环一次,读取的是文件的一行内容
            {
                *out += line;    // 将文件内容保存在 *out 里面
            }
            in.close(); // 关掉文件
            return true;
        }
    };

   class StringUtil
    {
    public:
        //切分字符串
        static void Splist(const std::string &target, std::vector<std::string> *out, const std::string &sep)
        {
            //boost库中的split函数
            boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
            }
    };
 //下面这5个是分词时所需要的词库路径
    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 JiebaUtil    
    {    
    private:    
        static cppjieba::Jieba jieba; //定义静态的成员变量(需要在类外初始化)   
    public:    
        static void CutString(const std::string &src, std::vector<std::string> *out)    
        {   
            //调用CutForSearch函数,第一个参数就是你要对谁进行分词,第二个参数就是分词后的结果存放到哪里
            jieba.CutForSearch(src, *out);    
        }     
    };
 
    //类外初始化,就是将上面的路径传进去,具体和它的构造函数是相关的,具体可以去看一下源代码
    cppjieba::Jieba JiebaUtil::jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH);
}

倒排索引代码

构建倒排索引相对复杂一些,只要将上面倒排索引的原理和伪代码的思路;理解到位后,下面的代码就比较简单了。

//构建倒排索引
bool BuildInvertedIndex(const DocInfo &doc)    
{   
    //词频统计结构体 
    struct word_cnt    
    {    
        int title_cnt;    
        int content_cnt;    
        word_cnt():title_cnt(0), content_cnt(0){}    
    };    
    std::unordered_map<std::string, word_cnt> word_map; //用来暂存词频的映射表    
    
    //对标题进行分词    
    std::vector<std::string> title_words;    
    ns_util::JiebaUtil::CutString(doc.title, &title_words);      
    
    //对标题进行词频统计       
    for(auto s : title_words)     
    {    
        boost::to_lower(s); // 将我们的分词进行统一转化成为小写的    
        word_map[s].title_cnt++;//如果存在就获取,不存在就新建    
    }    
    
    //对文档内容进行分词    
    std::vector<std::string> content_words;    
    ns_util::JiebaUtil::CutString(doc.content, &content_words);    
      
    
    //对文档内容进行词频统计       
    for(auto s : content_words)      
    {    
        boost::to_lower(s); // 将我们的分词进行统一转化成为小写的    
        word_map[s].content_cnt++;    
    }    
    
#define X 10    
#define Y 1  
    //最终构建倒排  
    for(auto &word_pair : word_map)    
    {    
        InvertedElem item;    
        item.doc_id = doc.doc_id; //倒排索引的id即文档id   
        item.word = word_pair.first;    
        item.weight = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;    
        InvertedList& inverted_list = inverted_index[word_pair.first];    
        inverted_list.push_back(std::move(item));    
     }    
     return true;    
}    

下篇文章将继续对项目进行讲解~


补充:

写项目突然想到一个有意思的点~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/901795.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android——事件冲突处理

当我们给列表的item设置了点击事件后&#xff0c;又给item中的按钮设置了点击事件&#xff0c;此时item的点击事件会失效。 解决 给item的布局xml中设置以下属性 android:descendantFocusability"blocksDescendants"<LinearLayout xmlns:android"http://sc…

005:航空力学基础、无人机操纵、飞机性能

摘要&#xff1a;本文详细介绍无人机稳定性、操控性、飞机性能等概念。 一、飞机的稳定性 概念&#xff1a; 飞机的稳定性&#xff08;安定性&#xff09;&#xff0c;是指在飞机受到扰动后&#xff0c;不经飞行员操纵&#xff0c;能恢复到受扰动前的原始状态&#xff08;即原…

Android系统架构

Android系统架构&#xff1a; Android系统架构是一个复杂的、分层的结构&#xff0c;旨在提供高度的灵活性和可扩展性。这个架构可以大致分为以下几个主要层次&#xff1a; Linux Kernel&#xff08;Linux内核&#xff09;&#xff1a; Linux内核是Android系统的底层&#xff0…

OAK相机的RGB-D彩色相机去畸变做对齐

▌低畸变标准镜头的OAK相机RGB-D对齐的方法 OAK相机内置的RGB-D管道会自动将深度图和RGB图对齐。其思想是将深度图像中的每个像素与彩色图像中对应的相应像素对齐。产生的RGB-D图像可以用于OAK内置的图像识别模型将识别到的2D物体自动映射到三维空间中去&#xff0c;或者产生的…

OpenSSL

OpenSSL 概述 OpenSSL 是一个开源的、安全传输协议实现工具&#xff0c;广泛应用于数据加密与解密、证书生成与管理以及其他安全性相关的任务。在现代网络安全中&#xff0c;OpenSSL 被用于构建和维护 SSL/TLS 通信&#xff0c;确保数据在传输过程中的机密性和完整性。 简单来…

西圣和绿联电容笔哪款好用?西圣、绿联和uhb电容笔真实避坑测评!

现在市面上的平替电容笔种类非常多&#xff0c;在购买的时候大多数人都没有非常确定的目标&#xff0c;这主要是因为大多数人对电容笔的认识程度不够。 作为一个有着多年数码产品测评经验的测评员&#xff0c;我刚好对电容笔也有比较深刻的理解&#xff0c;也借着大家问我关于…

ASP.NET MVC-font awesome-localhost可用IIS不可用

环境&#xff1a; win10, .NET 6.0&#xff0c;IIS 问题描述 本地IIS正常显示&#xff0c;但放到远程服务器上&#xff0c;每个icon都显示?。同时浏览器的控制台报错&#xff1a; fontawesome-webfont.woff2:1 Failed to load resource: the server responded with a statu…

【网络协议栈】Tcp协议(上)结构的解析 和 Tcp中的滑动窗口(32位确认序号、32位序号、4位首部长度、6位标记位、16为窗口大小、16位紧急指针)

绪论​ “没有那么多天赋异禀&#xff0c;优秀的人总是努力翻山越岭。”本章主要讲到了再五层网络协议从上到下的第二层传输层中使用非常广泛的Tcp协议他的协议字段结构&#xff0c;通过这些字段去认识其Tcp协议运行的原理底层逻辑和基础。后面将会再写一篇Tcp到底是通过什么调…

java实现的音视频格式转化器

一、前言 最近写了一款图形界面版的音视频格式转化器&#xff0c;可以实现将多种视频之间进行转化&#xff0c;非常好用&#xff0c;如将AVI转换为&#xff0c;TS&#xff0c;FLV&#xff0c;MP4等。音频可将MP3转成WAV。 二、实现 1.需引入相关maven依赖。 <!-- 核心包 -…

群控系统服务端开发模式-应用开发-业务架构逻辑开发准备工作

安装与仓库已经调整完毕&#xff0c;现在开发业务架构逻辑&#xff0c;其次再开发功能逻辑。业务架构逻辑开发与功能逻辑开发不是一回事&#xff0c;一定要明白。业务架构指的是做某一件事或是某一种类型的事的逻辑&#xff0c;在互联网web应用中通常指一套系统的外在逻辑&…

知识见闻 - 磁力片原理

磁力片是一种利用磁性原理设计的玩具&#xff0c;它的工作原理和磁性方向的排列方式非常有趣。让我们深入了解一下磁力片的核心原理和磁性方向的特点。 磁力片的基本原理 磁力片的工作原理基于磁铁的基本特性。每个磁力片都包含多个小磁铁&#xff0c;这些磁铁被精心排列&#…

强化学习数学原理学习(一)

前言 总之开始学! 正文 先从一些concept开始吧,有一个脉络比较好 state 首先是就是状态和状态空间,显而易见,不多说了 action 同理,动作和动作空间 state transition 状态转换,不多说 policy 策略,不多说 reward 奖励,不多说 MDP(马尔科夫) 这里需要注意到就是这个是无…

小程序视频SDK解决方案,提供个性化开发和特效定制设计

美摄科技作为视频处理技术的领航者&#xff0c;深知在这一变革中&#xff0c;每一个细微的创新都能激发无限可能。因此&#xff0c;我们精心打造了一套小程序视频SDK解决方案&#xff0c;旨在满足不同行业、不同规模客户的多元化需求&#xff0c;携手共创视频内容的璀璨未来。 …

守护头顶安全——AI高空抛物监测,让悲剧不再重演

在城市的喧嚣中&#xff0c;我们享受着高楼林立带来的便捷与繁华&#xff0c;却往往忽视了那些隐藏在高空中的危险。近日&#xff0c;震惊全国的高空抛物死刑案件被最高院核准并执行。案件中被告人多次高空抛物的举动&#xff0c;夺去了无辜者的生命&#xff0c;也让自己付出了…

Leetcode11:盛水最多的容器

原题地址&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳…

Docker本地安装Minio对象存储

Docker本地安装Minio对象存储 1. 什么是 MinIO&#xff1f; MinIO 是一个开源的对象存储服务器。这意味着它允许你在互联网上存储大量数据&#xff0c;比如文件、图片、视频等&#xff0c;而不需要依赖传统的文件系统。MinIO 的特点在于它非常灵活、易于使用&#xff0c;同时…

Pr 视频效果:波形变形

视频效果/扭曲/波形变形 Distort/Wave Warp 波形变形 Wave Warp效果用于在剪辑上创建类似波浪的动态变形效果。 此效果会自动动画化&#xff0c;波形以恒定速度移动。要改变速度或停止波动&#xff0c;需要设置关键帧。 ◆ ◆ ◆ 效果选项说明 通过调整波形的类型、高度、宽度…

(六)问题记录,simulink仿真出现模型碰撞后穿越

明明已经设置了车轮和地面之间的 spatial contact force&#xff0c;可是还会出现模型穿越&#xff08;重力作用下自由落体&#xff09;&#xff0c;如下图所示。 可仅降低小车初始高度后就不会穿越&#xff0c;如下图所示。 初步怀疑是小车初始高度大的话&#xff0c;小车在和…

【原创】统信UOS如何安装最新版Node.js(20.x)

注意直接使用sudo apt install nodejs命令安装十有八九会预装10.x的老旧版本Node.js&#xff0c;如果已经安装的建议删除后安装如下方法重装。 在统信UOS系统中更新Node.js可以通过以下步骤进行&#xff1a; 1. 卸载当前版本的Node.js 首先&#xff0c;如果系统中已经安装了N…

Kafka消费者故障,出现活锁问题如何解决?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka消费者故障&#xff0c;出现活锁问题如何解决&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; Kafka消费者故障&#xff0c;出现活锁问题如何解决&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资…