分布式搜索和分析引擎Elasticsearch實戰指南

ES 介紹與安裝

Elasticsearch, 簡稱 ES,它是個開源分布式搜索引擎,它的特點有:分布式,零配置,自動發現,索引自動分片,索引副本機制,restful 風格接口,多數據源,自動搜索負載等。它可以近乎實時的存儲、檢索數據;本身擴展性很好,可以擴展到上百臺服務器,處理 PB 級別的數據。es 也使用 Java 開發并使用 Lucene 作為其核心來實現所有索引和搜索的功能,但是它的目的是通過簡單的 RESTful API 來隱藏 Lucene 的復雜性,從而讓全文搜索變得簡單。

Elasticsearch 是面向文檔(document oriented)的,這意味著它可以存儲整個對象或文檔(document)。然而它不僅僅是存儲,還會索引(index)每個文檔的內容使之可以被搜索。在 Elasticsearch 中,你可以對文檔(而非成行成列的數據)進行索引、搜索、排序、過濾。

ES 安裝

# 添加倉庫秘鑰
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
# 上邊的添加方式會導致一個 apt-key 的警告,如果不想報警告使用下邊這個
curl -s https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/icsearch.gpg --import
# 添加鏡像源倉庫
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elasticsearch.list
# 更新軟件包列表
sudo apt update
# 安裝 es
sudo apt-get install elasticsearch=7.17.21
# 啟動 es
sudo systemctl start elasticsearch
# 安裝 ik 分詞器插件
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/7.17.21

若 apt update 更新源報錯:

安裝 kibana

使用 apt 命令安裝 Kibana。

sudo apt install kibana

sudo apt install kibana配置 Kibana(可選):

根據需要配置 Kibana。配置文件通常位于 /etc/kibana/kibana.yml。可能需要

設置如服務器地址、端口、Elasticsearch URL 等。

sudo vim /etc/kibana/kibana.yml 

例如,你可能需要設置 Elasticsearch 服務的 URL: 大概 32 行左右

elasticsearch.host: “http://localhost:9200”

啟動 Kibana 服務:

安裝完成后,啟動 Kibana 服務。

sudo systemctl start kibana

設置開機自啟(可選):

如果你希望 Kibana 在系統啟動時自動啟動,可以使用以下命令來啟用自啟動。

sudo systemctl enable kibana

驗證安裝:

使用以下命令檢查 Kibana 服務的狀態。

sudo systemctl status kibana

訪問 Kibana:

在瀏覽器中訪問 Kibana,通常是 http://:5601

ES 客戶端的安裝

代碼:https://github.com/seznam/elasticlient

官網:https://seznam.github.io/elasticlient/index.html

ES C++的客戶端選擇并不多, 我們這里使用 elasticlient 庫, 下面進行安裝。

# 克隆代碼
git clone https://github.com/seznam/elasticlient
# 切換目錄
cd elasticlient
# 更新子模塊
git submodule update --init --recursive
# 編譯代碼
mkdir build
cd build
cmake ..
make
# 安裝
make install

cmake 生成 makefile 的過程會遇到一個問題

解決:需要安裝 MicroHTTPD 庫

sudo apt-get install libmicrohttpd-dev

make 的時候編譯出錯:這是子模塊 googletest 沒有編譯安裝

collect2: error: ld returned 1 exit status
make[2]: *** [external/httpmockserver/test/CMakeFiles/testserver.dir/build.make:105: bin/test-server] Error 1
make[1]: *** [CMakeFiles/Makefile2:675: 
external/httpmockserver/test/CMakeFiles/test-server.dir/all] Error 
2 
make: *** [Makefile:146: all] Error 2

解決:手動安裝子模塊

cd ../external/googletest/
mkdir cmake && cd cmake/
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make && sudo make install

安裝好重新 cmake 即可。

ES 核心概念

索引(Index)

一個索引就是一個擁有幾分相似特征的文檔的集合。比如說,你可以有一個客戶數據的索引,一個產品目錄的索引,還有一個訂單數據的索引。一個索引由一個名字來標識(必須全部是小寫字母的),并且當我們要對應于這個索引中的文檔進行索引、搜索、更新和刪除的時候,都要使用到這個名字。在一個集群中,可以定義任意多的索引。

