当前位置: 首页 > news >正文

[simdjson] `error_code` | .get() | 异常 | is_fatal() | current_location() | 链式处理

链接:https://github.com/simdjson/simdjson/blob/master/doc/basics.md#error-handling

第六章:错误处理

欢迎回到simdjson~

到目前为止,我们已经学习了解析器、如何处理带填充字符串的JSON数据、解析过程如何从文档开始、如何表示单个值以及如何使用对象和数组句柄遍历结构。

在前几章的示例中,我们可能已经注意到一个重要现象:几乎所有尝试访问数据的操作(如doc.get_object()object["key"]array.at(index)value.get_string()等)都不会直接返回值,而是返回包裹在simdjson::simdjson_result<T>中的结果。这是为什么?

答案简单却至关重要:JSON可能不符合我们的预期

  • 文件可能损坏
  • JSON可能存在结构错误(缺少逗号、未闭合括号等)
  • 预期为数字的字段实际是字符串
  • 预期的数组元素可能缺失
  • 需要的对象字段可能不存在

Simdjson在追求速度的同时也需要保持鲁棒性

它需要一种在解析或数据访问出错时通知我们,而不会导致程序崩溃的机制。这正是simdjson错误处理系统的核心价值。

simdjson_result<T>error_code系统

Simdjson使用特定的错误报告模式:

  1. simdjson_result<T>:可能失败的操作返回此类型对象。可以将其视为特殊容器,可以保存成功结果(类型T的值)或表示错误的error_code,但不会同时保存两者。

  2. error_code:这是一个枚举类型,列出了simdjson操作可能失败的所有方式。例如simdjson::NO_SUCH_FIELD(字段不存在)、simdjson::INCORRECT_TYPE(类型错误)、simdjson::NUMBER_ERROR(数字格式错误)、simdjson::UTF8_ERROR(UTF8编码错误)等。simdjson::SUCCESS表示操作成功。

这个系统允许我们显式检查错误,而不依赖C++异常(尽管也支持异常,后续会说明)。

让我们回顾第五章的简单访问示例,仔细查看其返回类型:

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;simdjson::padded_string json_data = R"({"name": "Alice", "age": 30})"_padded;auto doc_result = parser.iterate(json_data);// doc_result是simdjson::simdjson_result<simdjson::ondemand::document>// 必须首先检查文档获取是否成功!if (doc_result.error()) {std::cerr << "解析器迭代失败: " << doc_result.error() << std::endl;return EXIT_FAILURE;}simdjson::ondemand::document doc = std::move(doc_result.value());// 尝试获取根对象auto obj_result = doc.get_object();// obj_result是simdjson::simdjson_result<simdjson::ondemand::object>// 必须检查此结果是否有错误!if (obj_result.error()) {std::cerr << "获取根对象失败: " << obj_result.error() << std::endl;return EXIT_FAILURE;}simdjson::ondemand::object user_obj = obj_result.value();// 尝试获取"name"字段(预期为字符串)auto name_val_result = user_obj["name"];// name_val_result是simdjson::simdjson_result<simdjson::ondemand::value>// 访问字段后检查错误if (name_val_result.error()) {std::cerr << "访问'name'字段失败: " << name_val_result.error() << std::endl;return EXIT_FAILURE;}simdjson::ondemand::value name_val = name_val_result.value();// 从值对象获取字符串内容auto name_str_result = name_val.get_string();// name_str_result是simdjson::simdjson_result<std::string_view>// 获取字符串内容后检查错误if (name_str_result.error()) {std::cerr << "从'name'获取字符串失败: " << name_str_result.error() << std::endl;return EXIT_FAILURE;}std::string_view name_sv = name_str_result.value();std::cout << "用户名称: " << name_sv << std::endl;return EXIT_SUCCESS;
}

