Jsoup与HtmlUnit:两大Java爬虫工具对比解析

 Jsoup:HTML解析利器

  • 定位:专注HTML解析的轻量级库(也就是快,但动态页面无法抓取)

  • 核心能力

    • DOM树解析与CSS选择器查询

    • HTML净化与格式化

    • 支持元素遍历与属性提取

  • 应用场景:静态页面数据抽取、内容清洗

    public static Document getJsoupDoc(String url, Integer frequency, Integer connectTimeout) {Document document = null;try {if(connectTimeout==null){document = Jsoup.connect(url).ignoreContentType(true).get();}else{document = Jsoup.connect(url).ignoreContentType(true).maxBodySize(0).timeout(connectTimeout).get();}} catch (Exception e) {document = null;}if (document == null && frequency < 3) {frequency = frequency + 1;try {Thread.sleep(100);} catch (InterruptedException e) {log.error("休眠异常:" + e.getMessage(), e);}document = getJsoupDoc(url, frequency, connectTimeout);}return initUrl(url,document);}

     HtmlUnit:无头浏览器引擎

    • 定位:支持JavaScript的全功能浏览器模拟器(js动态数据的加载)

    • 核心能力

      • 执行复杂AJAX请求

      • 模拟用户交互(点击/表单提交)

      • 支持Cookie管理和页面跳转

    • 典型场景:动态网页抓取、自动化测试

      /*** @param url      爬虫链接* @param waitTime 等待时间* @return*/public static Document getDynamicCrawlersDocument(String url, Integer waitTime, boolean javaScriptEnabled) {Document document = null;try (WebClient browser = new WebClient()) {//解决动态页面抓取不到信息问题browser.getOptions().setCssEnabled(false);browser.getOptions().setJavaScriptEnabled(javaScriptEnabled);browser.getOptions().setThrowExceptionOnScriptError(false);browser.getOptions().setUseInsecureSSL(true);// 设置自定义的错误处理类browser.setJavaScriptErrorListener(new MyJSErrorListener());HtmlPage page = null;page = browser.getPage(url);// 等待后台脚本执行时间browser.waitForBackgroundJavaScript(waitTime);String pageAsXml = page.asXml();document = Jsoup.parse(pageAsXml.replaceAll("\\<\\?xml.*?\\?>", ""));document.setBaseUri(url);} catch (ScriptException e) {log.error("getDynamicCrawlersDocument页面:{}     JavaScript 异常:{}", url, e.getMessage());return initUrl(url,document);} catch (UnknownHostException e) {log.error("getDynamicCrawlersDocument页面:{}     无法解析或找到指定的主机名:{}", url, e.getMessage());return initUrl(url,document);} catch (FailingHttpStatusCodeException e) {log.error("getDynamicCrawlersDocument页面:{}     HTTP 状态异常:{}", url, e.getStatusCode());return initUrl(url,document);} catch (Exception e) {log.error("getDynamicCrawlersDocument页面:{}    获取页面异常:{}", url, e.getMessage());return initUrl(url,document);}return initUrl(url,document);}

      核心优势对比

      特性JsoupHtmlUnit
      解析速度⚡️ 毫秒级响应⏳ 需加载完整页面资源
      JS支持❌ 不执行任何脚本✅ 完整JavaScript引擎
      内存占用🟢 10MB级内存消耗🔴 100MB+内存需求
      学习曲线🟢 半天掌握核心API🟡 需理解浏览器事件模型
      反爬绕过❌ 基础Header支持✅ 模拟真实浏览器指纹
    • 实战场景选择指南

      ▶ 首选Jsoup的情况

      • 目标数据存在于初始HTML中(静态页面)

      • 需要高频抓取(>1000次/分钟)

      • 服务器资源受限(云函数/边缘计算)

      • 快速原型开发需求

    • ▶ 必须HtmlUnit的场景

      • 页面依赖AJAX动态加载(js数据请求)

      • 需要登录Cookie保持

      • 涉及表单交互操作

      • 需解析Shadow DOM内容

结语

Jsoup与HtmlUnit代表了Java爬虫的两个技术维度:极致效率完整模拟。理解二者的设计哲学,根据实际场景灵活选用甚至组合使用(如用HtmlUnit获取初始页面后用Jsoup解析),往往能取得最佳效果。在日益复杂的反爬机制下,合理选择工具将成为数据抓取成功的关键。

完整代码工具类

package com.zzkj.zei.utils;import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.zzkj.zei.pojo.system.SysSite;
import com.zzkj.zei.utils.spider.SpiderUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.htmlunit.BrowserVersion;
import org.htmlunit.FailingHttpStatusCodeException;
import org.htmlunit.ScriptException;
import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlAnchor;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.javascript.DefaultJavaScriptErrorListener;
import org.jetbrains.annotations.NotNull;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** FileName: JsoupHtmlUintUtils* Author: wzk* Date:2024/11/8 9:32*/
@Slf4j
public class JsoupHtmlUintUtils {/*** 动态检测** @param url 爬虫链接* @return*/public static Document getDynamicCrawlersDocument(String url) {Document document = null;//解决动态页面抓取不到信息问题WebClient browser = new WebClient(BrowserVersion.CHROME);browser.getOptions().setCssEnabled(false);browser.getOptions().setJavaScriptEnabled(false);browser.getOptions().setThrowExceptionOnScriptError(false);// 允许使用不安全的 SSLbrowser.getOptions().setUseInsecureSSL(true);// 设置自定义的错误处理类browser.setJavaScriptErrorListener(new MyJSErrorListener());HtmlPage page = null;try {page = browser.getPage(url);// 等待后台脚本执行时间browser.waitForBackgroundJavaScript(1000);String pageAsXml = page.asXml();document = Jsoup.parse(pageAsXml);} catch (ScriptException e) {log.info("页面:{}     JavaScript 异常:{}", url, e.getMessage());} catch (FailingHttpStatusCodeException e) {log.info("页面:{}     HTTP 状态异常:{}", url, e.getStatusCode());} catch (UnknownHostException e) {log.info("页面:{}     无法解析或找到指定的主机名:{}", url, e.getMessage());} catch (Exception e) {log.error("页面:{}    获取页面异常:{}", url, e.getMessage());}return initUrl(url,document);}/*** @param url      爬虫链接* @param waitTime 等待时间* @return*/public static Document getDynamicCrawlersDocument(String url, Integer waitTime, boolean javaScriptEnabled) {Document document = null;try (WebClient browser = new WebClient()) {//解决动态页面抓取不到信息问题browser.getOptions().setCssEnabled(false);browser.getOptions().setJavaScriptEnabled(javaScriptEnabled);browser.getOptions().setThrowExceptionOnScriptError(false);browser.getOptions().setUseInsecureSSL(true);// 设置自定义的错误处理类browser.setJavaScriptErrorListener(new MyJSErrorListener());HtmlPage page = null;page = browser.getPage(url);// 等待后台脚本执行时间browser.waitForBackgroundJavaScript(waitTime);String pageAsXml = page.asXml();document = Jsoup.parse(pageAsXml.replaceAll("\\<\\?xml.*?\\?>", ""));document.setBaseUri(url);} catch (ScriptException e) {log.error("getDynamicCrawlersDocument页面:{}     JavaScript 异常:{}", url, e.getMessage());return initUrl(url,document);} catch (UnknownHostException e) {log.error("getDynamicCrawlersDocument页面:{}     无法解析或找到指定的主机名:{}", url, e.getMessage());return initUrl(url,document);} catch (FailingHttpStatusCodeException e) {log.error("getDynamicCrawlersDocument页面:{}     HTTP 状态异常:{}", url, e.getStatusCode());return initUrl(url,document);} catch (Exception e) {log.error("getDynamicCrawlersDocument页面:{}    获取页面异常:{}", url, e.getMessage());return initUrl(url,document);}return initUrl(url,document);}private static List<Document> getDynamicCrawlersDocument(String url, Integer waitTime) {List<Document> documents = new ArrayList<>();HtmlPage oldPage = null;try (WebClient browser = new WebClient()) {//解决动态页面抓取不到信息问题browser.getOptions().setCssEnabled(false);browser.getOptions().setJavaScriptEnabled(true);browser.getOptions().setThrowExceptionOnScriptError(false);browser.getOptions().setUseInsecureSSL(true);// 设置自定义的错误处理类browser.setJavaScriptErrorListener(new MyJSErrorListener());HtmlPage page = null;page = browser.getPage(url);oldPage = page;// 等待后台脚本执行时间browser.waitForBackgroundJavaScript(waitTime);Document document;document = getDocuments(url, page);documents.add(document);while (true) {HtmlAnchor nextButton = page.getFirstByXPath("//a[contains(text(), '下一页')]");if (nextButton == null || nextButton.getAttribute("class").contains("disabled")) {break; // No more pages}page = nextButton.click();browser.waitForBackgroundJavaScript(waitTime);if (page.equals(oldPage) && !page.getUrl().toString().equals(url)) {break;}oldPage = page;document = getDocuments(url, page);documents.add(document);}} catch (ScriptException e) {log.error("getDynamicCrawlersDocument页面:{}     JavaScript 异常:{}", url, e.getMessage());} catch (UnknownHostException e) {log.error("getDynamicCrawlersDocument页面:{}     无法解析或找到指定的主机名:{}", url, e.getMessage());} catch (FailingHttpStatusCodeException e) {log.error("getDynamicCrawlersDocument页面:{}     HTTP 状态异常:{}", url, e.getStatusCode());} catch (Exception e) {log.error("getDynamicCrawlersDocument页面:{}    获取页面异常:{}", url, e.getMessage());}return documents;}private static @NotNull Document getDocuments(String url, HtmlPage page) {String pageAsXml = page.asXml();Document document = Jsoup.parse(pageAsXml.replaceAll("\\<\\?xml.*?\\?>", ""));document.setBaseUri(url);return initUrl(url,document);}public static List<Document> getDocuments(String url, Integer isDynamic) {List<Document> list;if (isDynamic == 1) {list = getDynamicCrawlersDocument(url, 1000);} else {list = getJsoupDoc(url);}return list;}public static Document getDocument(String url, Integer isDynamic) {Document document;if (isDynamic == 1) {document = getDynamicCrawlersDocument(url, 1000, true);} else {document = getJsoupDoc(url, 1, null);}return initUrl(url,document);}/*** @param url 爬虫链接* @return*/public static Document getJsoupDoc(String url, Integer frequency, Integer connectTimeout) {Document document = null;try {if(connectTimeout==null){document = Jsoup.connect(url).ignoreContentType(true).get();}else{document = Jsoup.connect(url).ignoreContentType(true).maxBodySize(0).timeout(connectTimeout).get();}} catch (Exception e) {document = null;}if (document == null && frequency < 3) {frequency = frequency + 1;try {Thread.sleep(100);} catch (InterruptedException e) {log.error("休眠异常:" + e.getMessage(), e);}document = getJsoupDoc(url, frequency, connectTimeout);}return initUrl(url,document);}private static List<Document> getJsoupDoc(String url) {List<Document> list = new ArrayList<>();Document document = getJsoupDoc(url, 1, null);list.add(document);return list;}public static String getRedirectUrl(String url) {log.info("getRedirectUrl-------------------url---------------" + url);String redirectUrl = "";//设置模拟浏览器try (WebClient webClient = new WebClient(BrowserVersion.CHROME)) {//是否等待页面javaScrpit加载webClient.getOptions().setJavaScriptEnabled(true);webClient.getOptions().setRedirectEnabled(true);// js运行错误时,是否抛出异常webClient.getOptions().setThrowExceptionOnScriptError(false);webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);// 设置连接超时时间webClient.getOptions().setTimeout(200);// HtmlUnitredirectUrl = webClient.getPage(url).getUrl().toString();} catch (FailingHttpStatusCodeException | IOException e) {log.error(url + "获取重定向网站失败1:" + e.getMessage(), e);} catch (Exception e) {log.error(url + "获取重定向网站失败2:" + e.getMessage(), e);}return redirectUrl;}/*** 获取重定向url** @param hrefUrl     链接地址* @param metaTagsUrl 元标签地址* @param sysSite     站点实体* @return*/public static String getRedirectUrl(String hrefUrl, String metaTagsUrl, SysSite sysSite) {String redirectUrl = "";try {if (metaTagsUrl.startsWith("./") && SpiderUtils.isNode(hrefUrl, sysSite)) {if (hrefUrl.endsWith("/")) {redirectUrl = hrefUrl + metaTagsUrl.substring(2);} else {redirectUrl = hrefUrl + metaTagsUrl.substring(1);}} else if (metaTagsUrl.startsWith("./") && hrefUrl.endsWith(".html")) {hrefUrl = hrefUrl.substring(0, hrefUrl.lastIndexOf("/"));metaTagsUrl = metaTagsUrl.substring(1);redirectUrl = hrefUrl + metaTagsUrl;} else if ("../".equals(metaTagsUrl) && SpiderUtils.isNode(hrefUrl, sysSite)) {if (hrefUrl.endsWith("/")) {hrefUrl = hrefUrl.substring(0, hrefUrl.length() - 1);}redirectUrl = hrefUrl.substring(0, hrefUrl.lastIndexOf('/'));} else if ("/".equals(metaTagsUrl)) {redirectUrl = sysSite.getSiteDomain();} else {//SpiderUtils.saveLogText("需要获取重定向以后的url--------------------hrefUrl:"+hrefUrl+"--------metaTagsUrl:"+metaTagsUrl);redirectUrl = JsoupHtmlUintUtils.getRedirectUrl(hrefUrl);//SpiderUtils.saveLogText("需要获取重定向以后的url-----------返回结果---------redirectUrl:"+redirectUrl);}} catch (Exception e) {log.error("获取的url失败:" + e.getMessage(), e);}return redirectUrl;}/*** 获取原标签的url** @param refreshMeta* @return*/public static String getMetaTagsUrl(Element refreshMeta) {String refreshUrl = "";try {if (refreshMeta != null) {String patternString = "http-equiv\\s*=\\s*\"?Refresh\"?\\s*[\\s;]*content\\s*=\\s*\"?(\\d+);\\s*url\\s*=\\s*(\"?)(.*?)\\2\"";Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);Matcher matcher = pattern.matcher(refreshMeta.html());if (matcher.find()) {refreshUrl = matcher.group(3);}}} catch (Exception e) {log.error("获取元标签的url失败:" + e.getMessage(), e);}return refreshUrl;}/*** 获取链接的状态码** @param url 爬虫链接* @return*/public static Integer getUrlResponseCode(String url, Integer frequency) {int statusCode;try (HttpResponse response = HttpRequest.head(url).setConnectionTimeout(1000).execute()) {//使用hutool方法获取状态码statusCode = response.getStatus();if (statusCode >= 400 && frequency < 3) {frequency = frequency + 1;try {Thread.sleep(200);} catch (InterruptedException e) {log.error("休眠异常:" + e.getMessage(), e);}statusCode = getUrlResponseCode(url, frequency);}} catch (Exception e) {log.error(url+"-----获取url的状态码失败:" + e.getMessage(), e);statusCode = 500;}return statusCode;}/*** 静态爬虫** @param url* @return*/private Document getStaticCrawlers(String url) {Document document = null;try {document = Jsoup.connect(url).timeout(5000).get();} catch (HttpStatusException e) {// 后台异常处理if ((e.getStatusCode() + "").startsWith("5")) {try {Thread.sleep(2000); // 睡眠2秒document = Jsoup.connect(url).timeout(5000).get();} catch (IOException ex) {ex.getMessage();} catch (InterruptedException ex) {throw new RuntimeException(ex);}}} catch (Exception e) {e.printStackTrace();}return initUrl(url,document);}private Document getStaticCrawlers(String url, Integer waitTime) {Document document = null;try {document = Jsoup.connect(url).timeout(waitTime).get();} catch (HttpStatusException e) {// 后台异常处理if ((e.getStatusCode() + "").startsWith("5")) {try {Thread.sleep(2000); // 睡眠2秒document = Jsoup.connect(url).timeout(waitTime).get();} catch (IOException ex) {ex.getMessage();} catch (InterruptedException ex) {throw new RuntimeException(ex);}}} catch (Exception e) {}return initUrl(url,document);}/*** 初始化Document中的相对路径为绝对路径* @param sourceUrl 基准URL,用于解析相对路径* @param document Jsoup解析的Document对象* @return 处理后的Document* @throws IllegalArgumentException 如果基准URL无效*/public static Document initUrl(String sourceUrl, Document document) {try{if (ObjectUtils.isNotEmpty(document)){URI baseUri;try {baseUri = new URI(sourceUrl);} catch (URISyntaxException e) {throw new IllegalArgumentException("链接处理异常: " + sourceUrl, e);}Elements aList = document.select("a");for (Element element : aList) {String href = element.attr("href");// 跳过空或无效的href属性if (href == null || href.isEmpty()) {continue;}//是javascript:void(0)类似这样的非法链接if (SpiderUtils.filterJavaScript(href)) {continue;}//不符合url规则if (SpiderUtils.illegalUrl(href)) {continue;}try {URI resolvedUri = baseUri.resolve(href);element.attr("href", resolvedUri.toString());} catch (IllegalArgumentException e) {// 可选:记录解析失败的情况log.error("无法解析链接 '" + href + "': " + e.getMessage());}}}} catch (Exception e){log.info("document初始化链接异常:",e.getMessage(),e);}return document;}static class MyJSErrorListener extends DefaultJavaScriptErrorListener {@Overridepublic void scriptException(HtmlPage page, ScriptException scriptException) {}@Overridepublic void timeoutError(HtmlPage page, long allowedTime, long executionTime) {}@Overridepublic void malformedScriptURL(HtmlPage page, String url, MalformedURLException malformedURLException) {}@Overridepublic void loadScriptError(HtmlPage page, URL scriptUrl, Exception exception) {}@Overridepublic void warn(String message, String sourceName, int line, String lineSource, int lineOffset) {}}}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小白成长之路-vim编辑

文章目录 Vim一、命令模式二、插入模式3.a:进入插入模式&#xff0c;在当前光标的后一个字符插入![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fd293c3832ed49e2974abfbb63eeb5bb.png)4.o: 在当前光标的下一行插入5.i:在当前光标所在字符插入&#xff0c;返回命令模…

[redis进阶六]详解redis作为缓存分布式锁

目录 一 什么是缓存 缓存总结板书: 二 使⽤Redis作为缓存 三 缓存的更新策略 1) 定期⽣成 2) 实时⽣成 四 面试重点:缓存预热,缓存穿透,缓存雪崩 和缓存击穿 1)缓存预热 2)缓存穿透 3)缓存雪崩 4)缓存击穿 五 分布式锁 板书: 1)什么是分布式锁 2)分布式锁的基…