字段(Field)

字段相當于是數據表的字段,對文檔數據根據不同屬性進行的分類標識。

名稱數值備注
enabledtrue(默認) | false是否僅作存儲,不做搜索和分析
indextrue(默認) | false是否構建倒排索引(決定了是否分詞,是否被索引
index_option
dynamictrue(缺省)| false控制 mapping 的自動更新
doc_valuetrue(默認) | false是否開啟 doc_value,用戶聚合和排序分析,分詞字段不能使用
fielddatafielddata": {“format”:“disabled”}是否為 text 類型啟動 fielddata,實現排序和聚合分析針對分詞字段,參與排序或聚合時能提高性能,不分詞字段統一建議使用 doc_value
storetrue | false(默認)是否單獨設置此字段的是否存儲而從_source 字段中分離,只能搜索,不能獲取值
coercetrue(默認) | false是否開啟自動數據類型轉換功能,比如:字符串轉數字,浮點轉整型
analyzer“analyzer”: “ik”指定分詞器,默認分詞器為 standard analyzer
boost“boost”: 1.23字段級別的分數加權,默認值是 1.0
fields“fields”: {“raw”: {“type”:“text”,“index”:“not_analyzed”}}對一個字段提供多種索引模式,同一個字段的值,一個分詞,一個不分詞
data_detectiontrue(默認) | false是否自動識別日期類型

文檔 (document)

一個文檔是一個可被索引的基礎信息單元。比如,你可以擁有某一個客戶的文檔,某一個產品的一個文檔或者某個訂單的一個文檔。文檔以 JSON(Javascript Object Notation)格式來表示,而 JSON 是一個到處存在的互聯網數據交互格式。在一個index/type 里面,你可以存儲任意多的文檔。一個文檔必須被索引或者賦予一個索引的 type。

Elasticsearch與傳統關系型數據庫相比如下:

DBDatabaseTableRowColumn
ESIndexTypeDocumentField

Kibana 訪問 es 進行測試

通過網頁訪問 kibana:

創建索引庫

POST /user/_doc
{"settings": {"analysis": {"analyzer": {"ik": {"tokenizer": "ik_max_word"}}}},"mappings": {"dynamic": true,"properties": {"nickname": {"type": "text","analyzer": "ik_max_word"},"user_id": {"type": "keyword","analyzer": "standard"},"phone": {"type": "keyword","analyzer": "standard"},"description": {"type": "text","enabled": false},"avatar_id": {"type": "keyword","enabled": false}}}
}

新增數據:

POST /user/_doc/_bulk
{"index":{"_id":"1"}}
{"user_id" : "USER4b862aaa-2df8654a-7eb4bb65-e3507f66","nickname" : "昵稱 1","phone" : "手機號 1","description" : "簽名 1","avatar_id" : "頭像 1"}
{"index":{"_id":"2"}}
{"user_id" : "USER14eeeaa5-442771b9-0262e455-e4663d1d","nickname" : "昵稱 2","phone" : "手機號 2","description" : "簽名 2","avatar_id" : "頭像 2"}
{"index":{"_id":"3"}}
{"user_id" : "USER484a6734-03a124f0-996c169d-d05c1869","nickname" : "昵稱 3","phone" : "手機號 3","description" : "簽名 3","avatar_id" : "頭像 3"}
{"index":{"_id":"4"}}
{"user_id" : "USER186ade83-4460d4a6-8c08068f-83127b5d","nickname" : "昵稱 4","phone" : "手機號 4","description" : "簽名 4","avatar_id" : "頭像 4"}
{"index":{"_id":"5"}}
{"user_id" : "USER6f19d074-c33891cf-23bf5a83-57189a19","nickname" : "昵稱 5","phone" : "手機號 5","description" : "簽名 5","avatar_id" : "頭像 5"}
{"index":{"_id":"6"}}
{"user_id" : "USER97605c64-9833ebb7-d0455353-35a59195","nickname" : "昵稱 6","phone" : "手機號 6","description" : "簽名 6","avatar_id" : "頭像 6"}

查看并搜索數據

GET /user/_doc/_search?pretty
{"query": {"bool": {"must_not": [{"terms": {"user_id.keyword": ["USER4b862aaa-2df8654a-7eb4bb65-e3507f66","USER14eeeaa5-442771b9-0262e455-e4663d1d","USER484a6734-03a124f0-996c169d-d05c1869"]}}],"should": [{"match": {"user_id": "昵稱"}},{"match": {"nickname": "昵稱"}},{"match": {"phone": "昵稱"}}]}}
}

刪除索引:

DELETE /user

檢索全部數據:

GET /user/_search
{"query": {"match_all": {}}
}

ES 客戶端接口介紹

// 創建客戶端對象
explicit Client(const std::vector < std::string >> &hostUrlList,std::int32_t timeout = 6000);// 應用于索引創建,以及新增數據
cpr::Response index(const std::string &indexName,const std::string &docType,const std::string &id,const std::string &body,const std::string &routing = std::string());// 檢索數據
cpr::Response search(const std::string &indexName,const std::string &docType,const std::string &body,const std::string &routing = std::string());// 刪除數據
cpr::Response remove(const std::string &indexName,const std::string &docType,const std::string &id,const std::string &routing = std::string());

使用案例,數據為上面的數據:

#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <iostream>int main()
{// 構造ES客戶端elasticlient::Client client({"http://127.0.0.1:9200/"});// 發起搜索請求try{auto rsp = client.search("user", "_doc", "{\"query\":{\"match_all\":{} }}");std::cout << rsp.status_code << std::endl;std::cout << rsp.text << std::endl;}catch (std::exception &e){std::cout << "請求失敗: " << e.what() << std::endl;return -1;}return 0;
}

測試結果:

image-20250216183726619

二次封裝

使用jsoncpp庫實現數據的序列化和反序列化

Json::Value:用于進行中間數據存儲

將多個字段數據進行序列化,需要先將數據存儲到Value對象中

若要對一個json格式字符串進行解析,解析結果也是存放在Value中

常用接口:

#include <json/json.h>Value &operator=(Value &other);
Value &operator[](const char *key);  // Value["name"] = "張三";
Value &append(const Value &value);   // 數組數據的新增,Value["score"].append(100);
std::string asString() const;        // Value["name"].asString();
ArrayIndex size() const;             // 獲取數組元素的個數
Value &operator[](ArrayIndex index); // 通過下標獲取數組元素,Value["socre"][0].asFloat();// Write類
class Json_API StreamWriter
{virtual write(Value const &root, JSONCPP_OSTREAM *sout) = 0; // 序列化接口
};
class Json_API StreamWriterBuilder
{StreamWriter *newStreamWriter(); // StreamWriter 對象生產接口
};// Reader類
class JSON_API CharReader
{virtual bool parse(char const *beginDoc, char const *endDoc, Value *root, JSONCPP_STRING *errs) = 0;
};class JSON_API CharReaderBuilder
{CharReader *newCharReader(); // 創建CharReader對象接口
};

Jsoncpp使用案例:

#include <json/json.h>
#include <iostream>
#include <string>
#include <sstream>
#include <memory>bool serialize(const Json::Value &root, std::string &str)
{Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss;int ret = sw->write(root, &ss);if (ret < 0){std::cout << "json serialize failed" << std::endl;return false;}str = ss.str();return true;
}bool deserialize(const std::string &body, Json::Value &val)
{Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), &val, &err);if (ret == false){std::cout << "json deserialize failed " << err << std::endl;return false;}return true;
}int main()
{std::string name = "小明";int age = 18;float score[3] = {91, 99, 100};Json::Value stu;stu["name"] = name;stu["age"] = age;stu["score"].append(score[0]);stu["score"].append(score[1]);stu["score"].append(score[2]);std::string json_str;bool ret = serialize(stu, json_str);if (ret == false){std::cout << "json serialize failed" << std::endl;return -1;}std::cout << json_str << std::endl;Json::Value root;ret = deserialize(json_str, root);if (ret == false){std::cout << "json deserialize failed" << std::endl;return -1;}std::cout << "姓名:" << root["name"].asString() << std::endl;std::cout << "年齡:" << root["age"].asInt() << std::endl;std::cout << "成績分別是: ";int sz = root["score"].size();for (int i = 0; i < sz; i++){std::cout << root["score"][i].asFloat() << " ";}std::cout << std::endl;return 0;
}