此示例展示了基本模式:

  1. 调用可能失败的方法(返回simdjson_result<T>
  2. 立即使用.error()方法检查返回结果
  3. 如果.error()返回simdjson::SUCCESS(布尔上下文为false),操作成功,可安全使用.value()获取值
  4. 如果返回其他错误码(布尔上下文为true),操作失败,应处理错误(例如打印消息、返回错误码)。若error()为true时尝试访问.value()将导致未定义行为

error()方法返回具体的simdjson::error_code

我们可以将错误码与simdjson::SUCCESS或其他特定错误码比较,或直接检查其布尔值。

要获取错误码的人类可读描述,可使用simdjson::error_message()函数:

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;// 测试多个错误JSONstd::vector<std::pair<std::string, std::string>> test_cases = {{R"({"data": [1, 2,], "status": "incomplete")","缺少闭合括号且有语法错误(多余逗号)"},{R"({"data": [1, 2, 3], "status": "ok")","正确的JSON"},{R"({"data": [1, 2, 3], "status": "ok",})","对象末尾多余逗号"},{R"({"data": [1, 2, 3], "status": "ok"})","正确的JSON(用于对比)"},{R"({"data": [1, 2, 3], "status": "ok", "extra":})","缺少值的键值对"},{R"({"data": [1, 2, 3], "status": "ok", "number": 123.456.789})","无效的数字格式"},{R"({"data": [1, 2, 3], "status": "ok", "string": "unclosed string)","未闭合的字符串"}};std::cout << "=== simdjson 错误处理测试 ===\n\n";for (size_t i = 0; i < test_cases.size(); ++i) {const auto& test_case = test_cases[i];std::cout << "测试 " << (i + 1) << ": " << test_case.second << std::endl;std::cout << "JSON: " << test_case.first << std::endl;simdjson::padded_string json_data = simdjson::padded_string(test_case.first);auto doc_result = parser.iterate(json_data);if (doc_result.error()) {std::cout << "解析失败: " << simdjson::error_message(doc_result.error()) << std::endl;std::cout << "错误代码: " << doc_result.error() << std::endl;} else {std::cout << "解析成功 ✓" << std::endl;// 尝试访问一些字段来验证解析try {auto& doc = doc_result.value();auto data_array = doc["data"];if (!data_array.error()) {std::cout << "  成功访问 'data' 字段" << std::endl;}auto status = doc["status"];if (!status.error()) {std::string status_str;if (status.get_string().get(status_str) == simdjson::SUCCESS) {std::cout << "  成功访问 'status' 字段: " << status_str << std::endl;}}} catch (const std::exception& e) {std::cout << "  访问字段时出错: " << e.what() << std::endl;}}std::cout << std::string(50, '-') << std::endl;}return EXIT_SUCCESS;
} 

错误JSON的输出示例:

在这里插入图片描述

simdjson_result<T>error_code模式是simdjson处理潜在错误的主要方式。

无异常错误处理(.get()

虽然.error().value()有效,但更常见的模式(特别是希望完全避免异常时)是使用simdjson_result<T>.get()方法。

该方法尝试将值存入提供的变量,并返回error_code

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;simdjson::padded_string json_data = R"({"name": "Alice", "age": 30})"_padded;simdjson::ondemand::document doc;// 使用.get()获取文档并捕获错误simdjson::error_code error = parser.iterate(json_data).get(doc);if (error) { // 直接检查错误码std::cerr << "解析器迭代失败: " << error << std::endl;return EXIT_FAILURE;}simdjson::ondemand::object user_obj;// 为对象句柄使用.get()error = doc.get_object().get(user_obj);if (error) {std::cerr << "获取根对象失败: " << error << std::endl;return EXIT_FAILURE;}simdjson::ondemand::value name_val;// 为"name"字段值句柄使用.get()error = user_obj["name"].get(name_val);if (error) {std::cerr << "访问'name'字段失败: " << error << std::endl;// 字段不存在的特定错误是NO_SUCH_FIELDif (error == simdjson::NO_SUCH_FIELD) {std::cerr << "(字段未找到)" << std::endl;}return EXIT_FAILURE;}std::string_view name_sv;// 为字符串内容使用.get()error = name_val.get_string().get(name_sv);if (error) {std::cerr << "从'name'获取字符串失败: " << error << std::endl;// 类型错误的特定错误是INCORRECT_TYPEif (error == simdjson::INCORRECT_TYPE) {std::cerr << "(值不是字符串)" << std::endl;}return EXIT_FAILURE;}std::cout << "用户名称: " << name_sv << std::endl;return EXIT_SUCCESS;
}

.get(value)模式非常显式:我们提供变量,方法尝试填充该变量(成功时返回SUCCESS)。若失败,变量状态未定义(不应使用),方法返回非SUCCESS错误码。