【MySQL】数据表插入数据

个人主页&#xff1a;Guiat 归属专栏&#xff1a;MySQL 文章目录 1. 插入数据概述1.1 插入数据的重要性1.2 插入数据的基本原则 2. 基本插入语句2.1 INSERT INTO语法2.2 插入多行数据2.3 不指定列名的插入2.4 插入NULL和默认值 3. 高级插入技术3.1 使用子查询插入数据3.2 IGNOR…

软考-软件设计师中级备考 14、刷题 算法

一、考点归纳 1&#xff09;排序 2、查找 3、复杂度 4、经典问题 0 - 1 背包动态规划0 - 1 背包问题具有最优子结构性质和重叠子问题性质。通过动态规划可以利用一个二维数组来记录子问题的解&#xff0c;避免重复计算&#xff0c;从而高效地求解出背包能装下的最大价值。分…

【阿里云】阿里云 Ubuntu 服务器无法更新 systemd(Operation not permitted)的解决方法

零、前言 目前正在使用的Ubuntu服务器中&#xff0c;仅阿里云&#xff08;不止一台&#xff09;出现了这个问题&#xff0c;因此我判定是阿里云服务器独有的问题。如果你的服务器提供商不是阿里云&#xff0c;那么这篇文章可能对你没有帮助。 如果已经因为升级错误导致依赖冲突…