image-20250216190238780

ES客戶端API二次封裝

封裝四個操作:索引創建,數據新增,數據查詢,數據刪除

封裝最主要完成的是請求正文的構造過程,Json::Value對象數據新增過程

索引創建:

1.能夠動態設定索引名稱,索引類型

2.能夠動態的添加字段,并設置字段類型,設置分詞器類型,是否構造索引

構造思想:根據固定的Json格式構造Value對象即可

數據新增:

1.提供用戶一個新增字段及數據的接口即可

2.提供一個發起請求的接口

封裝代碼實現:

#pragma once
#include <json/json.h>
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include "logger.hpp"namespace hdp
{bool serialize(const Json::Value &root, std::string &str){Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss;int ret = sw->write(root, &ss);if (ret < 0){LOG_ERROR("json serialize failed");return false;}str = ss.str();return true;}bool deserialize(const std::string &body, Json::Value &val){Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), &val, &err);if (ret == false){LOG_ERROR("json deserialize failed: {} ", err);return false;}return true;}class ESIndex{public:ESIndex(const std::shared_ptr<elasticlient::Client> &client,const std::string &name,const std::string &type = "_doc"): _name(name), _type(type), _client(client){Json::Value analysis;Json::Value analyzer;Json::Value ik;Json::Value tokenizer;tokenizer["tokenizer"] = "ik_max_word";ik["ik"] = tokenizer;analyzer["analyzer"] = ik;analysis["analysis"] = analyzer;_index["settings"] = analysis;}ESIndex &append(const std::string &key, const std::string &type = "text",const std::string &analyzer = "ik_max_word",bool enabled = true){Json::Value field;field["type"] = type;field["analyzer"] = analyzer;if (enabled == false)field["enabled"] = enabled;_properties[key] = field;return *this;}bool create(const std::string &index_id = "default_index_id"){Json::Value mappings;mappings["dynamic"] = true;mappings["properties"] = _properties;_index["mappings"] = mappings;std::string body;bool ret = serialize(_index, body);if (ret == false){LOG_ERROR("索引序列化失敗");return false;}try{cpr::Response rsp = _client->index(_name, _type, index_id, body);if (rsp.status_code < 200 || rsp.status_code >= 300){LOG_ERROR("創建ES索引 {} 失敗,響應狀態碼異常: {}", _name, rsp.status_code);return false;}}catch (std::exception &e){LOG_ERROR("創建ES索引 {} 失敗: {}", _name, e.what());return false;}return true;}private:std::string _name;std::string _type;Json::Value _index;Json::Value _properties;std::shared_ptr<elasticlient::Client> _client;};class ESInsert{public:ESInsert(const std::shared_ptr<elasticlient::Client> client,const std::string &name,const std::string &type = "_doc"): _name(name),_type(type), _client(client) {}template <class T>ESInsert &append(const std::string &key, const T &val){_item[key] = val;return *this;}bool insert(const std::string &id = ""){std::string body;bool ret = serialize(_item, body);if (ret == false){LOG_ERROR("索引序列化失敗");return false;}try{cpr::Response rsp = _client->index(_name, _type, id, body);if (rsp.status_code < 200 || rsp.status_code >= 300){LOG_ERROR("新增數據 {} 失敗,響應狀態碼為: {}", body, rsp.status_code);return false;}}catch (std::exception &e){LOG_ERROR("新增數據 {} 失敗: {}", body, e.what());return false;}return true;}private:std::string _name;std::string _type;Json::Value _item;std::shared_ptr<elasticlient::Client> _client;};class ESRemove{public:ESRemove(const std::shared_ptr<elasticlient::Client> &client,const std::string &name, const std::string &type = "_doc"): _name(name), _type(type), _client(client) {}bool remove(const std::string &id){try{cpr::Response rsp = _client->remove(_name, _type, id);if (rsp.status_code < 200 || rsp.status_code >= 300){LOG_ERROR("刪除數據{}失敗:響應狀態碼異常: {}", rsp.status_code);return false;}}catch (std::exception &e){LOG_ERROR("刪除數據 {} 異常: {}", id, e.what());return false;}return true;}private:std::string _name;std::string _type;std::shared_ptr<elasticlient::Client> _client;};class ESSearch{public:ESSearch(const std::shared_ptr<elasticlient::Client> &client,const std::string &name, const std::string &type = "_doc"): _name(name), _type(type), _client(client) {}ESSearch &append_must_not_terms(const std::string &key, const std::vector<std::string> &vals){Json::Value fields;for (const auto& val : vals){fields[key].append(val);}Json::Value terms;terms["terms"] = fields;_must_not.append(terms);return *this;}ESSearch &append_should_match(const std::string &key, const std::string &val){Json::Value field;field[key] = val;Json::Value match;match["match"] = field;_should.append(match);return *this;}ESSearch &append_must_term(const std::string &key, const std::string &val){Json::Value field;field[key] = val;Json::Value term;term["term"] = field;_must.append(term);return *this;}ESSearch &append_must_match(const std::string &key, const std::string &val){Json::Value field;field[key] = val;Json::Value match;match["match"] = field;_must.append(match);return *this;}Json::Value search(){Json::Value cond;if(_must_not.empty() == false)cond["must_not"] = _must_not;if(_should.empty() == false)cond["should"] = _should;if(_must.empty() == false)cond["must"] = _must;Json::Value query;query["bool"] = cond;Json::Value root;root["query"] = query;std::string body;bool ret = serialize(root, body);if (ret == false){LOG_ERROR("索引序列化失敗");return Json::Value();}cpr::Response rsp;try{rsp = _client->search(_name, _type, body);if (rsp.status_code < 200 || rsp.status_code >= 300){LOG_ERROR("檢索數據 {} 失敗,響應狀態碼異常: {}", body, rsp.status_code);return Json::Value();}}catch (std::exception &e){LOG_ERROR("檢索數據 {} 失敗: {}", body, e.what());return Json::Value();}// 需要對響應正文進行反序列化Json::Value json_res;ret = deserialize(rsp.text, json_res);if (ret == false){LOG_ERROR("檢索數據 {} 結果反序列化失敗", rsp.text);return Json::Value();}serialize(json_res, body);LOG_DEBUG("檢索響應正文: [{}]", body);return json_res["hits"]["hits"];}private:std::string _name;std::string _type;Json::Value _must_not;Json::Value _should;Json::Value _must;std::shared_ptr<elasticlient::Client> _client;};
}

二次封裝測試代碼:

#include "../../../common/icsearch.hpp"
#include <gflags/gflags.h>DEFINE_int32(run_mode, 0, "程序的運行模式,0-調試,1-發布");
DEFINE_string(log_file, "", "發布模式下,用于指定日志的輸出文件");
DEFINE_int32(log_level, 0, "發布模式下,用于指定日志的輸出等級");int main(int argc, char *argv[])
{google::ParseCommandLineFlags(&argc, &argv, true);hdp::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);std::vector<std::string> host_list = {"http://127.0.0.1:9200/"};std::shared_ptr<elasticlient::Client> client = std::make_shared<elasticlient::Client>(host_list);// 創建索引bool ret = hdp::ESIndex(client, "test_user").append("nickname").append("phone", "keyword", "standard", true).create();if (ret == false){LOG_ERROR("創建索引失敗");return -1;}else{LOG_DEBUG("創建索引成功");}// 新增數據ret = hdp::ESInsert(client, "test_user").append("nickname", "張三").append("phone", "123456").insert("0001");if (ret == false){LOG_ERROR("新增數據失敗");return -1;}else{LOG_DEBUG("新增數據成功");}ret = hdp::ESInsert(client, "test_user").append("nickname", "李四").append("phone", "112233").insert("0002");if (ret == false){LOG_ERROR("新增數據失敗");return -1;}else{LOG_DEBUG("新增數據成功");}std::this_thread::sleep_for(std::chrono::seconds(1));// 檢索數據Json::Value user = hdp::ESSearch(client, "test_user").append_should_match("nickname", "李四").search();if (user.empty() || user.isArray() == false){LOG_ERROR("檢索結果為空,或者結果不是數組類型");return -1;}else{LOG_DEBUG("數據檢索成功");}int size = user.size();for (int i = 0; i < size; ++i){LOG_INFO("nickname: {}", user[i]["_source"]["nickname"].asString());LOG_INFO("phone: {}", user[i]["_source"]["phone"].asString());}// 更新數據ret = hdp::ESInsert(client, "test_user").append("nickname", "李四").append("phone", "123456789").insert("0002");if (ret == false){LOG_ERROR("更新數據失敗");return -1;}else{LOG_DEBUG("更新數據成功");}std::this_thread::sleep_for(std::chrono::seconds(1));user = hdp::ESSearch(client, "test_user").append_should_match("phone.keyword", "123456789").search();if (user.empty() || user.isArray() == false){LOG_ERROR("檢索結果為空,或者結果不是數組類型");return -1;}else{LOG_DEBUG("數據檢索成功");}size = user.size();for (int i = 0; i < size; ++i){LOG_INFO("nickname: {}", user[i]["_source"]["nickname"].asString());LOG_INFO("phone: {}", user[i]["_source"]["phone"].asString());}// 刪除數據ret = hdp::ESRemove(client, "test_user").remove("0002");if (ret == false){LOG_ERROR("刪除數據失敗");return -1;}else{LOG_DEBUG("刪除數據成功");}std::this_thread::sleep_for(std::chrono::seconds(1));user = hdp::ESSearch(client, "test_user").append_should_match("phone.keyword", "123456789").search();if (user.empty() || user.isArray() == false){LOG_ERROR("檢索結果為空,或者結果不是數組類型");return -1;}else{LOG_DEBUG("數據檢索成功");}size = user.size();for (int i = 0; i < size; ++i){LOG_INFO("nickname: {}", user[i]["_source"]["nickname"].asString());LOG_INFO("phone: {}", user[i]["_source"]["phone"].asString());}return 0;
}