这种错误处理风格通常用于性能敏感且避免异常的应用程序,要求在每个可能失败的操作后严格检查error_code

使用异常处理错误

默认情况下,simdjson编译时启用C++异常。

如果习惯在C++代码中使用异常,可以通过转换或直接使用.value()方法(无需先检查.error())简化语法。若操作失败,simdjson将抛出simdjson::simdjson_error异常。

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;// 测试多个错误JSONstd::vector<std::pair<std::string, std::string>> test_cases = {{R"({"data": [1, 2,], "status": "incomplete")","缺少闭合括号且有语法错误(多余逗号)"},{R"({"data": [1, 2, 3], "status": "ok"})","正确的JSON"},{R"({"data": [1, 2, 3], "status": "ok",})","对象末尾多余逗号"},{R"({"data": [1, 2, 3], "status": "ok", "extra":})","缺少值的键值对"},{R"({"data": [1, 2, 3], "status": "ok", "number": 123.456.789})","无效的数字格式"},{R"({"data": [1, 2, 3], "status": "ok", "string": "unclosed string)","未闭合的字符串"}};std::cout << "=== simdjson 异常处理测试 ===\n\n";for (size_t i = 0; i < test_cases.size(); ++i) {const auto& test_case = test_cases[i];std::cout << "测试 " << (i + 1) << ": " << test_case.second << std::endl;std::cout << "JSON: " << test_case.first << std::endl;simdjson::padded_string json_data = simdjson::padded_string(test_case.first);try {// 调用iterate,失败时抛出异常simdjson::ondemand::document doc = parser.iterate(json_data);// 若到达此处说明迭代成功std::cout << "解析成功 ✓" << std::endl;// 尝试获取对象字段及其字符串值// 直接使用operator[]和get_string,失败时抛出异常std::string_view status_sv = doc["status"].get_string();std::cout << "  状态: " << status_sv << std::endl;// 尝试访问数组auto data_array = doc["data"];if (!data_array.error()) {std::cout << "  成功访问 'data' 数组" << std::endl;// 尝试遍历数组for (auto element : data_array) {auto element_result = element.get_int64();if (!element_result.error()) {int64_t value = element_result.value();std::cout << "    数组元素: " << value << std::endl;}}}} catch (const simdjson::simdjson_error& e) {// 捕获simdjson特定错误std::cout << "Simdjson错误: " << e.what() << std::endl;// 可从异常获取具体错误码std::cout << "错误代码: " << e.error() << std::endl;} catch (const std::exception& e) {// 捕获其他标准异常std::cout << "标准异常: " << e.what() << std::endl;}std::cout << std::string(50, '-') << std::endl;}return EXIT_SUCCESS;
} 

使用异常的bad_json输出示例:

在这里插入图片描述

使用异常可以通过将潜在失败代码包裹在try块中,在catch块处理错误,使代码更简洁

simdjson_error异常对象包含error_code和消息(e.what())。

若希望完全禁用异常编译simdjson,可在构建库时设置CMake选项SIMDJSON_EXCEPTIONS=OFF,或在包含simdjson.h前定义SIMDJSON_EXCEPTIONS宏为0。禁用异常后,任何试图抛出simdjson_error的代码将导致程序终止(例如abort())。

致命错误与可恢复错误

某些错误(如simdjson::TAPE_ERRORsimdjson::INCOMPLETE_ARRAY_OR_OBJECT)表示JSON结构本身或解析过程存在根本问题,继续使用解析器及其关联的文档/值句柄将不安全。这些是致命错误。

