• [热门活动] MCP能否成为AI时代的“基础设施级协议”?
    MCP通过标准化接口设计,解决了传统AI与工具集成的碎片化问题,其客户端-服务器架构允许模型与外部资源(如数据库、API、本地文件)动态交互,形成类似“AI插件市场”的生态基础。大家怎么认为的。
  • [技术干货] javaSE————网络原理-转载
    今天巨无聊,全是概念,重点记一下五元组,TCP/IP五层模型和OSI七层调用模型,大家这期就当看故事啦;1,网络发展史1)独立模式我们刚开始使用计算机呢,客户端的数据不是共享的,如果有一个人想要办理业务,而这个业务所需的资源是在三台电脑上,那么这个人就需要在这三个电脑上不断的办理任务,而其他人想要办理业务,还需要等到前一个人办理完,效率非常低,那怎么办,我们就改进;2)网络互联接下来我们就使用网络进行计算机资源的共享,让多个计算机可以一起办理业务,达成数据共享,即网络通信,我们可以根据网络互联的规模分为局域网和广域网;3)局域网LAN局域网是本地,局部构建的一种私有网络,又被称为内网,局域网内的主机能够实现网络通信,局域网和局域网在没有连接的情况是不能进行通信的;组件局域网等待方式也有很多种,可以通过网线直连,也可以通过交换机相连,还可以通过集线器相连,还可以通过路由器连接交换机在与多个主机相连;4)广域网WAN广域网就是多个局域网完成了连接,很多很多的局域网都能进行网络通信,我们其实可以把咱们中国的网络看成一个巨大的广域网,我们管内部叫做内网,外面的就是我们常说的外网,有很多人可能对此不满,但这也是保护我们的一种方式,起码我们活的挺快乐的;不说了,再说被封了,哈哈哈哈哈哈;2,网络通信基础1)IP地址那么,广域网这么大,我们怎么能准确找到每个主机的所在呢,我们就使用IP地址来标识每个网络主机和网络设备的网络地址,我们可以通过CMD看自己主机的地址,输入这个命令ipconfig,就能看到了,那个IPv4地址就是我们的地址啦;2)端口号端口是啥玩意,我们有了地址,那么电脑发送或者我们接收了一个数据,难道我们只是通过地址就能知道吗,我们知道了地址,但不知道是哪个软件发送或者接收这个数据,比如发来一个QQ的数据报,那我们去给CSDN吗,不,我们应该是找到QQ的端口号,之后把这个数据给到QQ,让QQ来做相应的操作;我们可以把网络通信可以看成送快递,我们把IP地址看作收货地址,把端口号看作收件人;3)认识协议我们现在能找到地址和端口号了,我们网络传输的是二进制的数据,那么我们传入一段二级制指令,对方是怎么知道我们传的是什么东西呢,之前说过,图片,音频和视频都是二进制的指令,我们到一个数据报,我们怎么知道这是啥文件呢,去使用什么编码方式呢,所以就需要大家都统一一下,我们就约定网络传输的格式,我们就管它叫协议;协议的最终体现呢,就是网络传输数据报的格式;4)五元组在TCP/IP协议中,我们使用五元组来标识网络通信:1,源IP:标识源主机2,源端口号:标识源主机中该次通信发送的进程3,目的IP:标识目的主机4,目的端口号:标识源主机中该次通信接收的进程5,协议号:标识发送进程和接收进程双方约定的格式5)协议分层啥事协议分层呢,我们的协议很多,很复杂,我们把它分为不同层次的协议,让每个协议尽可能有自己的功能,OSI七层模型和TCP/IP五层模型,都把每层划分了很多不同的功能;OSI七层调用模型:层数    名称    功能    功能概览7    应用层    针对特定应用的协议    比如我们发送邮件,就用电子邮件协议,实现登录,就要使用登录协议6    表示层    数据固有格式和网络标准格式的转换    我们将接收的信息会根据网络标准格式转换为标准的信息5    会话层    通讯管理,负责建立和断开通讯    何时建立连接,何时断开连接和建立多久的连接;4    传输层    管理两个节点之间的数据传输,负责可靠传输    检查是否有数据丢失3    网络层    地址管理与路由选择    会考虑经过哪些路由到达地址2    数据链路层    互联数据的传送和识别数据帧    .....数据帧和比特流之间的转换1    物理层    以‘0’,‘1’代表电压高低,灯光闪灭,界定连接器和网线的规格    比特流与电子信号的转换这个我们大概了解即可,我们也是从网上扒下来的,我们会重点去学习应用层; TCP/IP通讯协议:TCP/IP模型其实就是OSI七层协议模型,只不过把OSI重新划分了一下,TCP/IP通讯协议采用五层的层级结构,每一层都可以呼叫下一层来给自己提供网络需求;5层,应用层:负责应用程序间沟通,如简单电⼦邮件传输(SMTP)、文件传输协议(FTP)、网络远 程访问协议(Telnet)等。我们的⽹络编程主要就是针对应⽤层。4层,传输层:负责两台主机之间的数据传输。如传输控制协议(TCP),能够确保数据可靠的从源主机发 送到⽬标主机,还有UDP。3层,网络层:负责地址管理和路由选择。例如在IP协议中,通过IP地址来标识⼀台主机,并通过路由表 的⽅式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)⼯作在⽹路层。2层,数据链路层:负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(就是说从⽹线上 检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就⾃动重发)、数据差错校验等⼯作。 有以太⽹、令牌环网,⽆线LAN等标准。交换机(Switch)工作在数据链路层。1层,物理层:负责光/电信号的传递方式。比如现在以太网通用的网线(双绞线)、早期以太网采用的的同 轴电缆(现在主要⽤于有线电视)、光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理 层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。这也是扒来的,下面我用自己的理解讲讲:——————5,应用层:就是我们拿到了包裹(数据包)后怎么样~4,传输层:任意两个设备的通信,不考虑传输过程,只考虑起点和终点;3,网络层:任意两个设备的通信,考虑中间的过程,传输过程可能有很多的交换机啥的;2,数据链路层:完成相邻两个设备之间的通信;1,物理层:规定网络通信中一些硬件设施的符合要求:我们拿送快递来举一个例子,比如我们网购一个手机,我们拿到手机之后怎么使用,就是应用层;商家发货的寄件方后收件方的地址,就是传输层;物流公司关心包裹是咋传输的,就是网络层;大车司机关心今天送到哪个地方,一个一个节点之间,就是数据链路层;TCP/IP协议栈其实里面包含了很多协议,但是最重要的就是TCP/IP协议了,我们再来谈谈主机,交换机和路由器都涉及到哪些层次:1,主机    工作涉及到    物理层到应用层(通过应用层满足数网络通信的要求);2,路由器    工作涉及    物理层到网路层(组件局域网,进行网络数据报的转发);3,交换机    工作涉及到    物理层到数据链路层(对路由器接口的扩展,不需要考虑组网问题);3,网络通信基本流程不同的协议层对数据包有不同的叫法,在传输层叫段,在网络层叫数据报,在数据链路层叫数据帧;应用层数据包,往往是结构化数据:我们发送数据的时候,会把结构化数据变成二进制比特流或者字符串,我们叫做序列化;我们接收数据的时候,会把二进制比特流或者字符串变成结构化数据,我们叫做反序列化;流程:我们使用QQ,发送Hello给对方;1,应用程序获取用户输入,构造一个应用层数据包,会遵守应用层协议(往往是程序员自己定制的)我们假设约定的格式为(发送者QQ,接收着QQ,消息时间,消息正文);2,应用层调用传输层API(socket api)把数据交给传输层,把数据拿到后,构造出传输层数据包,传输层的协议主要就是TCP和UDP;我们拿TCP数据包举例,TCP数据包 = TCP报头(TCP功能的相关属性) + TCP载荷(就是应用层的数据包);数据包就变成这样的了; 3,传输层数据包构造好之后,就会调用网络层的API,把传输层的数据包交给网络层,网络层来处理数据包,网络最重要的协议,IP协议我们又会加一个IP报头,IP数据包 = IP报头(包含很多信息,包括源IP和目的IP)  +  IP载荷(整个传输层的数据包);在这些报头中还包含了上一层所用协议的内容,4,IP协议继续调用数据链路层的API,把IP协议交给数据链路层,数据链路层的核心协议,以太网,根据以太网这个协议会在网络层的基础上进一步加工以太网数据帧 = 帧头 + 载荷 + 帧尾5,以太网继续把数据帧给硬件设备(网卡)网卡会把二进制的比特流发送出去,这才成功的发送出去 。发送数据我们我们从上到下的过程我们称为封住,反过来接收数据的时候我们从下到下的过程我们称为复用;————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2301_79083481/article/details/146917352
  • [技术干货] Java微服务架构:注册中心与配置中心-转载
    引言在微服务架构中,注册中心和配置中心是保障系统高可用、动态扩展的关键组件。本文将从核心概念出发,结合主流工具(如Nacos、Eureka、Consul、Apollo等),深入探讨它们的原理、适用场景及实战集成方法,帮助开发者快速落地微服务架构。一、注册中心(Service Registry)1.1 核心作用服务注册与发现:服务实例启动时向注册中心注册元数据(如IP、端口、健康状态),消费者动态发现服务列表。健康检查:实时监测服务实例状态,自动剔除故障节点。负载均衡:结合客户端/服务端负载均衡策略(如Ribbon、Spring Cloud LoadBalancer)。1.2 主流工具对比1. Netflix Eureka架构模型:AP(高可用性,最终一致性)。特点:轻量级,与Spring Cloud无缝集成,适合中小型项目。缺点:Netflix已停止维护,仅适合简单场景。代码示例:@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApp { ... }2. Alibaba Nacos架构模型:支持AP/CP模式动态切换。优势:一站式服务注册与配置管理,支持DNS和健康检查,适合云原生。适用场景:高并发、动态扩展的分布式系统。配置示例:spring:  cloud:    nacos:      discovery:        server-addr: 127.0.0.1:88483. Consul架构模型:CP(强一致性,基于Raft协议)。特点:多数据中心支持,集成KV存储、ACL权限控制。适用场景:跨机房、强一致性的金融级系统。4. Zookeeper架构模型:CP(基于ZAB协议)。特点:分布式协调服务,被Kafka、Dubbo等广泛使用。缺点:配置复杂,性能在高并发下可能成为瓶颈。二、配置中心(Configuration Center)2.1 核心作用集中管理配置:避免配置散落在各服务中,支持环境隔离(开发、测试、生产)。动态更新:无需重启服务,实时生效配置变更。版本控制:记录配置历史,支持回滚和审计。2.2 主流工具对比1. Spring Cloud Config特点:与Git/SVN集成,需配合Spring Cloud Bus实现动态刷新。代码示例:@RefreshScope@RestControllerpublic class ConfigController {    @Value("${app.config}")    private String config;}2. Nacos优势:配置实时推送,可视化界面,支持灰度发布。配置示例:spring:  cloud:    nacos:      config:        server-addr: 127.0.0.1:8848        namespace: dev3. Apollo特点:企业级功能(权限管理、多环境、多集群),配置变更实时生效。适用场景:中大型企业,对安全和审计要求高。4. Consul KV特点:通过Key-Value存储配置,适合已使用Consul的服务治理场景。三、实战:Nacos集成示例3.1 注册中心配置步骤1:启动Nacos Server下载Nacos Server(官网链接),启动命令:sh startup.sh -m standalone  # Linuxstartup.cmd -m standalone    # Windows步骤2:服务注册与发现添加依赖:<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>配置服务信息(application.yml):spring:  application:    name: order-service  cloud:    nacos:      discovery:        server-addr: localhost:8848启用服务发现:@SpringBootApplication@EnableDiscoveryClientpublic class OrderServiceApplication { ... }3.2 配置中心集成步骤1:创建配置文件在Nacos控制台(localhost:8848/nacos)创建配置:Data ID: order-service-dev.yamlGroup: DEFAULT_GROUP内容:app:  config: "Nacos动态配置示例"12步骤2:客户端配置添加依赖:<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>bootstrap.yml配置:spring:  application:    name: order-service  profiles:    active: dev  cloud:    nacos:      config:        server-addr: localhost:8848        file-extension: yaml        namespace: public动态读取配置:@RestController@RefreshScopepublic class ConfigController {    @Value("${app.config}")    private String config;}四、选型建议与生产实践4.1 工具对比表需求    注册中心推荐    配置中心推荐快速集成Spring Cloud    Eureka    Spring Cloud Config一体化解决方案    Nacos    Nacos强一致性、跨数据中心    Consul    Consul KV企业级配置管理    -    Apollo4.2 生产注意事项高可用部署:Nacos/Consul需集群部署,至少3节点避免脑裂。安全加固:启用Nacos鉴权、Consul ACL、Apollo Token机制。数据持久化:Nacos默认使用内嵌数据库,生产建议切换MySQL集群。五、总结注册中心和配置中心是微服务的“神经系统”,选择合适的工具能大幅提升系统稳定性和开发效率:轻量级场景:Eureka + Spring Cloud Config云原生架构:Nacos一站式解决方案企业级需求:Consul + Apollo————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/m0_51041242/article/details/147110568
  • [技术干货] Java Word转PDF的实现过程-转载
    简介:在IT领域,文档格式转换是常见的任务之一,特别是在管理大量文本数据时。本文将详细探讨如何利用Java技术将Word文档(.docx)转换成PDF格式。转换过程包括文件读取、解析、格式转换等多个技术步骤,并涉及对第三方库的使用。文章假设存在一个名为 DoxcToPdf 的工具或库,用于完成这一转换任务,并对整个过程进行详细解析,包括错误处理和性能优化的考虑。 1. Word到PDF转换概述在当今数字化办公环境中,文档格式的转换是一种常见的需求。将Word文档转换为PDF格式是其中一种重要的转换场景,尤其是在需要保留原文档格式、字体、图片及其他元素以便于分享和打印时。这种转换不仅涉及到文件内容的完整性和一致性,还包括对不同文档结构的理解和处理。在深入探讨技术细节之前,我们先简要了解一下转换流程的宏观概念及其背后的技术原理。1.1 Word到PDF转换的需求背景Microsoft Word格式(.doc或.docx)由于其强大的编辑功能,在创建文档和报告方面广受欢迎。然而,当涉及到跨平台共享、网页发布或打印需求时,PDF格式(便携式文档格式)因其固定的布局和格式而成为更佳的选择。PDF能够确保内容在不同设备和操作系统上的一致性,而且不需要额外的字体或布局软件。1.2 Word到PDF转换的技术路线要实现Word到PDF的转换,可以大致分为以下几个步骤:文件读取 :首先,需要从Word文档中读取内容和格式信息。这通常涉及到文件IO流的操作以及对Word文档结构的理解。内容解析 :解析读取到的内容,将文档中的文本、图片、表格等元素区分开,并提取相关的格式信息。格式转换 :将解析出的内容按照PDF的格式规范重新组织和渲染,生成新的PDF文件。内容重排与样式映射 :为了使PDF文件在视觉上与原Word文档保持一致,可能需要进行内容重排和样式的映射。文件整合与写入 :将转换后的内容整合并写入到PDF文件中。错误处理与性能优化 :确保转换过程的稳定性和性能,处理可能出现的异常情况。在接下来的章节中,我们将详细探讨上述每个步骤的技术细节,包括相关的Java技术栈、库的选择和使用以及最佳实践。通过深入分析这些步骤,你将获得将Word文档转换为PDF的专业技能,并能够优化转换过程以满足不同场景的需求。2. 文件读取技术2.1 Java IO流基础2.1.1 IO流的基本概念在Java中,IO流是进行输入(input)和输出(output)操作的基础。IO流提供了一系列的类和接口,用于处理不同类型的数据传输。在读取文件时,我们通常使用输入流,即从数据源(如文件)读取数据;相反,输出流则是将数据写入目标(如另一个文件)。Java的IO流基于字节流和字符流的概念,字节流主要处理二进制数据,而字符流处理的是字符数据,适用于文本文件。2.1.2 文件读取操作Java提供了 FileInputStream 和 FileReader 等类用于文件的读取。以下是一个简单的文件读取操作的示例代码:import java.io.FileInputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.FileChannel; public class FileReadExample {    public static void main(String[] args) {        try (FileInputStream fis = new FileInputStream("example.txt")) {            FileChannel fileChannel = fis.getChannel();            ByteBuffer buffer = ByteBuffer.allocate(1024);            int bytesRead = fileChannel.read(buffer);            while (bytesRead != -1) {                buffer.flip();                while (buffer.hasRemaining()) {                    System.out.print((char) buffer.get());                }                buffer.clear();                bytesRead = fileChannel.read(buffer);            }        } catch (IOException e) {            e.printStackTrace();        }    }}在上面的代码中,我们通过 FileInputStream 和 FileChannel 读取了 example.txt 文件,并使用 ByteBuffer 作为数据传输的载体。我们首先读取数据到缓冲区,然后将缓冲区内容打印出来,直到文件末尾。2.2 Java Zip技术解析2.2.1 Zip格式与文件压缩解压原理Zip是一种常用的文件压缩和存档格式,它支持文件的压缩,同时也支持将多个文件存储在单个压缩文件中。Zip格式通过使用压缩算法(如Deflate)来减小文件大小,从而节省存储空间和传输时间。在Java中,我们可以利用Zip相关的类,例如 ZipInputStream 和 ZipOutputStream ,来处理压缩和解压缩任务。2.2.2 使用Zip流读取Word文档在处理Word文档转换为PDF的过程中,可能需要先将压缩包内的.docx文件解压,然后再进行读取。使用 ZipInputStream 可以方便地实现这一过程。下面展示了如何利用 ZipInputStream 来读取Zip文件中的Word文档:import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.InputStream;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream; public class ZipReadExample {    public static void main(String[] args) {        try (ZipInputStream zis = new ZipInputStream(new FileInputStream("document.zip"))) {            ZipEntry entry = zis.getNextEntry();            while (entry != null) {                String name = entry.getName();                if (name.endsWith(".docx")) {                    System.out.println("File Found :: " + name);                    // Process the .docx file                    InputStream docxStream = new BufferedInputStream(zis);                    // Read .docx file content here                    docxStream.close();                }                entry = zis.getNextEntry();            }            zis.closeEntry();        } catch (Exception e) {            e.printStackTrace();        }    }}在这个例子中,我们使用 ZipInputStream 来遍历压缩文件中的所有条目,并检查每个条目的名称是否以 .docx 结尾,如果是,则进行后续的处理。这种读取方式为我们处理Word文档提供了便利,尤其是在涉及到复杂文件结构的情况下。在下一章节中,我们会继续深入探讨文件解析技术,其中包含了对XML解析技术的介绍和实战解析示例。3. 文件解析技术文件解析是将文件内容转换成计算机可识别的数据结构的过程,这对于文档处理尤为重要。在进行Word到PDF转换的过程中,我们需要深入理解文件格式,并据此解析、提取并重组内容。接下来,我们将对解析技术进行详细介绍,尤其是XML解析技术,这在处理如.docx这样的基于XML的现代文档格式中是不可或缺的。3.1 XML解析技术简介3.1.1 XML的组成与结构XML(可扩展标记语言)是一种标记语言,设计用来存储和传输数据。与HTML不同,XML不是为了显示数据而设计的,而是专注于数据内容的描述。它是一种元语言,用于定义其他特定领域的标记语言,从而允许用户定义自己的标签和属性。一个基本的XML文档由元素组成,这些元素以标签的形式出现。每个标签可以包含属性,还可以嵌套其他标签。XML文档必须有且仅有一个根元素,这表示文档的开始和结束。此外,XML还严格要求标签正确嵌套,所有标签都必须被关闭。下面是一个简单的XML文档示例:<?xml version="1.0" encoding="UTF-8"?><bookstore>    <book category="cooking">        <title lang="en">Everyday Italian</title>        <author>Giada De Laurentiis</author>        <year>2005</year>        <price>30.00</price>    </book></bookstore>3.1.2 解析XML的优势解析XML的优势在于其可读性强,以及便于进行数据交换。由于XML文档具有自我描述性质,因此更容易被不同的应用程序理解,这一点对于文档格式转换尤为重要。在转换过程中,XML允许我们精确地定位并处理文档中的各个部分,无论其结构多么复杂。另一个显著的优势是,XML文档的解析可以通过多种方法实现,这为开发人员提供了灵活性。例如,可以使用DOM解析器将整个文档加载到内存中作为树状结构进行操作;也可以使用SAX解析器逐个处理XML中的事件,这种方式对内存的需求较小。3.2 Java XML解析器实战3.2.1 JAXB解析示例JAXB(Java Architecture for XML Binding)是一个强大的库,可以将Java对象序列化为XML格式,或者将XML文档反序列化为Java对象。通过JAXB,我们可以更轻松地处理XML,因为我们可以操作对象而不是直接处理文本。下面的代码示例展示了如何使用JAXB将Java对象序列化为XML文件:import javax.xml.bind.annotation.XmlRootElement;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.JAXBContext;import javax.xml.bind.JAXBException;import javax.xml.bind.Marshaller; @XmlRootElementclass Book {    private String title;    private String author;    private int year;    private double price;    // Getters and setters...} public class JAXBExample {    public static void main(String[] args) {        try {            Book book = new Book();            book.setTitle("Everyday Italian");            book.setAuthor("Giada De Laurentiis");            book.setYear(2005);            book.setPrice(30.00);             JAXBContext context = JAXBContext.newInstance(Book.class);            Marshaller marshaller = context.createMarshaller();            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);            marshaller.marshal(book, System.out);        } catch (JAXBException e) {            e.printStackTrace();        }    }}3.2.2 DOM4J解析示例DOM4J是一个开源的Java XML API,用于读写XML文档。它支持DOM、SAX和JAXP,但主要侧重于SAX的性能和灵活性。DOM4J使用XPath表达式和XSLT转换作为核心API的一部分。下面的代码示例展示了如何使用DOM4J来解析一个简单的XML文档,并打印出根元素和其子元素:import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.DocumentHelper;import org.dom4j.Element; public class DOM4JExample {    public static void main(String[] args) {        try {            String xmlContent = "<bookstore><book><title>Everyday Italian</title><author>Giada De Laurentiis</author></book></bookstore>";            Document document = DocumentHelper.parseText(xmlContent);            Element rootElement = document.getRootElement();            System.out.println("Root element: " + rootElement.getName());            for (Element element : (List<Element>) rootElement.elements()) {                System.out.println("Child element: " + element.getName() + ", text: " + element.getText());            }        } catch (DocumentException e) {            e.printStackTrace();        }    }}通过使用这些示例,我们可以看到XML解析技术的多样性和实用性。这些技术的应用能够确保在进行Word到PDF转换时,能够准确无误地处理文档结构,确保最终输出的PDF文件内容准确且格式整洁。4. 格式转换技术4.1 处理Word文档4.1.1 Apache POI 库基础Apache POI是Apache Software Foundation的一个Java库,它提供了一套用于读写Microsoft Office格式文件的API。使用Apache POI可以轻松地处理Word文档,如.doc和.docx文件格式。 HSSF (Horrible Spreadsheet Format)和 XSSF 是Apache POI的两个子项目,分别用于处理旧版的 .xls 和较新的 .xlsx 格式的Excel文档。在处理Word文档方面, HWPF (Horrible Word Processor Format)用于处理 .doc 格式的文档,而 XWPF (XML Word Processor Format)则用于处理 .docx 格式的文档。要使用Apache POI库,首先需要将其添加到项目的依赖中。对于Maven项目,可以在 pom.xml 文件中添加以下依赖:<dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi</artifactId>    <version>版本号</version></dependency><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>版本号</version></dependency>Apache POI的设计是高度面向对象的,通过对象模型来模拟Word文档的结构。例如,在处理 .docx 格式时, XWPFDocument 类代表了一个Word文档对象,而 XWPFParagraph 代表一个段落, XWPFRun 代表段落内的文本运行(文本格式化)等。4.1.2 读取.docx文档内容读取 .docx 文档内容需要创建 XWPFDocument 对象,并通过该对象的API来访问文档的不同部分。下面的代码示例展示了如何读取 .docx 文档中的所有文本内容:import org.apache.poi.xwpf.usermodel.XWPFDocument;import org.apache.poi.xwpf.usermodel.XWPFParagraph; import java.io.FileInputStream;import java.io.IOException; public class ReadDocxExample {    public static void main(String[] args) {        try (FileInputStream fis = new FileInputStream("example.docx")) {            XWPFDocument document = new XWPFDocument(fis);            for (XWPFParagraph para : document.getParagraphs()) {                System.out.println(para.getText());            }        } catch (IOException e) {            e.printStackTrace();        }    }}在上述代码中,我们首先使用 FileInputStream 打开一个 .docx 文件,然后创建一个 XWPFDocument 对象来代表这个Word文档。通过调用 document.getParagraphs() 方法,我们可以获取文档中的所有段落,并遍历输出每个段落的内容。4.2 生成PDF文件4.2.1 iText 库使用方法iText 是一个强大的Java库,可以用来创建和操纵PDF文档。它提供了一系列API来生成PDF文件,包括文字、图像、表格、表单等。 iText 还支持将现有的Word文档转换成PDF格式。从2019年开始,iText以商业开源许可证发布,因此在使用之前需要确保许可证的合规性。要使用 iText ,你需要将以下依赖添加到项目的 pom.xml 文件中:<dependency>    <groupId>com.itextpdf</groupId>    <artifactId>itext7-core</artifactId>    <version>版本号</version></dependency>以下是使用 iText 7 将文本写入PDF文件的一个简单示例:import com.itextpdf.kernel.pdf.PdfDocument;import com.itextpdf.kernel.pdf.PdfWriter;import com.itextpdf.layout.Document;import com.itextpdf.layout.element.Paragraph; import java.io.FileNotFoundException; public class CreatePdfExample {    public static void main(String[] args) {        String dest = "output.pdf";        try (PdfWriter writer = new PdfWriter(dest);             PdfDocument pdf = new PdfDocument(writer);             Document document = new Document(pdf)) {            document.add(new Paragraph("Hello, World!"));        } catch (FileNotFoundException e) {            e.printStackTrace();        }    }}在这段代码中,我们首先创建了 PdfWriter 和 PdfDocument 对象。 PdfWriter 负责写入PDF文件, PdfDocument 代表整个PDF文档。然后,我们创建了一个 Document 对象,该对象代表正在操作的PDF文件。最后,我们向 Document 对象添加了一个包含文本的 Paragraph 元素,然后将PDF写入到指定的文件。4.2.2 PDFBox 库使用方法Apache PDFBox 是Apache软件基金会提供的一个开源项目,它是一个用来创建和操作PDF文档的Java库。这个库的功能包括创建新的PDF文档、渲染PDF内容以及从PDF文档中提取文字和图像等。与 iText 不同, PDFBox 更侧重于对PDF文件的读取和修改,而不是创建复杂的排版。首先,你需要将 PDFBox 添加到你的项目依赖中:<dependency>    <groupId>org.apache.pdfbox</groupId>    <artifactId>pdfbox</artifactId>    <version>版本号</version></dependency>下面的代码示例演示了如何使用 PDFBox 读取PDF文件并输出文件中的所有文本内容:import org.apache.pdfbox.pdmodel.PDDocument;import org.apache.pdfbox.text.PDFTextStripper; import java.io.File;import java.io.IOException; public class ReadPdfExample {    public static void main(String[] args) {        try (PDDocument document = PDDocument.load(new File("input.pdf"))) {            PDFTextStripper stripper = new PDFTextStripper();            String pdfContent = stripper.getText(document);            System.out.println(pdfContent);        } catch (IOException e) {            e.printStackTrace();        }    }}在这个例子中,我们使用 PDFTextStripper 类来获取PDF文档中的文本。首先加载一个PDF文件到 PDDocument 对象中,然后创建 PDFTextStripper 实例。调用 stripper.getText(document) 方法后,文档的全部文本内容将被提取并存储在字符串变量 pdfContent 中,然后输出。这两个工具— iText 和 PDFBox —提供了不同的功能,开发者可以根据具体需求选择合适的库来实现Word到PDF的转换。5. 内容重排与样式映射在将Word文档转换为PDF的过程中,内容重排与样式映射是确保转换质量的关键环节。合理的内容重排策略能够提升文档的可读性,而精确的样式映射则是保证最终PDF文件视觉效果一致性的基础。5.1 文档内容的重排策略内容重排主要关注文档的逻辑结构,合理的重排能够使信息传达更为清晰。5.1.1 理解文档结构的重要性在转换过程中,首先需要识别文档中的标题、段落、列表等元素。这是因为不同元素可能需要不同的布局和格式处理。例如,标题可能需要较大的字体和加粗样式,而列表项则可能需要缩进和特定的项目符号。// 示例代码:使用Apache POI解析.docx文档中的结构XWPFDocument document = new XWPFDocument(OPCPackage.open(new File("example.docx").getAbsolutePath()));List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {    CTParagraph ctParagraph = paragraph.getCTP();    List<CTR> elements = ctParagraph.getAbstractNumList();    // 遍历段落中的元素以识别结构    for (CTR element : elements) {        // ...    }}5.1.2 实现内容的逻辑重排逻辑重排通常涉及创建新的段落和列表,保持原有的文档格式但优化其展示。例如,在PDF中,原有的Word文档的标题可以通过分页和增加标题样式来获得更好的视觉效果。// 示例代码:在iText中创建新的段落和列表PdfPTable table = new PdfPTable(1);PdfPCell cell = new PdfPCell();cell.addElement(new Paragraph("Title 1"));cell.addElement(new Paragraph("Item 1"));table.addCell(cell);// 继续添加其他项...5.2 样式映射机制样式映射关注的是将Word文档中的样式转换为PDF文档中的等效样式。5.2.1 Word样式与PDF样式的对应关系Word文档中包含多种内建和自定义的样式,这些样式在PDF中可能没有直接的等效样式。因此,需要定义一个映射表来规定样式转换规则,比如将Word中的“标题1”样式映射为PDF中的“Heading 1”样式。// 示例代码:使用iText进行样式映射Map<String, String> styleMapping = new HashMap<>();styleMapping.put("Heading 1", PdfName.HEADING_1.toString());styleMapping.put("Heading 2", PdfName.HEADING_2.toString());// 其他样式映射...5.2.2 样式转换的实际应用案例在实际应用中,可能需要处理的样式类型不仅限于标题和列表,还可能包括图片、表格和引用等。样式转换的代码会根据不同文档的需要进行相应的调整。// 示例代码:转换段落样式Paragraph paragraph = new Paragraph();for (XWPFParagraph p : paragraphs) {    String style = p.getParagraphFormat().getBuiltInStyleId();    if (styleMapping.containsKey(style)) {        paragraph.add(new Paragraph(p.getText(), styleMapping.get(style)));    } else {        paragraph.add(new Paragraph(p.getText())); // 默认样式    }}在这一章节中,我们讨论了内容重排与样式映射的重要性,并通过代码示例展示了如何在实际应用中进行操作。下一章节将继续探讨如何整合所有模块,并实现最终的PDF文件写入。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/weixin_42181686/article/details/146289533
  • [技术干货] Java-servlet(完结篇)过滤器乱码解决与监听器-转载
    一、过滤器乱码解决在Web开发里,乱码问题常常让人头疼。不过,利用过滤器能有效解决这个问题。下面是一段解决乱码问题的过滤器代码:@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {    System.out.println("执行了过滤器-FilterDemo3");    // 先让请求通过过滤器链,处理后续的过滤器或目标资源    filterChain.doFilter(servletRequest, servletResponse);    // 设置响应内容类型为HTML,字符编码为UTF-8    servletResponse.setContentType("text/html;charset=utf-8");    // 将ServletRequest强转为HttpServletRequest,方便后续操作    HttpServletRequest request = (HttpServletRequest) servletRequest;    // 再次通过过滤器链,保证请求和响应都得到完整处理    filterChain.doFilter(servletRequest, servletResponse);}这段代码的过滤器,先让请求在过滤器链中传递,然后设置响应编码为UTF - 8,避免响应内容出现乱码。同时,再次调用过滤器链,确保整个请求和响应过程的完整性。二、监听器监听器是Servlet规范里的重要组件,能监听Web应用中的各种事件,并在事件发生时执行相应操作。监听器有23种模式,下面介绍几种常用的监听器。1. HttpSessionListener用于监听HttpSession的创建和销毁事件。代码示例如下:package javax.servlet.http;import java.util.EventListener;// HttpSessionListener继承自EventListenerpublic interface HttpSessionListener extends EventListener {    // 当HttpSession创建时调用此方法    void sessionCreated(HttpSessionEvent var1);    // 当HttpSession销毁时调用此方法    void sessionDestroyed(HttpSessionEvent var1);}使用时,要在web.xml文件中注册监听器:<listener>    <listener-class>listener.MySessionListener</listener-class></listener>然后实现自定义的监听器类MySessionListener:package listener;import javax.servlet.http.HttpSession;import javax.servlet.http.HttpSessionBindingEvent;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;public class MySessionListener implements HttpSessionListener {    // 当HttpSession创建时,打印日志    @Override    public void sessionCreated(HttpSessionEvent se) {        System.out.println("session Created");    }    // 当HttpSession销毁时,打印日志    @Override    public void sessionDestroyed(HttpSessionEvent se) {        System.out.println("session Destroyed");    }    // 这里额外重写一个处理属性添加的方法,虽然接口未定义,但可按需添加处理逻辑    @Override    public void attributeAdded(HttpSessionBindingEvent se) {        System.out.println("attribute added");    }}2. ServletContextListener用于监听ServletContext的创建和销毁事件ServletContext代表整个Web应用的上下文,通过它可以获取Web应用的初始化参数、共享资源等。示例代码如下:package javax.servlet;import java.util.EventListener;public interface ServletContextListener extends EventListener {    // 当ServletContext创建时调用此方法    void contextInitialized(ServletContextEvent sce);    // 当ServletContext销毁时调用此方法    void contextDestroyed(ServletContextEvent sce);}在web.xml中注册:<listener>    <listener-class>listener.MyServletContextListener</listener-class></listener>实现自定义的监听器类:package listener;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;public class MyServletContextListener implements ServletContextListener {    @Override    public void contextInitialized(ServletContextEvent sce) {        // 在Web应用启动时执行的操作,比如初始化数据库连接池        System.out.println("ServletContext初始化了");    }    @Override    public void contextDestroyed(ServletContextEvent sce) {        // 在Web应用关闭时执行的操作,比如关闭数据库连接池        System.out.println("ServletContext销毁了");    }}3. ServletRequestListener用于监听ServletRequest的创建和销毁事件。ServletRequest代表客户端的一次请求,通过监听它的生命周期,可以在请求处理前后进行一些通用操作。示例代码如下:package javax.servlet;import java.util.EventListener;public interface ServletRequestListener extends EventListener {    // 当ServletRequest创建时调用此方法    void requestInitialized(ServletRequestEvent sre);    // 当ServletRequest销毁时调用此方法    void requestDestroyed(ServletRequestEvent sre);}在web.xml中注册:<listener>    <listener-class>listener.MyServletRequestListener</listener-class></listener>实现自定义的监听器类:package listener;import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;public class MyServletRequestListener implements ServletRequestListener {    @Override    public void requestInitialized(ServletRequestEvent sre) {        // 在请求开始处理时执行的操作,比如记录请求开始时间        System.out.println("请求开始处理");    }    @Override    public void requestDestroyed(ServletRequestEvent sre) {        // 在请求处理结束时执行的操作,比如记录请求处理时间        System.out.println("请求处理结束");    }}三、监听器的使用场景统计在线用户数量:利用HttpSessionListener,在sessionCreated方法中让在线用户数加1,在sessionDestroyed方法中减1,从而实时统计在线用户数量。记录用户登录和退出时间:在HttpSession创建时记录登录时间,销毁时记录退出时间,便于分析用户行为。资源初始化和销毁:借助ServletContextListener,在Web应用启动时初始化全局资源,如数据库连接池;在应用关闭时释放资源,确保资源管理得当。请求日志记录:通过ServletRequestListener,在请求开始和结束时记录日志,方便排查问题和监控系统运行状态。Java-servlet 结语至此,Java Servlet 的入门讲解暂告一段落。通过这部分内容,我们已经搭建起 Web 开发的基础框架,理解了服务端与客户端交互的核心逻辑。接下来,我们将深入探讨 JSP(Java 服务器页面) 与 MySQL 数据库 的核心知识。JSP 作为 Servlet 的延伸,能更便捷地实现动态页面渲染。而 MySQL 则是企业级应用中最常用的关系型数据库之一。三者结合(Servlet 处理请求逻辑、JSP 构建动态视图、MySQL 存储数据),将形成一套完整的 Web 后端开发体系——这正是支撑现代 Web 项目的“三板斧”。掌握这三项核心技术,不仅能为前端开发提供坚实的后端支持,更能让你在构建完整 Web 应用时游刃有余。无论是小型项目的快速落地,还是大型系统的架构设计,它们都是不可或缺的基石。后续课程中,我们将通过实战案例串联知识,帮助大家更好地理解和应用。期待与你继续探索,一起夯实 Web 开发的核心能力!————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2402_83322742/article/details/146974428
  • [技术干货] 京东外卖对比美团外卖定位准确度对比分析
    一、技术投入与定位算法优势定位技术基础美团:作为外卖行业龙头,长期投入大量资源优化定位技术,采用多源数据融合定位(GPS+基站+WiFi+蓝牙信标+传感器数据),尤其在复杂场景(如室内、高楼林立区域)通过AI算法补偿信号误差。例如,美团的“超级大脑”系统可实时分析用户行为模式(如历史订单位置、常用地址),辅助定位纠偏。部分竞品:可能依赖基础地图API(如高德、百度)的定位服务,缺乏外卖场景的深度优化,导致在信号弱或环境复杂时误差较大。动态校准机制美团通过用户实时反馈(如“定位不准确”按钮)和骑手轨迹数据,持续迭代定位模型,形成闭环优化。例如,若某区域多次被用户标记为定位错误,系统会自动触发定位算法调整。二、业务规模与数据积累订单密度与数据样本美团覆盖全国超2800个县市区,日均订单量超5000万单,海量订单数据为定位算法提供了丰富的训练样本。例如,在CBD、城中村等复杂场景中,美团可基于高频订单轨迹优化定位策略,而订单量较少的平台可能因数据不足导致算法泛化能力弱。骑手轨迹网络美团骑手数量超500万,其移动轨迹数据(如GPS轨迹、停留点)可反向验证用户定位准确性。例如,若用户定位与骑手接单后的导航路径存在系统性偏差,系统会触发定位算法优化。三、场景化适配能力复杂场景优化室内定位:美团与商场、写字楼合作部署蓝牙信标,提升室内定位精度至3米内;部分竞品可能仅依赖WiFi指纹定位,精度波动较大。动态路况:美团算法可结合实时路况(如拥堵、单行道)调整定位策略,避免因导航路线偏差导致用户实际位置与系统定位不符。用户行为预判美团通过分析用户历史行为(如常点外卖地址、时段偏好),可提前预判用户位置。例如,若用户在工作日12点在某写字楼附近活动,系统会优先推荐该写字楼地址,减少用户手动调整定位的频率。四、用户体验与反馈闭环用户反馈机制美团在App内设置“定位不准确”一键反馈入口,用户反馈数据直接流入算法团队,实现快速迭代。例如,某区域一周内收到100次定位错误反馈,算法团队会在48小时内完成优化。骑手端协同骑手接单后若发现用户定位与实际地址不符,可通过App上报偏差数据,系统将同步更新定位模型。这种骑手-用户-平台的协同机制,使美团定位精度保持动态领先。五、战略投入与资源倾斜技术研发投入美团2022年研发投入超200亿元,其中定位技术是重点方向之一。相比之下,部分平台可能因资源有限,仅将定位技术作为基础功能,缺乏深度优化。本地生活生态协同美团通过到店、酒店、旅游等业务积累的POI数据(如商家地址、楼层信息),可反向优化外卖定位。例如,若某商场内某餐厅的定位数据在到店业务中已验证准确,可直接同步至外卖业务。总结美团外卖定位更准确的核心逻辑在于技术深度、数据规模、场景适配和反馈闭环的协同作用。其通过海量订单、骑手轨迹、用户反馈等多维数据构建的定位算法,在复杂场景下的容错率和动态校准能力显著优于部分竞品。此外,美团对本地生活生态的长期投入,也为其外卖定位技术提供了其他平台难以复制的“数据护城河”。
  • [技术干货] 坐标系之间的相互转换(WGS84、GCJ02、BD09)
    一、坐标系介绍WGS84:全球定位系统(GPS)所使用的标准坐标系,也是目前最常用的地理坐标系之一。GCJ02:由中国国家测绘局制订的地理信息系统坐标系,也被称为火星坐标系。它是在WGS84坐标系的基础上进行加密处理得到的,主要应用于国内的地图服务商如高德地图等提供的在线地图服务中。BD09:百度地图特有的坐标系,它在GCJ02坐标系的基础上进行了再次加密,以确保用户数据的安全性和准确性。二、相互转换WGS84到GCJ02的转换:由于WGS84是国际公认的标准坐标系,而GCJ02是对WGS84进行加密处理得到的坐标系,因此可以通过特定的加密算法将WGS84坐标系下的经纬度转换为GCJ02坐标系下的经纬度。GCJ02到WGS84的转换:与WGS84到GCJ02的转换相反,可以通过相应的解密算法将GCJ02坐标系下的经纬度转换回WGS84坐标系下的经纬度。GCJ02到BD09的转换:BD09是在GCJ02的基础上进行再次加密得到的坐标系,因此可以通过特定的加密算法将GCJ02坐标系下的经纬度转换为BD09坐标系下的经纬度。BD09到GCJ02的转换:与GCJ02到BD09的转换相反,可以通过相应的解密算法将BD09坐标系下的经纬度转换回GCJ02坐标系下的经纬度。三、转换工具与方法使用专业软件或插件:可以使用如QGIS等专业地理信息系统软件或插件进行坐标系的转换。这些软件和插件通常提供了丰富的坐标系转换功能,并支持多种常见的坐标系之间的转换。编写转换算法:对于有编程能力的人员,可以编写相应的转换算法进行坐标系的转换。这些算法通常基于特定的数学公式和加密算法,可以实现高精度的坐标系转换。使用在线转换工具:还可以利用一些在线的坐标系转换工具进行转换。这些工具通常提供了简单易用的界面,用户只需输入要转换的坐标值并选择相应的坐标系即可得到转换结果。综上所述,WGS84、GCJ02和BD09坐标系之间是可以相互转换的,但需要注意转换过程中的精度和误差问题。在实际应用中,应根据具体需求选择合适的转换方法和工具进行坐标系的转换。
  • [技术干货] Java 中 Button 按钮的方法与介绍
    在 Java 中,按钮(Button)是 GUI 编程中最常用的组件之一,主要用于触发用户操作。根据使用的 GUI 框架不同,Button 的实现方式也有所不同。下面我将介绍 Swing 和 JavaFX 两种主要框架中的 Button 使用方法。1. Swing 中的 JButton基本介绍JButton 是 Swing 包中的按钮组件,位于 javax.swing 包中。常用构造方法JButton() // 创建一个无文本、无图标的按钮JButton(String text) // 创建带有指定文本的按钮JButton(Icon icon) // 创建带有指定图标的按钮JButton(String text, Icon icon) // 创建带有指定文本和图标的按钮常用方法设置属性void setText(String text) // 设置按钮文本String getText() // 获取按钮文本void setIcon(Icon icon) // 设置按钮图标Icon getIcon() // 获取按钮图标void setEnabled(boolean enabled) // 启用/禁用按钮boolean isEnabled() // 检查按钮是否启用void setToolTipText(String text) // 设置鼠标悬停提示文本外观控制void setBackground(Color bg) // 设置背景色void setForeground(Color fg) // 设置前景色(文本颜色)void setFont(Font font) // 设置字体事件处理void addActionListener(ActionListener l) // 添加动作监听器void removeActionListener(ActionListener l) // 移除动作监听器基本使用示例import javax.swing.*;import java.awt.*;import java.awt.event.*;public class SwingButtonExample { public static void main(String[] args) { // 创建主窗口 JFrame frame = new JFrame("JButton 示例"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300, 200); frame.setLayout(new FlowLayout()); // 创建按钮 JButton button = new JButton("点击我"); // 添加事件监听器 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(frame, "按钮被点击了!"); } }); // 将按钮添加到窗口 frame.add(button); // 显示窗口 frame.setVisible(true); }}2. JavaFX 中的 Button基本介绍Button 是 JavaFX 中的按钮组件,位于 javafx.scene.control 包中。常用构造方法Button() // 创建一个无文本的按钮Button(String text) // 创建带有指定文本的按钮Button(String text, Node graphic) // 创建带有文本和图标的按钮常用方法设置属性void setText(String text) // 设置按钮文本String getText() // 获取按钮文本void setGraphic(Node graphic) // 设置按钮图标Node getGraphic() // 获取按钮图标void setDisable(boolean disable) // 禁用/启用按钮boolean isDisabled() // 检查按钮是否禁用void setTooltip(Tooltip tooltip) // 设置鼠标悬停提示外观控制void setStyle(String style) // 设置CSS样式void setId(String id) // 设置ID用于CSS选择器void setPrefSize(double width, double height) // 设置首选大小事件处理void setOnAction(EventHandler<ActionEvent> value) // 设置动作事件处理器基本使用示例import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Alert;import javafx.scene.control.Alert.AlertType;import javafx.scene.layout.StackPane;import javafx.stage.Stage;public class JavaFXButtonExample extends Application { @Override public void start(Stage primaryStage) { // 创建按钮 Button btn = new Button(); btn.setText("点击我"); // 设置按钮事件 btn.setOnAction(event -> { Alert alert = new Alert(AlertType.INFORMATION); alert.setTitle("信息"); alert.setHeaderText(null); alert.setContentText("按钮被点击了!"); alert.showAndWait(); }); // 创建布局并添加按钮 StackPane root = new StackPane(); root.getChildren().add(btn); // 创建场景 Scene scene = new Scene(root, 300, 250); // 设置舞台 primaryStage.setTitle("JavaFX Button 示例"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); }}3. 高级功能Swing JButton 高级功能设置按钮图标状态:button.setPressedIcon(pressedIcon); // 按下时显示的图标button.setRolloverIcon(rolloverIcon); // 鼠标悬停时显示的图标button.setDisabledIcon(disabledIcon); // 禁用时显示的图标设置按钮边框:button.setBorder(BorderFactory.createLineBorder(Color.RED));设置快捷键:button.setMnemonic(KeyEvent.VK_C); // 设置Alt+C为快捷键JavaFX Button 高级功能使用CSS样式:btn.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white;");添加图标:ImageView imageView = new ImageView(new Image("file:icon.png"));btn.setGraphic(imageView);设置按钮效果:btn.setEffect(new DropShadow());总结:对于新的Java GUI项目,建议使用JavaFX,因为它是Oracle推荐的现代GUI框架对于维护旧项目或需要轻量级解决方案时,可以使用SwingJavaFX提供了更丰富的视觉效果和现代化的API,而Swing则更成熟稳定无论是使用Swing还是JavaFX,Button都是创建交互式用户界面的基础组件,掌握其使用方法对于Java GUI开发至关重要。
  • [技术干货] Java 计算字符串哈希值的几种方案
    1. 使用 String 类的 hashCode() 方法Java 的 String 类自带一个 hashCode() 方法,该方法返回一个 int 类型的哈希值。这个哈希值是基于字符串的内容计算得出的。String str = "Hello, World!"; int hash = str.hashCode(); System.out.println("Hash code: " + hash); 2. 使用 MessageDigest 类进行更复杂的哈希如果你需要更复杂的哈希(如 MD5、SHA-1、SHA-256 等),可以使用 java.security.MessageDigest 类。这些哈希算法返回的是字节数组,你可以将其转换为十六进制字符串表示,但本质上哈希计算的结果是字节数组。import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class HashExample { public static void main(String[] args) { try { String str = "Hello, World!"; MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hashBytes = md.digest(str.getBytes()); // 将字节数组转换为十六进制字符串(如果需要字符串表示) StringBuilder hexString = new StringBuilder(); for (byte b : hashBytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) hexString.append('0'); hexString.append(hex); } System.out.println("SHA-256 Hash: " + hexString.toString()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } } 总结String.hashCode() 返回一个 int 类型的哈希值。使用 MessageDigest 可以计算更复杂的哈希(如 MD5、SHA-256),返回字节数组,可以转换为十六进制字符串表示。如果你只需要一个简单的整数哈希值,String.hashCode() 是一个直接且方便的选择。如果你需要更安全的哈希(如用于密码存储),则应使用 MessageDigest 或类似的库来计算更复杂的哈希。
  • [大赛资讯] 复赛名次在哪公布?
    复赛名次在哪公布?复赛名次在哪公布
  • [技术干货] 使用JavaScript获取和解析页面内容的完整指南
    1. 理解DOM和页面结构在开始获取和解析页面内容之前,我们需要理解DOM(Document Object Model)的概念。DOM是将HTML或XML文档表示为树状结构的编程接口,其中每个节点都是文档的一部分,如元素、属性或文本。1.1 DOM树结构DOM将文档表示为节点树,其中:文档节点是整个文档的根节点元素节点代表HTML元素属性节点代表HTML属性文本节点包含元素内的文本内容<!DOCTYPE html><html><head>    <title>示例页面</title></head><body>    <h1>欢迎</h1>    <p class="intro">这是一个示例段落。</p></body></html>对应的DOM树结构:Documenthtmlheadtitle“示例页面” (文本节点)bodyh1“欢迎” (文本节点)p (class属性为"intro")“这是一个示例段落。” (文本节点)1.2 为什么需要解析页面内容解析页面内容有许多实际应用:网页抓取(Web Scraping)内容分析自动化测试浏览器扩展开发数据提取和转换2. 获取整个页面的HTML代码2.1 使用document.documentElement.outerHTML获取整个页面HTML代码的最简单方法是使用document.documentElement.outerHTML属性:const fullPageHTML = document.documentElement.outerHTML;console.log(fullPageHTML); // 输出完整的HTML文档原理:document.documentElement代表HTML文档的根元素(通常是<html>元素)outerHTML属性获取元素及其所有子元素的HTML表示2.2 使用document.documentElement.innerHTML如果只需要<html>元素内部的内容(不包括<html>标签本身),可以使用:const htmlContent = document.documentElement.innerHTML;console.log(htmlContent); // 输出<html>内部的所有内容2.3 使用document.getElementsByTagName(‘html’)[0]另一种获取整个HTML内容的方式:const htmlElement = document.getElementsByTagName('html')[0];const fullHTML = htmlElement.outerHTML;console.log(fullHTML);2.4 获取DOCTYPE声明如果需要包含DOCTYPE声明,可以组合使用:const doctype = document.doctype;const doctypeString = doctype ? `<!DOCTYPE ${doctype.name}${doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : ''}${doctype.systemId ? ` "${doctype.systemId}"` : ''}>` : '';const fullDocument = doctypeString + document.documentElement.outerHTML;console.log(fullDocument);3. 解析页面内容获取HTML代码后,下一步是解析其中的内容。JavaScript提供了多种方法来选择和操作DOM元素。3.1 使用DOM选择器方法3.1.1 getElementById通过元素的ID获取单个元素:const header = document.getElementById('header');console.log(header.textContent);3.1.2 getElementsByClassName通过类名获取元素集合:const items = document.getElementsByClassName('item');Array.from(items).forEach(item => {    console.log(item.textContent);});3.1.3 getElementsByTagName通过标签名获取元素集合:const paragraphs = document.getElementsByTagName('p');Array.from(paragraphs).forEach(p => {    console.log(p.innerHTML);});3.1.4 querySelector和querySelectorAll使用CSS选择器语法选择元素:// 获取第一个匹配的元素const firstItem = document.querySelector('.list-item');console.log(firstItem.textContent);// 获取所有匹配的元素const allItems = document.querySelectorAll('.list-item');allItems.forEach(item => {    console.log(item.textContent);});3.2 遍历DOM树3.2.1 父节点和子节点const parent = document.querySelector('.parent');const children = parent.children; // 获取所有子元素// 遍历子节点Array.from(children).forEach(child => {    console.log(child.tagName);});// 获取父节点const child = document.querySelector('.child');const parentNode = child.parentNode;console.log(parentNode.tagName);3.2.2 兄弟节点const item = document.querySelector('.item');const nextSibling = item.nextElementSibling;const previousSibling = item.previousElementSibling;console.log('下一个兄弟节点:', nextSibling);console.log('上一个兄弟节点:', previousSibling);3.2.3 递归遍历整个DOM树function traverseDOM(node, depth = 0) {    // 打印当前节点信息    console.log(`${' '.repeat(depth * 2)}${node.nodeName}${node.nodeValue ? `: ${node.nodeValue.trim()}` : ''}`);        // 如果有子节点,递归遍历    if (node.childNodes && node.childNodes.length > 0) {        Array.from(node.childNodes).forEach(child => {            traverseDOM(child, depth + 1);        });    }}// 从body开始遍历traverseDOM(document.body);3.3 提取元素属性和内容3.3.1 获取元素属性const link = document.querySelector('a');console.log('href:', link.getAttribute('href'));console.log('class:', link.className);console.log('id:', link.id);console.log('所有属性:', link.attributes);3.3.2 获取元素文本内容const paragraph = document.querySelector('p');console.log('textContent:', paragraph.textContent); // 包括隐藏元素的文本console.log('innerText:', paragraph.innerText); // 仅显示文本,受CSS影响console.log('innerHTML:', paragraph.innerHTML); // 包含HTML标签3.3.3 获取表单元素值const input = document.querySelector('input[type="text"]');console.log('输入值:', input.value);![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a3878042aafe4c62b4b69a2fa122c956.png)const checkbox = document.querySelector('input[type="checkbox"]');console.log('是否选中:', checkbox.checked);const select = document.querySelector('select');console.log('选择的值:', select.value);console.log('选择的文本:', select.options[select.selectedIndex].text);4. 高级解析技术4.1 使用XPath解析XPath提供了一种在XML/HTML文档中导航和选择节点的强大方式:// 评估XPath表达式function evaluateXPath(xpath, context = document) {    const result = [];    const query = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);        for (let i = 0; i < query.snapshotLength; i++) {        result.push(query.snapshotItem(i));    }        return result;}// 使用示例:获取所有h2标题的文本const headings = evaluateXPath('//h2');headings.forEach(h2 => {    console.log(h2.textContent);});4.2 使用TreeWalker遍历DOMTreeWalker接口提供了更灵活的DOM遍历方式:const treeWalker = document.createTreeWalker(    document.body, // 根节点    NodeFilter.SHOW_ELEMENT, // 只显示元素节点    { acceptNode: function(node) {         return node.tagName === 'P' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;     }}, // 只接受<p>元素    false);const paragraphs = [];let currentNode = treeWalker.nextNode();while (currentNode) {    paragraphs.push(currentNode);    currentNode = treeWalker.nextNode();}console.log('找到的段落:', paragraphs);4.3 使用DOMParser解析HTML字符串如果需要解析HTML字符串而不是现有文档:const htmlString = `<html><body><h1>标题</h1><p>段落内容</p></body></html>`;const parser = new DOMParser();const doc = parser.parseFromString(htmlString, 'text/html');// 现在可以像普通DOM一样操作const title = doc.querySelector('h1');console.log(title.textContent); // 输出"标题"4.4 使用MutationObserver监听DOM变化如果需要监控DOM的变化:const observer = new MutationObserver(mutations => {    mutations.forEach(mutation => {        console.log('DOM发生了变化:', mutation);                if (mutation.addedNodes.length) {            console.log('添加的节点:', mutation.addedNodes);        }                if (mutation.removedNodes.length) {            console.log('移除的节点:', mutation.removedNodes);        }    });});// 开始观察body元素及其子元素的变化observer.observe(document.body, {    childList: true,    subtree: true,    attributes: true,    characterData: true});// 停止观察// observer.disconnect();5. 实际应用示例5.1 提取所有链接function extractAllLinks() {    const links = document.querySelectorAll('a[href]');    const urls = Array.from(links).map(link => {        return {            text: link.textContent.trim(),            href: link.getAttribute('href'),            title: link.getAttribute('title') || ''        };    });        console.log('页面中的所有链接:', urls);    return urls;}extractAllLinks();5.2 提取文章内容function extractArticleContent() {    // 尝试找到可能包含文章内容的元素    const potentialSelectors = [        'article',         '.article',         '.post',         '.content',         'main',         '#main'    ];        let articleElement = null;        for (const selector of potentialSelectors) {        const element = document.querySelector(selector);        if (element) {            articleElement = element;            break;        }    }        // 如果没有找到特定元素,尝试启发式方法    if (!articleElement) {        // 查找包含多个段落的最长元素        const allElements = document.querySelectorAll('body *');        let maxLength = 0;                allElements.forEach(el => {            const textLength = el.textContent.trim().length;            const paragraphCount = el.querySelectorAll('p').length;                        if (textLength > maxLength && paragraphCount > 1) {                maxLength = textLength;                articleElement = el;            }        });    }        if (articleElement) {        const title = document.querySelector('h1') ||                      document.querySelector('title') ||                      { textContent: '无标题' };                const paragraphs = Array.from(articleElement.querySelectorAll('p'))            .map(p => p.textContent.trim())            .filter(text => text.length > 0);                const images = Array.from(articleElement.querySelectorAll('img'))            .map(img => img.getAttribute('src'));                return {            title: title.textContent.trim(),            paragraphs,            images        };    }        return null;}console.log('提取的文章内容:', extractArticleContent());5.3 提取表格数据function extractTableData() {    const tables = document.querySelectorAll('table');    const tableData = [];        tables.forEach((table, index) => {        const rows = table.querySelectorAll('tr');        const data = [];                rows.forEach(row => {            const cells = row.querySelectorAll('td, th');            const rowData = Array.from(cells).map(cell => cell.textContent.trim());            data.push(rowData);        });                tableData.push({            tableIndex: index + 1,            rows: data        });    });        console.log('提取的表格数据:', tableData);    return tableData;}extractTableData();5.4 提取元数据function extractMetaData() {    const metaTags = document.querySelectorAll('meta');    const metadata = {};        metaTags.forEach(tag => {        const name = tag.getAttribute('name') ||                     tag.getAttribute('property') ||                     tag.getAttribute('itemprop');        const content = tag.getAttribute('content');                if (name && content) {            metadata[name] = content;        }    });        // 获取标题    metadata.title = document.title;        // 获取描述(优先从meta标签获取)    if (!metadata.description) {        const firstParagraph = document.querySelector('p');        if (firstParagraph) {            metadata.description = firstParagraph.textContent.trim().substring(0, 150) + '...';        }    }        // 获取关键词    if (!metadata.keywords) {        metadata.keywords = [];    } else if (typeof metadata.keywords === 'string') {        metadata.keywords = metadata.keywords.split(',').map(k => k.trim());    }        console.log('页面元数据:', metadata);    return metadata;}extractMetaData();6. 处理动态内容现代网页经常使用JavaScript动态加载内容,这给内容提取带来了挑战。6.1 检测动态加载的内容// 使用MutationObserver检测动态加载的内容function watchForDynamicContent(callback) {    const observer = new MutationObserver(mutations => {        mutations.forEach(mutation => {            if (mutation.addedNodes.length) {                callback(mutation.addedNodes);            }        });    });        observer.observe(document.body, {        childList: true,        subtree: true    });        return observer;}// 示例:检测新加载的内容并提取其中的链接const dynamicLinks = new Set();const observer = watchForDynamicContent(nodes => {    nodes.forEach(node => {        if (node.nodeType === Node.ELEMENT_NODE) {            const links = node.querySelectorAll('a[href]');            links.forEach(link => {                const href = link.getAttribute('href');                if (!dynamicLinks.has(href)) {                    dynamicLinks.add(href);                    console.log('发现新链接:', href);                }            });        }    });});// 停止观察// observer.disconnect();6.2 等待特定元素出现function waitForElement(selector, timeout = 5000) {    return new Promise((resolve, reject) => {        if (document.querySelector(selector)) {            return resolve(document.querySelector(selector));        }                const observer = new MutationObserver(mutations => {            if (document.querySelector(selector)) {                observer.disconnect();                resolve(document.querySelector(selector));            }        });                observer.observe(document.body, {            childList: true,            subtree: true        });                setTimeout(() => {            observer.disconnect();            reject(new Error(`等待元素 "${selector}" 超时`));        }, timeout);    });}// 使用示例waitForElement('.dynamic-content')    .then(element => {        console.log('元素已加载:', element);    })    .catch(error => {        console.error(error);    });6.3 模拟滚动以加载更多内容async function scrollToLoadAllContent() {    let lastHeight = document.body.scrollHeight;    let attempts = 0;    const maxAttempts = 10;        while (attempts < maxAttempts) {        // 滚动到底部        window.scrollTo(0, document.body.scrollHeight);                // 等待内容加载        await new Promise(resolve => setTimeout(resolve, 2000));                // 检查高度是否变化        const newHeight = document.body.scrollHeight;        if (newHeight === lastHeight) {            break;        }                lastHeight = newHeight;        attempts++;    }        console.log('完成滚动,最终高度:', lastHeight);}// 使用示例scrollToLoadAllContent().then(() => {    console.log('所有内容已加载(或达到最大尝试次数)');});7. 性能优化和最佳实践7.1 批量操作减少重绘// 不推荐的方式(每次循环都会导致重绘)const items = document.querySelectorAll('.item');items.forEach(item => {    item.style.color = 'red';});// 推荐的方式(使用文档片段批量操作)const fragment = document.createDocumentFragment();const newItems = Array(10).fill().map((_, i) => {    const div = document.createElement('div');    div.className = 'item';    div.textContent = `项目 ${i + 1}`;    fragment.appendChild(div);    return div;});document.body.appendChild(fragment);7.2 使用事件委托提高性能// 不推荐的方式(为每个元素添加事件监听器)document.querySelectorAll('.clickable-item').forEach(item => {    item.addEventListener('click', handleClick);});// 推荐的方式(事件委托)document.body.addEventListener('click', event => {    if (event.target.closest('.clickable-item')) {        handleClick(event);    }});function handleClick(event) {    console.log('点击的项目:', event.target);}7.3 缓存DOM查询结果// 不推荐的方式(多次查询相同的元素)function updateElements() {    document.querySelector('.item').style.color = 'red';    document.querySelector('.item').style.fontSize = '16px';    document.querySelector('.item').textContent = '更新后的文本';}// 推荐的方式(缓存查询结果)function updateElementsOptimized() {    const item = document.querySelector('.item');    item.style.color = 'red';    item.style.fontSize = '16px';    item.textContent = '更新后的文本';}7.4 使用更高效的选择器// 不高效的选择器(过于通用)const allDivs = document.querySelectorAll('div div div');// 更高效的选择器(更具体)const specificDivs = document.querySelectorAll('.container > .wrapper > .content');8. 安全考虑8.1 防止XSS攻击当处理动态内容时,要注意防范XSS(跨站脚本)攻击:// 不安全的方式(直接插入HTML)function unsafeInsert(content) {    document.querySelector('.output').innerHTML = content;}// 安全的方式(使用textContent或DOMPurify)function safeInsert(content) {    // 方法1:仅插入文本    document.querySelector('.output').textContent = content;        // 方法2:使用DOMPurify清理HTML    // const clean = DOMPurify.sanitize(content);    // document.querySelector('.output').innerHTML = clean;}8.2 处理用户生成的内容function sanitizeUserInput(input) {    // 移除脚本标签    let sanitized = input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');        // 移除危险的属性    sanitized = sanitized.replace(/\son\w+="[^"]*"/g, '');        // 其他清理逻辑...    return sanitized;}const userInput = '<script>alert("XSS")</script><img src="x" onerror="alert(1)">';console.log('清理后的输入:', sanitizeUserInput(userInput));9. 跨域限制和解决方案9.1 同源策略限制浏览器出于安全考虑实施了同源策略,限制了从不同源(协议、域名、端口)加载和操作内容的能力。9.2 使用CORS如果目标服务器支持CORS(跨源资源共享),可以直接请求:fetch('https://api.example.com/data', {    method: 'GET',    mode: 'cors',    headers: {        'Content-Type': 'application/json'    }}).then(response => response.json()).then(data => console.log(data)).catch(error => console.error('错误:', error));9.3 使用代理服务器对于不支持CORS的网站,可以通过自己的服务器代理请求:// 前端代码fetch('/proxy?url=' + encodeURIComponent('https://example.com'))    .then(response => response.text())    .then(html => {        const parser = new DOMParser();        const doc = parser.parseFromString(html, 'text/html');        // 解析文档...    });// 服务器端(Node.js示例)/*app.get('/proxy', async (req, res) => {    const { url } = req.query;    try {        const response = await axios.get(url);        res.send(response.data);    } catch (error) {        res.status(500).send('代理请求失败');    }});*/9.4 浏览器扩展解决方案如果是开发浏览器扩展,可以使用chrome.webRequest API绕过某些限制:// 在manifest.json中声明权限/*"permissions": [    "webRequest",    "webRequestBlocking",    "<all_urls>"]*/// 在background.js中/*chrome.webRequest.onBeforeSendHeaders.addListener(    details => {        // 修改请求头        details.requestHeaders.push({            name: 'Origin',            value: 'https://your-extension-id.chromiumapp.org'        });        return { requestHeaders: details.requestHeaders };    },    { urls: ['<all_urls>'] },    ['blocking', 'requestHeaders']);*/10. 完整的页面解析工具示例下面是一个完整的示例,展示如何构建一个功能丰富的页面解析工具:class PageParser {    constructor() {        this.parsedData = {            metadata: {},            structure: {},            content: {},            resources: {}        };    }        // 解析页面元数据    parseMetadata() {        // 标题        this.parsedData.metadata.title = document.title;                // meta标签        this.parsedData.metadata.metaTags = {};        document.querySelectorAll('meta').forEach(tag => {            const name = tag.getAttribute('name') ||                         tag.getAttribute('property') ||                         tag.getAttribute('itemprop');            if (name) {                this.parsedData.metadata.metaTags[name] = tag.getAttribute('content');            }        });                // 链接标签        this.parsedData.metadata.links = [];        document.querySelectorAll('link').forEach(link => {            this.parsedData.metadata.links.push({                rel: link.getAttribute('rel'),                href: link.getAttribute('href'),                type: link.getAttribute('type')            });        });                return this;    }        // 分析页面结构    analyzeStructure() {        // 统计各类元素数量        this.parsedData.structure.elementCounts = {};        const allElements = document.querySelectorAll('*');        Array.from(allElements).forEach(el => {            const tag = el.tagName.toLowerCase();            this.parsedData.structure.elementCounts[tag] =                 (this.parsedData.structure.elementCounts[tag] || 0) + 1;        });                // 获取主要内容区域        this.parsedData.structure.mainContent = this.findMainContent();                return this;    }        // 查找主要内容区域    findMainContent() {        const contentSelectors = [            'main',            'article',            '.main-content',            '.content',            '#content',            '.article',            '.post'        ];                for (const selector of contentSelectors) {            const element = document.querySelector(selector);            if (element) {                return {                    selector,                    textLength: element.textContent.length,                    paragraphCount: element.querySelectorAll('p').length                };            }        }                // 启发式方法:查找包含最多文本的元素        let maxLength = 0;        let mainElement = null;                document.querySelectorAll('body > div, body > section').forEach(el => {            const length = el.textContent.length;            if (length > maxLength) {                maxLength = length;                mainElement = el;            }        });                return mainElement ? {            selector: this.generateSelector(mainElement),            textLength: mainElement.textContent.length,            paragraphCount: mainElement.querySelectorAll('p').length        } : null;    }        // 生成元素选择器    generateSelector(element) {        if (element.id) {            return `#${element.id}`;        }                const path = [];        let current = element;                while (current && current !== document.body) {            let selector = current.tagName.toLowerCase();                        if (current.className && typeof current.className === 'string') {                const classes = current.className.split(/\s+/).filter(c => c);                if (classes.length) {                    selector += `.${classes.join('.')}`;                }            }                        // 如果有兄弟元素,添加:nth-child            const siblings = Array.from(current.parentNode.children);            const index = siblings.indexOf(current);            if (siblings.length > 1) {                selector += `:nth-child(${index + 1})`;            }                        path.unshift(selector);            current = current.parentNode;        }                return path.join(' > ');    }        // 提取页面内容    extractContent() {        // 提取所有文本段落        this.parsedData.content.paragraphs = Array.from(document.querySelectorAll('p'))            .map(p => p.textContent.trim())            .filter(text => text.length > 0);                // 提取所有标题        this.parsedData.content.headings = {};        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {            this.parsedData.content.headings[tag] = Array.from(document.querySelectorAll(tag))                .map(el => el.textContent.trim());        });                // 提取图片        this.parsedData.content.images = Array.from(document.querySelectorAll('img'))            .map(img => ({                src: img.getAttribute('src'),                alt: img.getAttribute('alt') || '',                width: img.width,                height: img.height            }));                return this;    }        // 收集页面资源    collectResources() {        // 脚本        this.parsedData.resources.scripts = Array.from(document.querySelectorAll('script[src]'))            .map(script => script.getAttribute('src'));                // 样式表        this.parsedData.resources.stylesheets = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))            .map(link => link.getAttribute('href'));                // 图片        this.parsedData.resources.images = Array.from(document.querySelectorAll('img[src]'))            .map(img => img.getAttribute('src'));                // 外部链接        this.parsedData.resources.links = Array.from(document.querySelectorAll('a[href]'))            .filter(a => {                const href = a.getAttribute('href');                return href && !href.startsWith('#') && !href.startsWith('javascript:');            })            .map(a => a.getAttribute('href'));                return this;    }        // 获取解析结果    getResult() {        return this.parsedData;    }        // 静态方法:完整解析页面    static parseFullPage() {        return new PageParser()            .parseMetadata()            .analyzeStructure()            .extractContent()            .collectResources()            .getResult();    }}// 使用示例document.addEventListener('DOMContentLoaded', () => {    const pageData = PageParser.parseFullPage();    console.log('完整页面分析结果:', pageData);        // 可以将结果发送到服务器或保存    // fetch('/api/save-analysis', {    //     method: 'POST',    //     body: JSON.stringify(pageData)    // });});11. 总结本文详细介绍了如何使用JavaScript获取和解析页面内容,涵盖了从基础到高级的各种技术。我们学习了:获取页面HTML:使用outerHTML、innerHTML等方法获取完整或部分的HTML代码DOM遍历和选择:使用各种选择器方法和遍历技术定位特定元素内容提取:从元素中提取文本、属性和结构化数据高级技术:XPath、TreeWalker、MutationObserver等高级API的使用动态内容处理:监控和等待动态加载的内容性能优化:批量操作、事件委托等提高性能的技术安全考虑:防范XSS攻击和正确处理用户输入跨域限制:理解和解决同源策略带来的限制完整工具实现:构建一个功能全面的页面解析工具通过这些技术,你可以构建强大的网页抓取工具、内容分析系统或浏览器扩展,满足各种实际应用需求。记住在实际应用中要考虑性能、安全和合法性,确保你的代码既高效又负责任。————————————————原文链接:https://blog.csdn.net/sixpp/article/details/146491098
  • [技术干货] 走进Java:String字符串的基本使用
    今天学习到了Java中的String,String是Java中一个非常重要的类,在我们做字符串操作的时候,需要使用到String。一、什么是StringString是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表示。char str [] = {'a', 'b', 'c'};在Java中,String像是一个很大的char数组,我们在定义字符串的时候,不需要再去像上方代码一样去定义,而是直接可以使用String去定义。String str = "abc";String就像是很大的char数组,但相比于char数组而言,String可以做字符串拼接操作,而char数组并不能直接去做字符串的拼接,如下代码。String str = "abc" + "def";二、如何定义一个String既然知道String是一个类,那么类一定有他的初始化方法,在Java中,String的初始化有以下几种常用的方式。这是官方给出的一些介绍。 1. 用双引号定义String字符串,可以直接通过双引号来定义,把需要的字符串内容用双引号包裹,可以直接对String类型的对象赋值。public class StringDemo {    public static void main(String[] args) {        String str = "abc";    }}2. 通过构造函数定义String作为一个类,可以通过new关键字初始化,在Java中可以通过构造函数对String类型的对象赋值。public class StringDemo {    public static void main(String[] args) {        String str = new String("abc");        System.out.println(str);    }}这是一种基本的构造字符串的方式,除此之外还有一些其他的构造方式。通过无参构造函数初始化这样的方式初始化的String的值为空,也就是什么都没有。public String() {        this.value = "".value;        this.coder = "".coder;    }通过char数组进行初始化String可以传入一个char数组进行初始化,String会拼接char数组当中的所有字符。public String(char value[]) {        this(value, 0, value.length, null);    }带位移的方式通过char数组进行初始化这样的方式和上边的方式其实是一样的,只不过在初始化字符串的时候,会根据传入的offset作为char数组开始拼接的起始索引,并且拼接上count个字符。public String(char value[], int offset, int count) {        this(value, offset, count, rangeCheck(value, offset, count));    }除了以上几种常用的方式外,还有一些其他的方式,如通过int数组进行初始化,通过byte数组进行初始化,大家感兴趣的话可以自己研究一下。 三、String中的一些常用方法了解到什么是String,下面就要理解一些String中常用的方法。1 字符串比较字符串之间的比较又分为很多不同的方法,下边是一些常用的字符串比较方法。 1.1 字符串使用 ==字符串使用 == 操作,其实是一个有坑的点,一般不用,这里不在多讲,有兴趣的可以尝试一下以下代码。 1.2 字符串使用equals()字符串的比较,使用这个方法比较多一点。  1.3 使用 equalsIgnoreCase()这个方法相比于普通的equals方法的区别是,这个方法是忽略大小写的。 除此之外,字符串比较还有这两个方法。1.4 cpmpareTo和compareToIgnoreCase这两个方法和equals方法的区别是,equals返回的是boolean类型的变量,而compare方法返回的是int类型的变量。具体比较方法如下:两个字符串按照单个字符从前向后作比较,遇到不同的字符,返回两个字符的差值如果两个字符比较完成了,没有发现不同的字符,返回两个字符串的长度差。2 字符串大小写转换 String中还提供了一些字符串大小写转换的方法。  2.1 toUpperCase()这个方法就是把字符串全部转换为大写。 2.2 toLowerCase()这个方法把字符串全部转换为小写  3  字符串长度获取3.1 length()字符串长度的获取通过以下方法获取。 4  判断是否包含一段字符串4.1 contains()String是有子字符串的概念的,比如我们要查看某个字符串中是否包含一小段的字符串,我们可以用以下方法。 5 字符串切割和拼接 5.1 split()字符串的切割和拼接是非常实用的方法,现在我们有这样的一个字符串。String students = "zhangsan,lisi,wangwu,liuliu";我们想要把这些姓名拆分出来,就需要用到下边的方法。 对于字符串切割,我们需要传入一个作为分割的字符,在上方的代码当中传入的是一个",",当然在具体应用的时候,还要根据具体的场景做分析。5.2 join()除了字符串分割外,当然也有字符串拼接的操作,如果我们想把studentArr中的学生姓名用横杠拼接起来,像这样zhangsan-lisi-wangwu-liuliu需要以下代码完成,这里只介绍基本的使用。 6 字符串寻找字串起始位置我们可以判断字串是否存在,也可以获取子串在字符串的起始索引。 6.1 indexOf()看下方代码,我们试图在字符串中寻找有没有值为"lisi"的字符串,我们调用indexOf方法就可以,indexOf方法有两种,一种是直接传匹配字串,另外一种是传入匹配子串的同时传入开始匹配的起始下边,比如我们从索引10开始寻找,因为lisi的开始索引为9,当我们把开始匹配的下标放到10的时候,就没有办法在匹配到"lisi"了。 6.2 lastIndexOf() 和indexOf()是一样的,不同的是,lastIndexOf()是判断的结尾,是从后往前找的。7 获取字串7.1 substring()字串可以判断存不存在,可以获取起始下标,当然也可以获取子串。通过起始下标和结束下标来截取子串。8 字符串替换 replace:有两个重载形式,replace(char oldChar, char newChar)用于字符替换,replace(CharSequence target, CharSequence replacement) 可用于字符串替换。这里的CharSequence是字符串序列,简单理解就是字符串 ,该方法不会将参数解析为正则表达式。replaceAll:方法签名为replaceAll(String regex, String replacement),参数regex要求是一个正则表达式字符串,它会按照正则表达式规则去匹配字符串中的子串并替换 。如果传入的不是正则表达式,也会当作普通字符串处理。replaceFirst:方法签名为replaceFirst(String regex, String replacement) ,和replaceAll一样,第一个参数regex也是基于正则表达式的,不过它只替换第一次匹配到的子串。当传入非正则表达式的普通字符串时,也能进行字符串替换操作。 对于一些不想要的字符,也是可以替换的,拿上方的这个字符串来说。String students = "zhangsan,lisi,wangwu,liuliu";如果不想要逗号了,想要用横线分割,下方代码可以实现。8.1  replace()8.2 replaceAll()与replace()不同的是,replaceAll()是可以传入正则表达式的,这里不在讲正则表达式,以下的方式也是可以替换的。  以上就是一些常用的String的使用方法。————————————————原文链接:https://blog.csdn.net/2201_76027234/article/details/146368113
  • [技术干货] 【Java从入门到起飞】变量与运算符
    1. 关键字定义:被Java语言赋予了特殊含义,用做专门用途的字符串(或单词)HelloWorld案例中,出现的关键字有 class、public 、 static 、 void 等,这些单词已经被Java定义好了。特点:全部关键字都是小写字母。关键字比较多,不需要死记硬背,学到哪里记到哪里即可。 说明: 关键字一共50个,其中const和goto是保留字(reserved word)。true,false,null不在其中,它们看起来像关键字,其实是字面量,表示特殊的布尔值和空值。2. 标识符Java中变量、方法、类等要素命名时使用的字符序列,称为标识符。 技巧:凡是自己可以起名字的地方都叫标识符。 标识符的命名规则(必须遵守的硬性规定): 由26个英文字母大小写,0-9 ,_或 $ 组成数字不可以开头。不可以使用关键字和保留字,但能包含关键字和保留字。Java中严格区分大小写,长度无限制。标识符不能包含空格。 3. 变量3.1 初识变量变量的概念: 内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化 变量的构成包含三个要素:数据类型、变量名、存储的值 Java中变量声明的格式:数据类型 变量名 = 变量值 变量的作用:用于在内存中保存数据。 使用变量注意: Java中每个变量必须先声明,后使用。使用变量名来访问这块区域的数据。变量的作用域:其定义所在的一对{ }内。变量只有在其作用域内才有效。出了作用域,变量不可以再被调用。同一个作用域内,不能定义重名的变量。3.2 Java中变量的数据类型Java中变量的数据类型分为两大类: 基本数据类型:包括 整数类型、浮点数类型、字符类型、布尔类型。 引用数据类型:包括数组、 类、接口、枚举、注解、记录。 3.3 变量的使用3.3.1 步骤1:变量的声明格式:数据类型  变量名;1//例如://存储一个整数类型的年龄int age;  //存储一个小数类型的体重double weight; //存储一个单字符类型的性别 char gender; //存储一个布尔类型的婚姻状态boolean marry; //存储一个字符串类型的姓名String name; //声明多个同类型的变量int a,b,c; //表示a,b,c三个变量都是int类型。 注意:变量的数据类型可以是基本数据类型,也可以是引用数据类型。 3.3.2 步骤2:变量的赋值给变量赋值,就是把“值”存到该变量代表的内存空间中。同时,给变量赋的值类型必须与变量声明的类型一致或兼容。 变量赋值的语法格式: 变量名 = 值; 举例1:可以使用合适类型的常量值给已经声明的变量赋值 age = 18;weight = 109;gender = '女'; 举例2:可以使用其他变量或者表达式给变量赋值 int m = 1;int n = m;        int x = 1;int y = 2;int z = 2 * x + y; 3:变量可以反复赋值 //先声明,后初始化char gender;gender = '女'; //给变量重新赋值,修改gender变量的值gender = '男';System.out.println("gender = " + gender);//gender = 男 举例4:也可以将变量的声明和赋值一并执行 boolean isBeauty = true;String name = "迪丽热巴"; 内存结构如图:  4. 基本数据类型介绍4.1 整数类型:byte、short、int、longJava各整数类型有固定的表数范围和字段长度,不受具体操作系统的影响,以保证Java程序的可移植性。  定义long类型的变量,赋值时需要以"l"或"L"作为后缀。 Java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long。 Java的整型常量默认为 int 型。 4.1.1 补充:计算机存储单位**字节(Byte):**是计算机用于计量存储容量的基本单位,一个字节等于8 bit。 **位(bit):**是数据存储的最小单位。二进制数系统中,每个0或1就是一个位,叫做bit(比特),其中8 bit 就称为一个字节(Byte)。 转换关系: 8 bit = 1 Byte1024 Byte = 1 KB1024 KB = 1 MB1024 MB = 1 GB1024 GB = 1 TB4.2 浮点类型:float、double与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。  浮点型常量有两种表示形式:十进制数形式。如:5.12 512.0f .512 (必须有小数点)科学计数法形式。如:5.12e2 512E2 100E-2float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。double:双精度,精度是float的两倍。通常采用此类型。定义float类型的变量,赋值时需要以"f"或"F"作为后缀。Java 的浮点型常量默认为double型。4.2.1 关于浮点型精度的说明并不是所有的小数都能可以精确的用二进制浮点数表示。二进制浮点数不能精确的表示0.1、0.01、0.001这样10的负次幂。 浮点类型float、double的数据不适合在不容许舍入误差的金融计算领域。如果需要精确数字计算或保留指定位数的精度,需要使用BigDecimal类。 测试用例: //测试1:(解释见章末企业真题:为什么0.1 + 0.2不等于0.3)System.out.println(0.1 + 0.2);//0.30000000000000004 //测试2:float ff1 = 123123123f;float ff2 = ff1 + 1;System.out.println(ff1);System.out.println(ff2);System.out.println(ff1 == ff2); 4.3 字符类型:charchar 型数据用来表示通常意义上“字符”(占2字节) Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。 字符型变量的三种表现形式: **形式1:**使用单引号(’ ')括起来的单个字符。 例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’; **形式2:**直接使用 Unicode值来表示字符型常量:‘\uXXXX’。其中,XXXX代表一个十六进制整数。 例如:\u0023 表示 ‘#’。 **形式3:**Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。 例如:char c3 = ‘\n’; // '\n’表示换行符 转义字符 说明 Unicode表示方式\n 换行符 \u000a\t 制表符 \u0009\" 双引号 \u0022\' 单引号 \u0027\\ 反斜线 \u005c\b 退格符 \u0008\r 回车符 \u000dchar类型是可以进行运算的。因为它都对应有Unicode码,可以看做是一个数值。 4.4 布尔类型:booleanboolean 类型用来判断逻辑条件,一般用于流程控制语句中: if条件控制语句;while循环控制语句;for循环控制语句;do-while循环控制语句;boolean类型数据只有两个值:true、false,无其它。 不可以使用0或非 0 的整数替代false和true,这点和C语言不同。拓展:Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达所操作的boolean值,在编译之后都使用java虚拟机中的int数据类型来代替:true用1表示,false用0表示。——《java虚拟机规范 8版》举例: boolean isFlag = true; if(isFlag){    //true分支}else{      //false分支} 经验之谈: Less is More!建议不要这样写:if ( isFlag = = true ),只有新手才如此。关键也很容易写错成if(isFlag = true),这样就变成赋值isFlag为true而不是判断!老鸟的写法是if (isFlag)或者if ( !isFlag)。 5. 基本数据类型变量间运算规则在Java程序中,不同的基本数据类型(只有7种,不包含boolean类型)变量的值经常需要进行相互转换。 转换的方式有两种:自动类型提升和强制类型转换。 5.1 自动类型提升规则:将取值范围小(或容量小)的类型自动提升为取值范围大(或容量大)的类型 。 基本数据类型的转换规则如图所示: (1)当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时 int i = 'A';//char自动升级为int,其实就是把字符的编码值赋值给i变量了double d = 10;//int自动升级为doublelong num = 1234567; //右边的整数常量值如果在int范围呢,编译和运行都可以通过,这里涉及到数据类型转换 //byte bigB = 130;//错误,右边的整数常量值超过byte范围long bigNum = 12345678912L;//右边的整数常量值如果超过int范围,必须加L,显式表示long类型。否则编译不通过 (2)当存储范围小的数据类型与存储范围大的数据类型变量一起混合运算时,会按照其中最大的类型运算。 int i = 1;byte b = 1;double d = 1.0; double sum = i + b + d;//混合运算,升级为double (3)当byte,short,char数据类型的变量进行算术运算时,按照int类型处理。 byte b1 = 1;byte b2 = 2;byte b3 = b1 + b2;//编译报错,b1 + b2自动升级为int char c1 = '0';char c2 = 'A';int i = c1 + c2;//至少需要使用int类型来接收System.out.println(c1 + c2);//113  5.2 强制类型转换将3.14 赋值到int 类型变量会发生什么?产生编译失败,肯定无法赋值。 int i = 3.14; // 编译报错 想要赋值成功,只有通过强制类型转换,将double 类型强制转换成int 类型才能赋值。 规则:将取值范围大(或容量大)的类型强制转换成取值范围小(或容量小)的类型。 自动类型提升是Java自动执行的,而强制类型转换是自动类型提升的逆运算,需要我们自己手动执行。 转换格式: 数据类型1 变量名 = (数据类型1)被强转数据值;  //()中的数据类型必须<=变量值的数据类型 (1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)强制转换为存储范围小的变量时,可能会损失精度或溢出。 int i = (int)3.14;//损失精度 double d = 1.2;int num = (int)d;//损失精度 int i = 200;byte b = (byte)i;//溢出 (2)当某个值想要提升数据类型时,也可以使用强制类型转换。这种情况的强制类型转换是没有风险的,通常省略。 int i = 1;int j = 2;double bigger = (double)(i/j); (3)声明long类型变量时,可以出现省略后缀的情况。float则不同。 long l1 = 123L;long l2 = 123;//如何理解呢? 此时可以看做是int类型的123自动类型提升为long类型 //long l3 = 123123123123; //报错,因为123123123123超出了int的范围。long l4 = 123123123123L;  //float f1 = 12.3; //报错,因为12.3看做是double,不能自动转换为float类型float f2 = 12.3F;float f3 = (float)12.3; 6 基本数据类型与String的运算6.1 字符串类型:StringString不是基本数据类型,属于引用数据类型使用一对""来表示一个字符串,内部可以包含0个、1个或多个字符。声明方式与基本数据类型类似。例如:String str = “尚硅谷”;6.2 运算规则1、任意八种基本数据类型的数据与String类型只能进行连接“+”运算,且结果一定也是String类型 System.out.println("" + 1 + 2);//12 int num = 10;boolean b1 = true;String s1 = "abc"; String s2 = s1 + num + b1;System.out.println(s2);//abc10true //String s3 = num + b1 + s1;//编译不通过,因为int类型不能与boolean运算String s4 = num + (b1 + s1);//编译通过 2、String类型不能通过强制类型()转换,转为其他的类型 String str = "123";int num = (int)str;//错误的 int num = Integer.parseInt(str);//正确的,后面才能讲到,借助包装类的方法才能转 7. 运算符运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。 运算符的分类: 按照功能分为:算术运算符、赋值运算符、比较(或关系)运算符、逻辑运算符、位运算符、条件运算符、Lambda运算符分类 运算符算术运算符(7个) +、-、*、/、%、++、–赋值运算符(12个) =、+=、-=、*=、/=、%=、>>=、<<=、>>>=、&=、|=、^=等比较(或关系)运算符(6个) >、>=、<、<=、==、!=逻辑运算符(6个) &、|、^、!、&&、||位运算符(7个) &、|、^、~、<<、>>、>>>条件运算符(1个) (条件表达式)?结果1:结果2Lambda运算符(1个) ->(第18章时讲解)按照操作数个数分为:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目运算符)分类 运算符一元运算符(单目运算符) 正号(+)、负号(-)、++、–、!、~二元运算符(双目运算符) 除了一元和三元运算符剩下的都是二元运算符三元运算符 (三目运算符) (条件表达式)?结果1:结果27.1 算术运算符7.1.1 基本语法  举例1:加减乘除模 public class ArithmeticTest1 {public static void main(String[] args) {int a = 3;int b = 4; System.out.println(a + b);// 7System.out.println(a - b);// -1System.out.println(a * b);// 12System.out.println(a / b);// 计算机结果是0,为什么不是0.75呢?System.out.println(a % b);// 3                //结果与被模数符号相同        System.out.println(5%2);//1System.out.println(5%-2);//1System.out.println(-5%2);//-1System.out.println(-5%-2);//-1//商*除数 + 余数 = 被除数//5%-2  ==>商是-2,余数时1    (-2)*(-2)+1 = 5//-5%2  ==>商是-2,余数是-1   (-2)*2+(-1) = -4-1=-5}} 举例2:“+”号的两种用法 第一种:对于+两边都是数值的话,+就是加法的意思第二种:对于+两边至少有一边是字符串的话,+就是拼接的意思public class ArithmeticTest2 {public static void main(String[] args) {// 字符串类型的变量基本使用// 数据类型 变量名称 = 数据值;String str1 = "Hello";System.out.println(str1); // Hello System.out.println("Hello" + "World"); // HelloWorld String str2 = "Java";// String + int --> StringSystem.out.println(str2 + 520); // Java520// String + int + int// String + int// StringSystem.out.println(str2 + 5 + 20); // Java520}} 举例3:自加自减运算 理解:++ 运算,表示自增1。同理,-- 运算,表示自减1,用法与++ 一致。 1、单独使用 变量在单独运算的时候,变量前++和变量后++,是没有区别的。变量前++ :例如 ++a 。变量后++ :例如 a++ 。public class ArithmeticTest3 {public static void main(String[] args) {// 定义一个int类型的变量aint a = 3;//++a;a++;        // 无论是变量前++还是变量后++,结果都是4System.out.println(a);}} 2、复合使用 和其他变量放在一起使用或者和输出语句放在一起使用,前++和后++就产生了不同。变量前++ :变量先自增1,然后再运算。变量后++ :变量先运算,然后再自增1。public class ArithmeticTest4 {public static void main(String[] args) {// 其他变量放在一起使用int x = 3;//int y = ++x; // y的值是4,x的值是4,int y = x++; // y的值是3,x的值是4 System.out.println(x);System.out.println(y);System.out.println("==========");        // 和输出语句一起int z = 5;//System.out.println(++z);// 输出结果是6,z的值也是6System.out.println(z++);// 输出结果是5,z的值是6System.out.println(z);        } } 7.2 赋值运算符7.2.1 基本语法符号:= 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。支持连续赋值。扩展赋值运算符: +=、 -=、*=、 /=、%= 赋值运算符 符号解释+= 将符号左边的值和右边的值进行相加操作,最后将结果赋值给左边的变量-= 将符号左边的值和右边的值进行相减操作,最后将结果赋值给左边的变量*= 将符号左边的值和右边的值进行相乘操作,最后将结果赋值给左边的变量/= 将符号左边的值和右边的值进行相除操作,最后将结果赋值给左边的变量%= 将符号左边的值和右边的值进行取余操作,最后将结果赋值给左边的变量public class SetValueTest1 {public static void main(String[] args) {int i1 = 10;long l1 = i1; //自动类型转换 byte bb1 = (byte)i1; //强制类型转换 int i2 = i1; //连续赋值的测试//以前的写法int a1 = 10;int b1 = 10; //连续赋值的写法int a2,b2;a2 = b2 = 10; int a3 = 10,b3 = 20; //举例说明+=  -=  *=  /=   %=  int m1 = 10;m1 += 5; //类似于 m1 = m1 + 5的操作,但不等同于。System.out.println(m1);//15 //练习1:开发中,如何实现一个变量+2的操作呢?// += 的操作不会改变变量本身的数据类型。其他拓展的运算符也如此。//写法1:推荐short s1 = 10;s1 += 2; //编译通过,因为在得到int类型的结果后,JVM自动完成一步强制类型转换,将int类型强转成shortSystem.out.println(s1);//12//写法2:short s2 = 10;//s2 = s2 + 2;//编译报错,因为将int类型的结果赋值给short类型的变量s时,可能损失精度s2 = (short)(s2 + 2);System.out.println(s2);  //练习2:开发中,如何实现一个变量+1的操作呢?//写法1:推荐int num1 = 10;num1++;System.out.println(num1); //写法2:int num2 = 10;num2 += 1;System.out.println(num2); //写法3:int num3 = 10;num3 = num3 + 1;System.out.println(num3); }} 7.3 比较(关系)运算符  比较运算符的结果都是boolean型,也就是要么是true,要么是false。 > < >= <= :只适用于基本数据类型(除boolean类型之外) == != :适用于基本数据类型和引用数据类型 比较运算符“==”不能误写成“=” 举例: class CompareTest {public static void main(String[] args) {int i1 = 10;int i2 = 20; System.out.println(i1 == i2);//falseSystem.out.println(i1 != i2);//trueSystem.out.println(i1 >= i2);//false  int m = 10;int n = 20;System.out.println(m == n);//falseSystem.out.println(m = n);//20 boolean b1 = false;boolean b2 = true;System.out.println(b1 == b2);//falseSystem.out.println(b1 = b2);//true}} 7.4 逻辑运算符7.4.1 基本语法  逻辑运算符,操作的都是boolean类型的变量或常量,而且运算得结果也是boolean类型的值。 运算符说明: & 和 &&:表示"且"关系,当符号左右两边布尔值都是true时,结果才能为true。否则,为false。| 和 || :表示"或"关系,当符号两边布尔值有一边为true时,结果为true。当两边都为false时,结果为false! :表示"非"关系,当变量布尔值为true时,结果为false。当变量布尔值为false时,结果为true。^ :当符号左右两边布尔值不同时,结果为true。当两边布尔值相同时,结果为false。理解:异或,追求的是“异”!逻辑运算符用于连接布尔型表达式,在Java中不可以写成 3 < x < 6,应该写成x > 3 & x < 6 。 区分“&”和“&&”: 相同点:如果符号左边是true,则二者都执行符号右边的操作 不同点:& : 如果符号左边是false,则继续执行符号右边的操作 ​ && :如果符号左边是false,则不再继续执行符号右边的操作 建议:开发中,推荐使用 &&区分“|”和“||”: 相同点:如果符号左边是false,则二者都执行符号右边的操作 不同点:| : 如果符号左边是true,则继续执行符号右边的操作 ​ || :如果符号左边是true,则不再继续执行符号右边的操作 建议:开发中,推荐使用 || 代码举例: public class LoginTest {public static void main(String[] args) {int a = 3;int b = 4;int c = 5; // & 与,且;有false则falseSystem.out.println((a > b) & (a > c)); System.out.println((a > b) & (a < c)); System.out.println((a < b) & (a > c)); System.out.println((a < b) & (a < c)); System.out.println("===============");// | 或;有true则trueSystem.out.println((a > b) | (a > c)); System.out.println((a > b) | (a < c)); System.out.println((a < b) | (a > c));System.out.println((a < b) | (a < c));System.out.println("===============");// ^ 异或;相同为false,不同为trueSystem.out.println((a > b) ^ (a > c));System.out.println((a > b) ^ (a < c)); System.out.println((a < b) ^ (a > c)); System.out.println((a < b) ^ (a < c)); System.out.println("===============");// ! 非;非false则true,非true则falseSystem.out.println(!false);System.out.println(!true);                //&和&&的区别        System.out.println((a > b) & (a++ > c));         System.out.println("a = " + a);        System.out.println((a > b) && (a++ > c));         System.out.println("a = " + a);        System.out.println((a == b) && (a++ > c));         System.out.println("a = " + a);                //|和||的区别        System.out.println((a > b) | (a++ > c));         System.out.println("a = " + a);        System.out.println((a > b) || (a++ > c));         System.out.println("a = " + a);        System.out.println((a == b) || (a++ > c));         System.out.println("a = " + a);}} 7.5 位运算符7.5.1 基本语法 位运算符的运算过程都是基于二进制的补码运算(1)左移:<< 运算规则:在一定范围内,数据每向左移动一位,相当于原数据*2。(正数、负数都适用) 【注意】当左移的位数n超过该数据类型的总位数时,相当于左移(n-总位数)位 3<<4  类似于  3*2的4次幂 => 3*16 => 48 -3<<4  类似于  -3*2的4次幂 => -3*16 => -48 (2)右移:>> 运算规则:在一定范围内,数据每向右移动一位,相当于原数据/2。(正数、负数都适用) 【注意】如果不能整除,向下取整。 69>>4  类似于  69/2的4次 = 69/16 =4 -69>>4  类似于  -69/2的4次 = -69/16 = -5 (3)无符号右移:>>> 运算规则:往右移动后,左边空出来的位直接补0。(正数、负数都适用) 69>>>4  类似于  69/2的4次 = 69/16 =4 -69>>>4   结果:268435451 (4)按位与:& 运算规则:对应位都是1才为1,否则为0。 1 & 1 结果为1 1 & 0 结果为0 0 & 1 结果为0 0 & 0 结果为0 9 & 7 = 1 -9 & 7 = 7 (5)按位或:| 运算规则:对应位只要有1即为1,否则为0。 1 | 1 结果为1 1 | 0 结果为1 0 | 1 结果为1 0 & 0 结果为0 9 | 7  //结果: 15 -9 | 7 //结果: -9 (6)按位异或:^ 运算规则:对应位一个为1一个为0,才为1,否则为0。 1 ^ 1 结果为0 1 ^ 0 结果为1 0 ^ 1 结果为1 0 ^ 0 结果为0 9 ^ 7  //结果为14  -9 ^ 7 //结果为-16 (7)按位取反:~ 运算规则:对应位为1,则结果为0;对应位为0,则结果为1。 ~0就是1 ~1就是0 ~9  //结果:-10 ~-9  //结果:8 7.6 条件运算符7.6.1 基本语法条件运算符格式:(条件表达式)? 表达式1:表达式21说明:条件表达式是boolean类型的结果,根据boolean的值选择表达式1或表达式2  如果运算后的结果赋给新的变量,要求表达式1和表达式2为同种或兼容的类型 public static void main(String[] args) {    int i = (1==2 ? 100 : 200);    System.out.println(i);//200        boolean marry = false;System.out.println(marry ? "已婚" : "未婚"  );        double d1 = (m1 > m2)? 1 : 2.0;System.out.println(d1);        int num = 12;    System.out.println(num > 0? true : "num非正数");} 7.6.2 与if-else的转换关系凡是可以使用条件运算符的地方,都可以改写为if-else结构。反之,不成立。 开发中,如果既可以使用条件运算符,又可以使用if-else,推荐使用条件运算符。因为执行效率稍高。 //if-else实现获取两个数的较大值 int i1 = 10;int i2 = 20; int max;//声明变量max,用于记录i1和i2的较大值 if(i1 > i2){    max = i1;}else{    max = i2;} System.out.println(max); 7.7 运算符优先级运算符有不同的优先级,所谓优先级就是在表达式运算中的运算符顺序。 上一行中的运算符总是优先于下一行的。 优先级 运算符说明 Java运算符1 括号 ()、[]、{}2 正负号 +、-3 单元运算符 ++、--、~、!4 乘法、除法、求余 *、/、%5 加法、减法 +、-6 移位运算符 <<、>>、>>>7 关系运算符 <、<=、>=、>、instanceof8 等价运算符 ==、!=9 按位与 &10 按位异或 ^11 按位或 `12 条件与 &&13 条件或 `14 三元运算符 ? :15 赋值运算符 =、+=、-=、*=、/=、%=16 位赋值运算符 &=、`开发建议: 不要过多的依赖运算的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用()来控制表达式的执行顺序。不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。例如:​ (num1 + num2) * 2 > num3 && num2 > num3 ? num3 : num1 + num2;————————————————原文链接:https://blog.csdn.net/fj123789/article/details/145671844
  • [技术干货] Java中使用Hutool进行AES加密解密详解
    在信息安全领域,数据加密是保护数据机密性、完整性和可用性的重要手段之一。AES(Advanced Encryption Standard)作为当前广泛使用的对称加密算法,以其高效、安全的特点,在各类应用系统中扮演着重要角色。Java作为一门广泛应用于企业级开发的编程语言,其强大的加密库为开发者提供了丰富的加密解密工具。然而,直接使用Java的加密API进行AES加密解密时,可能会面临代码繁琐、理解难度高等问题。此时,Hutool这一Java工具类库便显得尤为实用。Hutool是一个小而全的Java工具类库,它简化了Java开发中常见的繁琐操作,包括但不限于日期处理、文件操作、加密解密等。在加密解密方面,Hutool提供了简洁易用的API,使得AES加密解密变得轻松简单。本文将详细介绍如何在Java项目中使用Hutool进行AES加密解密。一、Hutool简介与引入1.1 Hutool简介Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式编程的简洁性。Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当。1.2 引入Hutool在Maven项目中,可以通过添加以下依赖来引入Hutool:<dependency>      <groupId>cn.hutool</groupId>      <artifactId>hutool-all</artifactId>      <version>你的版本号</version>  </dependency>请替换你的版本号为当前最新的Hutool版本号,以确保使用到最新的功能和修复。二、AES加密解密基础AES加密是一种对称加密算法,即加密和解密使用相同的密钥。AES支持三种长度的密钥:128位、192位和256位。在AES加密过程中,数据首先被分成多个固定长度的块(Block),然后每个块独立地进行加密。AES加密过程大致可以分为以下几个步骤:密钥扩展(Key Expansion):将用户提供的密钥扩展成一系列轮密钥(Round Keys)。初始轮(Initial Round):将明文块与初始轮密钥进行特定的操作(如异或、替换等)。中间轮(Intermediate Rounds):对初始轮的结果进行多次迭代加密,每次迭代使用不同的轮密钥。最终轮(Final Round):与中间轮类似,但可能包含一些额外的操作,如列混淆等。输出:最终轮的结果即为加密后的密文。解密过程则是加密过程的逆操作,使用相同的密钥和算法将密文还原为明文。三、使用Hutool进行AES加密解密3.1 加密在Hutool中,进行AES加密主要依赖于SecureUtil类和AES类。以下是一个简单的AES加密示例:import cn.hutool.crypto.SecureUtil;  import cn.hutool.crypto.symmetric.AES;    public class AesEncryptExample {      public static void main(String[] args) {          // 原始数据          String content = "Hello Hutool AES!";          // 密钥,AES要求密钥长度为128/192/256位          byte[] keyBytes = SecureUtil.generateKey(256).getEncoded();                    // 创建AES加密对象,使用ECB/PKCS5Padding          AES aes = SecureUtil.aes(keyBytes, "ECB/PKCS5Padding");                    // 加密          byte[] encrypt = aes.encrypt(content);                    // 加密结果通常用于存储或传输,这里简单打印其Base64编码形式          String encryptHex = aes.encryptHex(content);          System.out.println("加密结果(Hex): " + encryptHex);                    // 如果需要原始字节数组,则使用encrypt方法          // System.out.println(Base64.getEncoder().encodeToString(encrypt));      }  }注意:在实际应用中,密钥keyBytes应安全地生成和存储,避免硬编码在代码中。3.2 解密解密过程与加密过程类似,只是将加密后的数据(密文)作为输入,通过AES解密对象还原为原始数据(明文)。// 假设encryptHex是之前加密得到的Hex字符串  String encryptHex = "..."; // 这里应该是加密后的Hex字符串    // 使用相同的密钥和算法进行解密  AES aes = SecureUtil.aes(keyBytes, "ECB/PKCS5Padding");  String decryptStr = aes.decryptStr(encryptHex);    System.out.println("解密结果: " + decryptStr);四、AES加密模式与填充方式AES加密算法支持多种模式和填充方式,不同的模式和填充方式会影响加密解密的结果。在Hutool中,可以通过指定模式(如ECB、CBC等)和填充方式(如PKCS5Padding、NoPadding等)来创建AES加密解密对象。模式(Mode):决定了加密过程中密钥的使用方式。常见的模式有ECB、CBC、CFB、OFB等。填充方式(Padding):由于AES加密要求输入数据的长度必须是块大小的整数倍(AES块大小为128位),因此当输入数据长度不满足要求时,需要通过填充方式来达到要求。常见的填充方式有PKCS5Padding、PKCS7Padding、NoPadding等。在选择模式和填充方式时,需要根据具体的应用场景和安全需求来决定。例如,ECB模式虽然实现简单,但安全性较低,不适合用于需要高安全性的场景;而CBC模式则通过引入初始化向量(IV)来提高安全性,是较为常用的模式之一。五、安全性与性能考虑在使用AES加密解密时,安全性和性能是两个重要的考虑因素。安全性:确保密钥的安全生成、存储和传输是保障加密安全性的关键。此外,选择合适的加密模式和填充方式也是提高安全性的重要手段。性能:AES加密解密虽然高效,但在处理大量数据时仍可能对性能产生影响。因此,在性能敏感的应用中,需要合理设计加密解密策略,如采用异步处理、批量加密解密等方式来提高性能。六、总结Hutool作为一款实用的Java工具类库,为开发者提供了简洁易用的AES加密解密API。通过本文的介绍,读者可以了解到如何在Java项目中使用Hutool进行AES加密解密,包括加密解密的基本步骤、AES加密模式与填充方式的选择以及安全性和性能的考虑。希望本文能对读者在Java加密解密方面的学习和实践有所帮助。————————————————原文链接:https://blog.csdn.net/My_wife_QBL/article/details/141920322
  • [技术干货] JAVA SE 包装类和泛型
    1. 包装类在JAVA中,共有8种基本类型,分别是byte,short,long,int,double,float,char,boolean.但由于JAVA是一门纯面向对象的语言,而且8种基本并非继承于Object类,为了在泛型代码中可以⽀持基本类型,于是JAVA提供了包装类。 1.1 基本数据类型和对应的包装类 除了 Integer 和 Character, 其余基本类型的包装类都是⾸字⺟⼤写。 1.2 装箱和拆箱装箱:基本数据类型转换成包装类int i = 10;Integer ij = new Integer(i);//装箱 拆箱:包装类转换成基本数据类型int i = 10;Integer ij = new Integer(i);int j = ii.intValue();//拆箱 注意:现在都使用自动拆箱和自动装箱!!! 1.3 自动装箱和自动拆箱为了减少开发者的负担,java 提供了⾃动机制。 自动装箱 int i = 10; Integer j = i;//自动装箱12自动拆箱Integer j = 10;int a = j;//自动拆箱提问:下述代码分别输出什么,为什么? public class Test {    public static void main(String[] args) {        Integer a = 127;        Integer b = 127;        Integer c = 128;        Integer d = 128;        Integer e = -129;        Integer f = -129;        System.out.println(a == b);//true        System.out.println(c == d);//false        System.out.println(e == f);//false    }}注意:a和b是应用类型,==比较的是身份,比较值要重写equals方法进行比较。 答:[-128,127]这个范围数字比较是会出现true,其他数字比较则会出现false。原因是Integer中常用的数字被放到了常量池里,常用数字的范围是[-128,127]. 2. 泛型我们以前学过的数组,只能存放指定类型的元素,但是因为所有类的父类都是Object类,所以数组类型是否可以创建成Object呢? class MyArray {  private Object[] array = new Object[10];    public Object getPos(int pos) {        return this.array[pos];    }    public void setVal(int pos,Object val) {        this.array[pos] = val;    }}public class Test {    public static void main(String[] args) {        MyArray myArray = new MyArray();        myArray.setVal(0,10);        myArray.setVal(1,"hello");//字符串也可以存放        String ret = myArray.getPos(1);//编译报错        System.out.println(ret);    }}//1号下标本⾝就是字符串,但是确编译报错。必须进⾏强制类型转换 虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望 它只能够持有⼀种数据类型。⽽不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。 此时,就需要把类型,作为参数传递。需要什么类型,就传⼊什么类型。 2.1 泛型的语法基础写法: class 泛型类名称<类型形参列表> {// 这⾥可以使⽤类型参数} 其他写法: class 泛型类名称<类型形参列表> extends 继承类/* 这⾥可以使⽤类型参数 */ {// 这⾥可以使⽤类型参数} 上述代码进⾏改写如下: class MyArray<T> {    public Object[] array = new Object[10];    public T getPos(int pos) {        return (T)this.array[pos];    }    public void setVal(int pos,T val) {        this.array[pos] = val;    }}public class TestDemo {    public static void main(String[] args) {        MyArray<Integer> myArray = new MyArray<>();//1        myArray.setVal(0,10);        myArray.setVal(1,12);        int ret = myArray.getPos(1);//2        System.out.println(ret);        myArray.setVal(2,"小朱小朱");//3 编译报错!!!    }} 1. 注释1处,类型后加⼊ 指定当前类型2. 注释2处,不需要进行强制类型转换3. 注释3处,代码编译报错,此时因为在注释1处指定类当前的类型,此时编译器会在存放元素的时候帮助我们进行类型检查。 代码解释: 类名后的 < T >代表占位符,表⽰当前类是⼀个泛型类【规范】类型形参⼀般使⽤⼀个⼤写字⺟表示,常⽤的名称有:• E 表示Element• K 表示 Key• V 表示 Value• N 表示 Number• T 表示 Type• S, U, V 等等 - 第⼆、第三、第四个类型2.2 泛型类的使用泛型类<类型实参> 变量名; // 定义⼀个泛型类引⽤new 泛型类<类型实参>(构造⽅法实参); // 实例化⼀个泛型类对象 MyArray<Integer> list = new MyArray<Integer>(); 注意:泛型只能接受类,所有的基本数据类型必须使用包装类! 2.3 裸类型(Raw Type)裸类型是⼀个泛型类但没有带着类型实参,例如 MyArrayList 就是⼀个裸类型 MyArray list = new MyArray();2.4 擦除机制在编译时,Java 编译器会将泛型类型信息从代码中移除,这个过程就叫做类型擦除。擦除后,泛型类型会被替换为其边界类型(通常是 Object)或者指定的类型。同时也会在必要的地方插⼊类型转换以保持类型安全。 擦除前: class MyArray<T> {    public Object[] array = new Object[10];    public T getPos(int pos) {        return (T)this.array[pos];    }    public void setVal(int pos,T val) {        this.array[pos] = val;    }} 擦除后: class MyArray {    public Object[] array = new Object[10];    public Object getPos(int pos) {        return this.array[pos];    }    public void setVal(int pos, Object val) {        this.array[pos] = val;    }}2.5 泛型的上界在定义泛型类时,有时需要对传⼊的类型变量做⼀定的约束,可以通过类型边界来约束。 语法: class 泛型类名称<类型形参 extends 类型边界> {...} public class MyArray<E extends Number> {...}//只接受 Number 的⼦类型作为 E 的类型实参2.6 泛型方法语法: ⽅法限定符 <类型形参列表> 返回值类型 ⽅法名称(形参列表){...} 示例: public class Util {//静态的泛型⽅法 需要在static后⽤<>声明泛型类型参数public static <E> void swap(E[] array, int i, int j) {E t = array[i];array[i] = array[j];array[j] = t;}}2.7 通配符?用于在泛型的使⽤,即为通配符 请观察下述代码: class Message<T> {    private T message ;    public T getMessage() {        return message;    }    public void setMessage(T message) {        this.message = message;    }}public class Test {    public static void main(String[] args) {        Message<String> message = new Message<>() ;        message.setMessage("欢迎来到小朱的CSDN");        fun(message);    }    public static void fun(Message<String> temp){        System.out.println(temp.getMessage());    }} 以上程序会带来新的问题,如果现在泛型的类型设置的不是String,⽽是Integer. public class TestDemo {    public static void main(String[] args) {        Message<Integer> message = new Message() ;        message.setMessage(99);        fun(message); // 出现错误,只能接收String    }    public static void fun(Message<String> temp){        System.out.println(temp.getMessage());    }} 我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使⽤通配符"?"来处理 public class TestDemo {    public static void main(String[] args) {        Message<Integer> message = new Message() ;        message.setMessage(55);        fun(message);    }    // 此时使⽤通配符"?"描述的是它可以接收任意类型    public static void fun(Message<?> temp){        System.out.println(temp.getMessage());    }} 在"?"的基础上又产生了两个子通配符: 通配符上界: <? extends 上界><? extends Number>//可以传⼊的实参类型是Number或者Number的⼦类 通配符下界————————————————原文链接:https://blog.csdn.net/2401_82690001/article/details/145964769
总条数:764 到第
上滑加载中