測試結果:

image-20250216181820079

ES客戶端API使用注意事項:

1.地址后面不要忘了相對根目錄: http://127.0.0.1:9200/

tring());
}

// 刪除數據
ret = hdp::ESRemove(client, "test_user").remove("0002");
if (ret == false)
{LOG_ERROR("刪除數據失敗");return -1;
}
else
{LOG_DEBUG("刪除數據成功");
}std::this_thread::sleep_for(std::chrono::seconds(1));
user = hdp::ESSearch(client, "test_user").append_should_match("phone.keyword", "123456789").search();
if (user.empty() || user.isArray() == false)
{LOG_ERROR("檢索結果為空,或者結果不是數組類型");return -1;
}
else
{LOG_DEBUG("數據檢索成功");
}
size = user.size();
for (int i = 0; i < size; ++i)
{LOG_INFO("nickname: {}", user[i]["_source"]["nickname"].asString());LOG_INFO("phone: {}", user[i]["_source"]["phone"].asString());
}
return 0;

}


測試結果:[外鏈圖片轉存中...(img-v3LOnflV-1753877627079)]ES客戶端API使用注意事項:1.地址后面不要忘了相對根目錄:  http://127.0.0.1:9200/2.ES客戶端API使用,要進行異常捕捉,否則操作失敗會導致程序異常退出

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93531.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93531.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93531.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【13】C# 窗體應用WinForm——.NET Framework、WinForm、工程創建、工具箱簡介、窗體屬性及創建