其他错误(如simdjson::NO_SUCH_FIELDsimdjson::INCORRECT_TYPE可恢复。它们表示特定数据访问或转换失败,但整体解析结构仍然有效,可能可以继续处理文档其他部分。

可使用simdjson::is_fatal()函数检查错误码是否为致命错误。

is_fatal(error)为true,通常应停止使用parser实例及从其获得的任何documentvalueobjectarray句柄。

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;// 测试多个不同类型的错误JSONstd::vector<std::pair<std::string, std::string>> test_cases = {{R"({"data": [1, 2 3], "status": true})","缺少逗号导致TAPE_ERROR(致命错误)"},{R"({"data": [1, 2, 3], "status": true})","正确的JSON(用于对比)"},{R"({"data": [1, 2, 3], "status": true, "extra":})","缺少值的键值对(致命错误)"},{R"({"data": [1, 2, 3], "status": true, "string": "unclosed)","未闭合的字符串(致命错误)"},{R"({"data": [1, 2, 3], "status": true, "number": 123.456.789})","无效的数字格式(致命错误)"},{R"({"data": [1, 2, 3], "status": true, "nested": {"key": "value",}})","嵌套对象末尾多余逗号(致命错误)"}};std::cout << "=== simdjson 致命错误测试 ===\n\n";for (size_t i = 0; i < test_cases.size(); ++i) {const auto& test_case = test_cases[i];std::cout << "测试 " << (i + 1) << ": " << test_case.second << std::endl;std::cout << "JSON: " << test_case.first << std::endl;simdjson::padded_string json_data = simdjson::padded_string(test_case.first);// 创建新的解析器实例(避免致命错误影响)simdjson::ondemand::parser parser;simdjson::ondemand::document doc;simdjson::error_code error = parser.iterate(json_data).get(doc);if (error) {std::cout << "初始迭代失败: " << simdjson::error_message(error) << " (错误码: " << error << ")" << std::endl;if (simdjson::is_fatal(error)) {std::cout << "  → 这是致命错误,解析器状态已损坏" << std::endl;} else {std::cout << "  → 这是可恢复错误" << std::endl;}} else {std::cout << "初始迭代成功 ✓" << std::endl;// 尝试访问数组simdjson::ondemand::array data_array;error = doc["data"].get_array().get(data_array);if (error) {std::cout << "访问'data'数组失败: " << simdjson::error_message(error) << " (错误码: " << error << ")" << std::endl;if (simdjson::is_fatal(error)) {std::cout << "  → 这是致命错误,无法继续使用解析器/文档" << std::endl;std::cout << "  → 解析器和文档现在处于无效状态" << std::endl;std::cout << "  → 不要继续使用'doc'或'parser',除非使用current_location()" << std::endl;std::cout << "  → 若需解析新文档,应创建新解析器" << std::endl;} else {std::cout << "  → 这是可恢复错误,可能继续处理文档其他部分" << std::endl;std::cout << "  → 对于特定错误(如NO_SUCH_FIELD),可以继续访问doc[\"status\"]" << std::endl;// 尝试访问其他字段auto status_result = doc["status"];if (!status_result.error()) {auto bool_result = status_result.get_bool();if (!bool_result.error()) {bool status_value = bool_result.value();std::cout << "  状态: " << (status_value ? "true" : "false") << std::endl;}}}} else {std::cout << "成功访问数据数组 ✓" << std::endl;// 尝试遍历数组int count = 0;for (auto element : data_array) {auto element_result = element.get_int64();if (!element_result.error()) {int64_t value = element_result.value();std::cout << "  数组元素[" << count++ << "]: " << value << std::endl;}}// 尝试访问其他字段auto status_result = doc["status"];if (!status_result.error()) {auto bool_result = status_result.get_bool();if (!bool_result.error()) {bool status_value = bool_result.value();std::cout << "  状态: " << (status_value ? "true" : "false") << std::endl;}}}}std::cout << std::string(60, '-') << std::endl;}// 演示致命错误后的解析器状态std::cout << "\n=== 致命错误后的解析器状态演示 ===\n";simdjson::ondemand::parser demo_parser;simdjson::padded_string bad_json = R"({"data": [1, 2 3], "status": true})"_padded;simdjson::ondemand::document doc1;simdjson::error_code error1 = demo_parser.iterate(bad_json).get(doc1);if (error1) {std::cout << "第一次解析失败: " << simdjson::error_message(error1) << std::endl;if (simdjson::is_fatal(error1)) {std::cout << "致命错误!解析器状态已损坏\n";// 尝试使用损坏的解析器解析新文档(会失败)simdjson::padded_string good_json = R"({"data": [1, 2, 3], "status": true})"_padded;simdjson::ondemand::document doc2;simdjson::error_code error2 = demo_parser.iterate(good_json).get(doc2);if (error2) {std::cout << "使用损坏的解析器解析正确JSON也失败: " << simdjson::error_message(error2) << std::endl;}// 创建新解析器(正确做法)std::cout << "创建新解析器...\n";simdjson::ondemand::parser new_parser;simdjson::ondemand::document doc3;simdjson::error_code error3 = new_parser.iterate(good_json).get(doc3);if (!error3) {std::cout << "新解析器成功解析正确JSON ✓\n";}}}return EXIT_SUCCESS;
} 