css 点击后改变样式

背景&#xff1a; 期望实现效果&#xff1a;鼠标点击之后&#xff0c;保持选中样式。 实现思路&#xff1a;在css样式中&#xff0c;:active 是一种伪类&#xff0c;用于表示用户当前正在与被选定的元素进行交互。当用户点击或按住鼠标时&#xff0c;元素将被激活&#xff0c;此…

采用AI神经网络降噪算法的语言降噪消回音处理芯片NR2049-P

随着AI时代来临.通话设备的环境噪音抑制也进入AI降噪算法时代. AI神经网络降噪技术是一款革命性的语音处理技术&#xff0c;他突破了传统单麦克风和双麦克风降噪的局限性,利用采集的各种日常环境中的噪音样本进行训练学习.让降噪算法具有自适应噪声抑制功能&#xff0c;可以根…

不用联网不用编程,PLC通过智能网关快速实现HTTP协议JSON格式与MES等系统平台双向数据通讯

智能网关IGT-DSER集成了多种PLC的原厂协议&#xff0c;方便实现各种PLC、智能仪表通过HTTP协议与MES等各种系统平台通讯对接。PLC内不用编写程序&#xff0c;设备不用停机&#xff0c;通过网关的参数配置软件(下载地址)配置JSON文件的字段与PLC寄存器地址等参数即可。 …