文章目錄1. WinForm工程創建 及 界面介紹1.1 WinForm工程創建1.2 窗體 Form1.cs “查看代碼”1.3 打開窗體設計器2. 工具箱3. 窗體屬性及創建3.1 窗體屬性3.2 實例&#xff1a;創建一個新窗體3.2.1 添加新Windows窗體3.2.2 窗體屬性配置3.2.3 設置該窗體為啟動窗體WinForm 是 W…

論文閱讀-IGEV

文章目錄1 概述2 模塊2.1 總體說明2.2 特征抽取器2.3 CGEV2.4 基于Conv-GRU的更新算子2.5 空間上采樣2.6 損失函數3 效果參考文獻1 概述 在雙目深度估計中&#xff0c;有一類是基于3D卷積的方法&#xff0c;代表就是PSMNet&#xff0c;它應用 3D 卷積編碼器-解碼器來聚合和正則…

[2025CVPR-圖象分類方向]SPARC:用于視覺語言模型中零樣本多標簽識別的分數提示和自適應融合

1. ?背景與問題定義? 視覺語言模型&#xff08;如CLIP&#xff09;在單標簽識別中表現出色&#xff0c;但在零樣本多標簽識別&#xff08;MLR&#xff09;任務中表現不佳。MLR要求模型識別圖像中多個對象&#xff08;例如&#xff0c;圖像包含“貓”和“沙發”&#xff09;&…