tricky_json输出示例:

在这里插入图片描述

理解致命与可恢复错误有助于判断是否需要放弃当前解析尝试,或跳过问题字段/值继续处理。

错误定位:current_location()

当发生错误(特别是解析错误如TAPE_ERRORSTRING_ERROR等)时,了解错误在JSON输入中的具体位置非常有用。simdjson::ondemand::document对象为此提供current_location()方法。

doc.current_location()返回simdjson_result<const char*>。若成功(意味着文档处于有意义的状态),返回指向原始输入字符串中解析器遇到问题或停止位置的字节指针。

#include <simdjson.h>
#include <iostream>
#include <string_view> // 用于std::string_viewint main() {simdjson::ondemand::parser parser;// 缺少引号的JSONsimdjson::padded_string bad_json = R"({"message": "hello world, "status": true})"_padded;simdjson::ondemand::document doc;simdjson::error_code error = parser.iterate(bad_json).get(doc);if (error) {std::cerr << "解析失败: " << error << std::endl;// 尝试获取错误位置simdjson::simdjson_result<const char*> loc_result = doc.current_location();if (loc_result.error()) {std::cerr << "无法获取错误位置: " << loc_result.error() << std::endl;} else {const char* error_ptr = loc_result.value();size_t error_pos = error_ptr - bad_json.data();std::cerr << "错误发生在字节位置: " << error_pos << std::endl;// 可选打印错误位置附近的片段size_t snippet_len = 20;size_t start_pos = (error_pos > snippet_len) ? error_pos - snippet_len : 0;size_t end_pos = std::min(error_pos + snippet_len, bad_json.size());std::string_view snippet(bad_json.data() + start_pos, end_pos - start_pos);std::cerr << "片段: ..." << snippet << "..." << std::endl;// 注意:若使用padded_string_view,error_ptr - view.data()给出索引}return EXIT_FAILURE;}std::cout << "解析成功!" << std::endl; // bad_json不会到达此处return EXIT_SUCCESS;
}

缺少引号示例输出:

解析失败: 字符串错误
错误发生在字节位置: 26
片段: ...orld, "status": true}...

current_location()主要用于调试。即使在文档发生致命错误后,也可以调用该方法获取错误位置。

错误链式处理(简洁性)

我们可能注意到示例中(或希望自己编写)将多个返回simdjson_result的操作串联后再检查最终错误的模式,这称为错误链式处理。

#include <simdjson.h>
#include <iostream>int main() {simdjson::ondemand::parser parser;// 嵌套结构JSONsimdjson::padded_string json_data = R"({"user": {"address": {"city": "Toronto"}}})"_padded;simdjson::ondemand::document doc;simdjson::error_code error = parser.iterate(json_data).get(doc);if (error) { /* 处理迭代错误 */ return 1; }std::string_view city_name;// 串联多个访问和最终值提取error = doc["user"]["address"]["city"].get_string().get(city_name);if (error) { // 仅在链式末端检查错误std::cerr << "访问城市错误: " << error << std::endl;// 错误可能是NO_SUCH_FIELD(user、address或city缺失)// 或INCORRECT_TYPE(user、address或city类型不符)return EXIT_FAILURE;}std::cout << "城市: " << city_name << std::endl;return EXIT_SUCCESS;
}

在此示例中,doc["user"]返回simdjson_result<value>,在该结果上调用["address"]返回另一个simdjson_result<value>,依此类推。最后的.get_string()在最后一个["city"]访问结果上调用,返回simdjson_result<std::string_view>,最终在结果上调用.get(city_name)进行提取并返回错误码。

在这里插入图片描述
详细测试代码,见github仓库~

这种写法使代码紧凑,但缺点是最终error_code仅告知链式中的某处失败,而无法确定具体失败步骤。若useraddresscity缺失,最终.get(city_name)都会返回相同的simdjson::NO_SUCH_FIELD错误。