如何将两台虚拟机进行搭桥

将两台虚拟机实现网络互通&#xff08;“搭桥”&#xff09;需配置虚拟网络&#xff0c;以下是基于 VMware Workstation 和 VirtualBox 的详细操作指南&#xff08;以 Windows 系统为例&#xff0c;Linux 原理类似&#xff09;&#xff1a; 一、VMware Workstation 配置&#x…

Xianyu AutoAgent,AI闲鱼客服机器人

Xianyu AutoAgent是一款专为闲鱼平台开发的智能客服机器人系统&#xff0c;旨在提供全天候的自动化服务。它具备多专家协同决策、智能议价和上下文感知对话等功能&#xff0c;能够管理轻量级的对话记忆&#xff0c;利用完整的对话历史为用户提供更自然的交流体验。 Xianyu Aut…

键盘输出希腊字符方法

在不同操作系统中&#xff0c;输出希腊字母的方法有所不同。以下是针对 Windows 和 macOS 系统的详细方法&#xff0c;以及一些通用技巧&#xff1a; 1.Windows 系统 1.1 使用字符映射表 字符映射表是一个内置工具&#xff0c;可以方便地找到并插入希腊字母。 • 步骤&#xf…

什么是SparkONYarn模式

1. 什么是 Spark on YARN&#xff1f; Spark on YARN 是 Apache Spark 的一种部署模式&#xff0c;允许 Spark 应用程序在 Hadoop YARN 集群上运行&#xff0c;充分利用 YARN 的资源管理和调度能力。这种模式将 Spark 与 Hadoop 生态深度集成&#xff0c;使企业能够在同一集群…