2025創始人IP如何破局?

內容持續更新卻無人點贊&#xff0c;課程精心打磨卻無人報名&#xff0c;直播賣力講解卻無人停留 —— 明明有內容、有經驗、有成果&#xff0c;卻始終難以打動用戶。問題的核心&#xff0c;或許在于你尚未打造出真正的 “創始人IP”。?一、創始人IP&#xff1a;不止標簽&…

告別配置混亂!Spring Boot 中 Properties 與 YAML 的深度解析與最佳實踐

一、Spring配置文件 1.1、什么是Spring配置 Spring配置指的是在Spring框架中定義和管理應用程序組件&#xff08;如Bean&#xff09;及其依賴關系的過程 作用&#xff1a; 配置文件主要用于解決硬編碼問題&#xff0c;它將可能變更的信息集中存放。程序啟動時&#xff0c;會從…

無人機噴灑系統技術要點與難點解析

一、 模塊運行方式1. 任務規劃與加載模塊&#xff1a;輸入&#xff1a;農田邊界、障礙物信息、作物類型、病蟲害信息、所需噴灑量、天氣條件。運行&#xff1a;利用地面站軟件或移動APP&#xff0c;規劃最優飛行路徑&#xff0c;設定飛行高度、速度、噴灑參數、作業區域。將規…