对于简单情况或只需确认完整路径是否存在时,这种方式很便捷。需要详细错误报告时,必须逐步骤检查。

底层机制(简化版)

simdjson_result<T>是简单的包装器,本质保存类型T的值和error_code

// 简化表示(实际类使用受保护继承自std::pair)
template<typename T>
struct simdjson_result 
{T value;simdjson::error_code error;// 成功构造函数simdjson_result(T val) : value(std::move(val)), error(simdjson::SUCCESS) {}// 错误构造函数simdjson_result(simdjson::error_code err) : value(T{}), error(err) {}// 方法bool error() const { return error != simdjson::SUCCESS; }T& value() { /* 检查错误后返回值 */ }// ... .get()方法...// ... 异常支持(#ifdef SIMDJSON_EXCEPTIONS)...
};

完整定义见include/simdjson/error.h及其实现include/simdjson/error-inl.h

.value()方法和转换运算符包含检查(或基于SIMDJSON_EXCEPTIONS的条件检查),确保存在错误时不返回无效值或抛出异常。

simdjson_error异常类(位于相同头文件)是标准C++异常,仅存储simdjson::error_code并通过what()提供对应消息。

当simdjson内部操作遇到问题(格式错误JSON、错误类型、缺失键等)时,会构造并返回带有特定error_codesimdjson_result对象。我们的代码通过公共接口(.error().get()或异常处理)响应结果。

在这里插入图片描述

该图展示了simdjson_result如何作为内部可能失败操作与代码之间的桥梁,允许我们以可控方式处理潜在错误。

总结

Simdjson基于simdjson::simdjson_result<T>simdjson::error_code构建了鲁棒的错误处理系统:

  • 可能失败的操作返回simdjson_result<T>,保存成功值或错误码
  • 可通过.error().get(value)显式检查错误,或在启用C++异常时使用try...catch块并转换结果或调用.value()
  • simdjson::error_message()提供错误码的人类可读描述
  • 部分错误是fatalis_fatal()),需放弃当前解析器实例;其他是recoverable
  • document::current_location()帮助定位输入JSON中的错误位置
  • 错误链式处理提供跨多操作的简洁错误处理,但会降低错误报告粒度

理解并持续使用此错误处理系统是编写可靠simdjson代码的关键,它使我们能优雅处理格式错误或意外的JSON数据。

掌握错误处理后,我们可以探索高级解析技术,例如处理多JSON文档流。下一章将介绍文档流。

下一章:文档流

http://www.dtcms.com/a/289611.html

相关文章:

  • 限流算法与实现
  • MySQL锁(一) 概述与分类
  • CMSIS-RTOS线程创建osThreadNew
  • JAVA国际版任务悬赏+接单系统源码支持IOS+Android+H5
  • 代码随想录算法训练营二十一天|回溯part03
  • IDEA使用技巧
  • c/c++-memory-management
  • ansible awx自动化工具学习准备
  • 牛客-两个链表的第一个公共节点
  • 安全信息与事件管理(SIEM)系统架构设计
  • TypeScript 中替代 Interface 的方案
  • PLC(可编程逻辑控制器,Programmable Logic Controller)是什么?
  • 详解如何解决Mysql主从复制延迟
  • 脚本代码:网页限制解除(改)
  • 树同构(Tree Isomorphism)
  • 生成模型与判别模型
  • 虚拟机VMware安装国产桌面系统统信UOS
  • Nginx服务器基本搭建
  • 【性能测试】性能压测3个阶段+高频面试题回答(详细)
  • 大语言模型:人像摄影的“达芬奇转世”?——从算法解析到光影重塑的智能摄影革命
  • 机器学习基础:从数据到智能的入门指南
  • 【数据集】NOAA 全球监测实验室(GML)海洋边界层(MBL)参考简介
  • 渗透笔记(XSS跨站脚本攻击)
  • 【算法300】:栈02
  • Day25| 491.递增子序列、46.全排列、47.全排列 II、回溯总结
  • JAVA面试宝典 -《API设计:RESTful 与 GraphQL 对比实践》
  • Error: You don‘t have permission to access that port.当端口被莫名占用时,kill它!
  • G1回收器垃圾回收过程
  • 30天打牢数模基础-卷积神经网络讲解
  • 将来职业从事股票市场投资工作,应该做什么?