【git】clone项目后续,github clone的网络配置,大型项目git log 输出txt,切换commit学习,goland远程,自存档

git网络配置&#xff0c;解决git clone github速度奇慢 git config --global http.proxy http://127.0.0.1:7897 git config --global https.proxy http://127.0.0.1:7897git log输出到文件&#xff08;便于checkout&#xff09; 这里有些字符如表情会乱码&#xff0c;不知道…

Java游戏服务器开发流水账(3)游戏数据的缓存简介

简介 游戏服务器数据缓存是一种在游戏服务器运行过程中&#xff0c;用于临时存储经常访问的数据的技术手段&#xff0c;旨在提高游戏性能、降低数据库负载以及优化玩家体验。游戏开发中数据的缓存可以使用Java自身的内存也可以使用MemCache&#xff0c;Redis&#xff0c;注意M…

STL?vector!!!

一、前言 之前我们借助手撕string加深了类和对象相关知识&#xff0c;今天我们将一起手撕一个vector&#xff0c;继续深化类和对象、动态内存管理、模板的相关知识 二、vector相关的前置知识 1、什么是vector&#xff1f; vector是一个STL库中提供的类模板&#xff0c;它是存储…

C++学习之路,从0到精通的征途:继承

目录 一.继承的概念及定义 1.继承的概念 2.继承的定义 (1)继承的定义格式 (2)继承基类成员访问方式的变化 二.基类与派生类间的转换 1.派生类对象赋值给基类的引用/指针 2. 派生类对象直接赋值给基类对象 三.继承的作用域 四.派生类的默认成员函数 1.构造函数 2.拷…