mongodb源代碼分析createCollection命令創建Collection流程分析

MongoDB 提供兩種方式創建集合&#xff1a;隱式創建 和 顯式創建。方式 1&#xff1a;隱式創建&#xff08;推薦&#xff09;當你向不存在的集合中插入文檔時&#xff0c;MongoDB 會自動創建該集合。示例在 db中隱式創建 users 集合&#xff1a;javascriptdb.users.insertOne({…

c++注意點(13)----設計模式(抽象工廠)

創建型模式抽象工廠模式&#xff08;Abstract Factory Pattern&#xff09;是一種創建型設計模式&#xff0c;它提供一個接口&#xff0c;用于創建一系列相關或相互依賴的對象&#xff0c;而無需指定它們具體的類。簡單說&#xff0c;它就像一個 "超級工廠"&#xff…

【大語言模型入門】—— Transformer 如何工作:Transformer 架構的詳細探索

Transformer 如何工作&#xff1a;Transformer 架構的詳細探索Transformer 如何工作&#xff1a;Transformer 架構的詳細探索什么是 Transformer&#xff1f;什么是 Transformer 模型&#xff1f;歷史背景從 RNN 模型&#xff08;如 LSTM&#xff09;到 Transformer 模型在 NLP…

iOS安全和逆向系列教程 第20篇:Objective-C運行時機制深度解析與Hook技術

iOS安全和逆向系列教程 第20篇:Objective-C運行時機制深度解析與Hook技術 引言 在上一篇文章中,我們深入學習了ARM64匯編語言的基礎知識,掌握了從寄存器操作到指令分析的完整技能體系。現在,我們將把這些底層知識與iOS應用的高層邏輯聯系起來,深入探討Objective-C運行時…

IDEA中全局搜索快捷鍵Ctrl+Shift+F為何失靈?探尋原因與修復指南

在軟件開發中&#xff0c;高效地查找和管理代碼是提升生產力的關鍵。IntelliJ IDEA&#xff0c;作為一款功能強大的集成開發環境&#xff08;IDE&#xff09;&#xff0c;提供了豐富的搜索功能&#xff0c;幫助開發者迅速定位代碼、資源、甚至是IDE功能本身。 在 IntelliJ IDE…

【學習筆記】Lean4 定理證明 ing

文章目錄概述Lean4 定理證明初探示例&#xff1a;證明 1 1 2示例&#xff1a;證明 2 * (x y) 2 * x 2 * yLean4 定理證明基礎命題與定理命題&#xff08;Proposition&#xff09;定理&#xff08;Theorem&#xff09;量詞策略概述 Lean證明是指在Lean環境中&#xff0c;通…

墨者:SQL注入漏洞測試(HTTP頭注入)

墨者學院&#xff1a;SQL注入漏洞測試(HTTP頭注入)&#x1f680; 1. 什么是HTTP頭注入&#xff1f;&#x1f50d; HTTP頭注入是指攻擊者通過篡改HTTP請求頭部的字段&#xff08;如User-Agent、Referer、Cookie、Host等&#xff09;&#xff0c;將惡意SQL代碼插入到后端數據庫查…

linux_前臺,后臺進程

*在用戶訪問端口時&#xff0c;操作系統會形成對應的session,在其的內部進一步形成bash等進程 *一個會話只有一個前臺進程&#xff0c;可以有多個后臺進程&#xff0c;前臺與后臺進程的區別在于誰擁有鍵盤的使用權*前臺與后臺進程都可以訪問顯示器但是后臺無法訪問標準輸入獲取…

spring data mongodb 入門使用手冊

<!--pom.xml引入依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>文檔映射類Student.java import lombok.Data; import lombok.NoArgsCons…

Fastjson2常用操作大全:對象、字符串、集合、數組、Map與JSON互轉實戰

高性能&#xff1a; 核心解析器和生成器經過深度優化&#xff0c;性能遠超許多同類庫。 功能豐富&#xff1a; 支持標準JSON、JSONPath查詢、泛型處理、日期格式化、自定義序列化/反序列化等。 易用性&#xff1a; API 設計簡潔直觀&#xff0c;JSON 工具類提供了最常用的 toJS…

大模型——字節Coze重磅開源!Dify何去何從

大模型——字節Coze重磅開源!Dify何去何從 想必很多人盼了很久,就在昨晚,字節Coze終于開源了!Coze Studio 是字節跳動新一代 AI Agent 開發平臺扣子(Coze)的開源版本。 提供 AI Agent 開發所需的全部核心技術:Prompt、RAG、Plugin、Workflow,使得開發者可以聚焦創造 A…

NaVid——基于單目RGB捕獲的視頻讓VLM規劃「連續環境中VLN」的下一步:無需地圖/里程計/深度信息(含MP3D/R2R/RxR,及VLN-CE的詳解)

前言 因為我司「七月在線」準備于25年7月底復現下NaVILA&#xff0c;而在研究NaVILA的過程中&#xff0c;注意到了這個NaVid 雖然NaVid目前已經不是VLN sota了&#xff0c;但其首次展示了VLM在無需地圖、里程計或深度輸入的情況下&#xff0c;能夠實現優秀的導航性能且對后來…

【Vue2】結合chrome與element-ui的網頁端條碼打印

所有文章都是免費查看的&#xff0c;如果有無法查看的情況&#xff0c;煩請聯系我修改哈~ 序言 為什么要做這個呢&#xff1f;因為所需要的條碼打印功能比較簡單&#xff0c;符合需要即可&#xff0c;但是呢網上查看了發現并沒有合適的開源項&#xff0c;其他成熟的軟件收費又超…

循環神經網絡——動手學深度學習7

環境&#xff1a;PyCharm python3.8 &#x1f449;【循環神經網絡】(recurrent neural network&#xff0c;RNN) RNN通過 引入狀態變量存儲過去的信息和當前的輸入&#xff0c;從而可以確定當前的輸出。狀態變量捕捉序列的時序依賴&#xff0c;是處理文本、時間序列等數據的…