用vue和go实现登录加密

前端使用CryptoJS默认加密方法&#xff1a; var pass CryptoJS.AES.encrypt(formData.password, key.value).toString()使用 CryptoJS.AES.encrypt() 时不指定加密模式和参数时&#xff0c;CryptoJS 默认会执行以下操作 var encrypted CryptoJS.AES.encrypt("明文&quo…

React百日学习计划——Deepseek版

阶段一&#xff1a;基础巩固&#xff08;1-20天&#xff09; 目标&#xff1a;掌握HTML/CSS/JavaScript核心语法和开发环境搭建。 每日学习内容&#xff1a; HTML/CSS&#xff08;1-10天&#xff09; 标签语义化、盒模型、Flex布局、Grid布局、响应式设计&#xff08;媒体查询…

WPF中如何自定义控件

WPF自定义控件简化版&#xff1a;账户菜单按钮&#xff08;AccountButton&#xff09; 我们以**“账户菜单按钮”为例&#xff0c;用更清晰的架构实现一个支持标题显示、渐变背景、选中状态高亮**的自定义控件。以下是分步拆解&#xff1a; 一、控件核心功能 我们要做一个类似…

Deepseek+Xmind:秒速生成思维导图与流程图

deepseekxmind&#xff0c;快速生成思维导图和流程图 文章目录 思维导图deepseek笔记本 txt文件xmind 流程图deepseekdraw.io 思维导图 deepseek 笔记本 txt文件 将deep seek的东西复制到文本文件中&#xff0c;然后将txt文件拓展名改成md xmind 新建思维导图----左上角三…