-
简介:在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
-
一、过滤器乱码解决在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:全球定位系统(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)是 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开发至关重要。
-
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 或类似的库来计算更复杂的哈希。
-
复赛名次在哪公布?复赛名次在哪公布
-
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);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,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
-
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
-
在信息安全领域,数据加密是保护数据机密性、完整性和可用性的重要手段之一。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
-
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
-
一、IDEA 的下载IDEA(IntelliJ IDEA)是一款非常强大且深受开发者喜爱的 Java 集成开发环境(IDE),它提供了丰富的功能和便捷的开发体验,下面为大家详细介绍其下载步骤:访问官方网站:打开浏览器,输入 IntelliJ IDEA 的官方网址:https://www.jetbrains.com/idea/ 在官网首页,你可以看到关于 IDEA 的各种介绍和版本信息。选择版本:IDEA 分为社区版(Community Edition)和旗舰版(Ultimate Edition)。社区版是免费的,适合初学者和个人开发者,它提供了基本的 Java 开发功能;旗舰版则是付费版本,功能更为强大,支持更多的框架和技术,如 Web 开发、数据库管理等。根据自己的需求和预算,选择合适的版本。一般来说,对于学习 Java-servlet 开发,社区版就已经足够了。下载安装包:点击对应版本的“Download”按钮,根据你的操作系统(Windows、Mac 或 Linux)选择相应的安装包进行下载。等待下载完成:下载完成后,找到下载的安装包文件(通常是一个.exe 文件对于 Windows 系统,.dmg 文件对于 Mac 系统),双击运行安装程序,按照安装向导的提示逐步完成安装。二、Maven 的下载Maven 是一个项目管理和构建工具,它可以帮助我们管理项目的依赖关系、构建项目、生成文档等。使用 Maven 可以大大简化项目的管理和开发过程。以下是 Maven 的下载步骤:访问 Maven 官方网站:在浏览器中输入 Maven 的官方网址:https://maven.apache.org/ 。进入官网后,你可以了解到 Maven 的相关信息和文档。下载 Maven 安装包:在官网的首页,找到“Download”链接,点击进入下载页面。在下载页面中,你会看到不同版本的 Maven 安装包。建议选择最新的稳定版本进行下载。根据你的操作系统,选择对应的二进制压缩包(如.zip 文件对于 Windows 系统,.tar.gz 文件对于 Linux 和 Mac 系统)。 点击下载对应的版本我的gitee直接下载链接https://gitee.com/srte-7719/tool/tree/master/Maven三、Tomcat 的下载Tomcat 是 Apache 软件基金会开发的一个开源的 Web 应用服务器,它是运行 Java Web 应用程序的重要平台。下面是 Tomcat 的下载步骤:访问 Tomcat 官方网站:在浏览器中输入 Tomcat 的官方网址:https://tomcat.apache.org/ 。进入官网后,你可以看到关于 Tomcat 的介绍和下载链接。选择版本:在官网的首页,找到“Tomcat 版本”部分,选择你想要下载的 Tomcat 版本。目前较常用的版本有 Tomcat 8 和 Tomcat 9,建议选择最新的稳定版本。下载安装包:点击对应版本的“Download”按钮,进入下载页面。在下载页面中,你会看到不同类型的安装包。对于 Windows 系统,推荐下载“Windows Service Installer”(.msi 文件),它可以方便地将 Tomcat 安装为 Windows 服务;对于 Linux 和 Mac 系统,推荐下载“Core”部分的“tar.gz”格式的压缩包。安装(对于 Windows 系统的.msi 文件):下载完成后,双击运行.msi 文件,按照安装向导的提示进行安装。在安装过程中,你可以选择安装路径、配置端口号(默认是 8080)等参数。解压(对于 Linux 和 Mac 系统的.tar.gz 文件):将下载的.tar.gz 文件解压到你希望安装 Tomcat 的目录。例如,在 Linux 系统中,可以解压到“/usr/local/tomcat”目录。————————————————原文链接:https://blog.csdn.net/2402_83322742/article/details/145930212
-
1.synchronized修饰方法 synchronized public void increase(){ // synchronized修饰实例方法 count++; } public void increase1(){ synchronized (this){ // 用this作为锁对象 count++; } } synchronized public static void increase2(){ count++; } public void increase3(){ synchronized (Demo1.class){ // 通过反射拿到这个类对象 count++; } }一个.java文件编译 => .class(字节码文件) => 运行时.class文件被加载到JVM里面(就是说:JVM加载到内存中的数据结构就是类对象)解释:当JVM加载一个.class文件时,它会在内存中创建一个对应的数据结构,这个数据结构通常被称为“类对象”或“类结构”。这个类对象包含了类中定义的所有信息类对象包括:类的属性,名字,类型,权限,类方法,继承哪个类,实现了哪个接口…………2:synchronized是一个可重入锁那可重入锁是什么意思呢?一个线程针对一个对象连续加锁两次不会出现死锁(我们等会会细说死锁)。满足这个要求的就是可重入锁。我们拿一个代码进行举例: synchronized (locker){ synchronized (locker){ count++; } }按理说这个代码就卡住了:解释: 我先给第一次给locker加锁,按理说下面第二次这个加锁操作肯定阻塞等待第一次加锁释放锁,我这个synchrinized才能给locker加锁。但是第二次不加锁第一次的加的锁就没办法解锁。这完全就死了(这种情况就死锁了)但是!!!synchronized是可重入锁:就是可以连续给一个对象进行两次加锁对象头里会有一个计数器(这个线程给这个对象加锁一个,计数器就+1)解释对象头和计数器,线程给一个对象肯定会保存这个线程的信息对象在加锁时会保存这个线程的信息,这些信息保存在对象头(隐藏里的隐藏信息)的Mark Word中。每个对象都有一个对象头(Object Header),它包含了对象的一些元数据(譬如锁状态)。并且一个线程连续给一个对象加锁还会有一个计数器,这个计数器是也存在于对象头里面的Mark Word中。这一机制确保了同一个线程可以多次获得同一个对象的锁。解锁时:并且一个对象被加三把锁时加入计数器的值 = 3 ,被解锁的时候也不是真的解锁而是解一把锁计数器-1,直到减到0才是真正的解锁3:死锁3.1.死锁的引入刚才我们讲述了synchronized是一个可重入锁,我们给一个对象连续加锁并不会导致死锁(线程卡死了)。那什么情况下会出现死锁呢?接下来给大家看一个代码 public static void main(String[] args) { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(() ->{ synchronized (locker1){ try { Thread.sleep(1000); //这个休眠1s真的很重要,开启t1线程立马就拿到了 locker1这把锁,如果不休眠的话 //很容易一下子这个线程就把两个锁都拿到了 } catch (InterruptedException e) { throw new RuntimeException(e); } // 嵌套的锁 synchronized (locker2){ System.out.println("t1加锁成功"); } } }); Thread t2 = new Thread(() -> { synchronized(locker2){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } // 嵌套的锁 synchronized(locker1){ System.out.println("t2 加锁成功"); } } }); t1.start(); t2.start(); }结果:什么都没有打印我们进入jconsole看一下两个线程的状态画个图解释一下所以说:所以两个线程都没有成功获得第二把锁 这个属于嵌套关系,线程A拿到locker1这把锁,又想要locker2这把锁(就可能出现死锁)但是如果是并列关系(线程A先释放前面的锁,再获取下一把锁)(就不会死锁)嵌套如何变成并列?改变代码结构! 这样就解决了,但是有时候代码结构不是那么好改变的。刚才说到死锁形成环。。。就会出现一个经典的问题——哲学家就餐问题3.2.哲学家就餐问题先搞一张图表示这个问题 这就是导致了死锁问题(怎么解决呢)说到怎么解决,就要知道形成死锁的几个必要条件,我们破坏其中一个就OK了有这四个条件(其中前两个条件是synchronized的属性(我们改变不了))1.互斥使用(锁的基本特性):一个线程拥有了锁A,另一个线程想要获取就只能阻塞等待2.不可抢占(锁的基本特性):和条件一差不多,另一个线程只能等那个线程释放锁A,不能抢占过来3.保持请求(代码结构):一个线程想要获得多把锁(嵌套,想要锁B但是又不想释放自己的锁B)(其实并形成环,你想要几个锁都没问题)4.循环等待(代码结构):(条件三导致的条件四),等待的关系形成环了条件三:其实有时候的需求就是需要进行获取多把锁,这个结构不好改变条件四:我们约定好了加锁的顺序,就可以避免循环等待(针对锁进行编号,先加小锁再加大锁)所以说哲学家就餐问题就可以这么解决!!!我们可以规定每个哲学家只能先拿起数字小的筷子,哲学家B先拿起筷子,然后最后哲学家A面前只剩下了一个筷子5,但是他现在不能拿了(这个数字比较大)所以哲学家E就能够吃面条了,然后就通了。 上述就是synchronized—死锁问题的全部内容了,死锁的出现,会让我们的程序陷入一个死循环的问题,但是我们只要知道死锁的成因,至少就知道了如何解决这个问题啦~~~预知后事如何,请听下回分解~~~能看到这里相信您一定对小编的文章有了一定的认可。有什么问题欢迎各位大佬指出欢迎各位大佬评论区留言修正~~————————————————原文链接:https://blog.csdn.net/2302_80639556/article/details/145079283
-
概述在 Spring 框架中,BeanPostProcessor 和 BeanFactoryPostProcessor 是两个重要的接口,用于在 Spring 容器管理 Bean 的生命周期过程中提供自定义的扩展点。它们允许开发者在 Spring 容器实例化和初始化 bean 的过程中进行额外的处理。 两者的定义和作用BeanPostProcessor:Spring 的 BeanPostProcessor 名称后置处理器,这是一个拓展机制,用于在 Spring 容器实例化、配置完成之后,在初始化前和初始化后,对 Bean 进行再加工的自定义处理。BeanFactoryPostProcessor:Spring 的 BeanFactoryPostProcessor 接口定义了一个方法,允许在 Spring 容器标准初始化之后、实例化任何 Bean 之前,对容器的内部 BeanFactory 进行修改。两者的区别与联系先说区别执行时机不同:BeanPostProcessor 在每个 Bean 实例化和初始化前后的过程中执行;BeanFactoryPostProcessor 在 Spring 容器的启动过程中执行,其执行时机是在容器标准初始化之后,所有的 BeanDefinition 已经加载到容器中,但还没有实例化任何 Bean 之前。作用对象不同:BeanPostProcessor 作用于每一个 Bean 实例,允许对 Bean 实例进行修改,例如修改属性、生成代理对象等。BeanFactoryPostProcessor 作用于 BeanFactory 本身,允许修改 Bean 定义,影响容器中的 Bean 配置,例如修改属性值、动态注册 Bean 定义等。再说联系Spring 容器的扩展机制:这两个接口都属于 Spring 容器的扩展机制,用于增强容器的功能和灵活性,都提供了在 Spring 容器初始化过程中进行自定义处理的扩展点,允许开发者在框架执行核心逻辑之前或之后插入自己的逻辑。需要在 Spring 配置中进行注册:无论是通过 XML 配置文件还是通过注解方式,这两个接口的实现类都需要在 Spring 配置中进行注册,以便 Spring 容器能够识别和调用它们。使用场景BeanPostProcessor动态代理:在 Bean 初始化后创建代理对象。属性检查或修改:在 Bean 初始化前检查或修改属性。BeanFactoryPostProcessor属性占位符配置:修改 Bean 定义中的占位符值。条件性 Bean 注册:根据条件动态注册或修改 Bean 定义。BeanPostProcessor 详解接口定义Spring 容器会为每个 Bean 创建实例后,分别调用 BeanPostProcessor 接口的两个方法: public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }} 方法详解postProcessBeforeInitialization在 Bean 初始化之前调用,开发者可以通过该方法对 Bean 进行自定义处理,包括修改 Bean 的属性值、添加一些特殊处理等。此方法的第一个参数即本次创建的 Bean 对象,第二个参数即本次创建 Bean 的名字。需要注意处理完成后需要将 Bean 作为返回值返回,归还给 Spring 管理。 postProcessAfterInitialization在 Bean 初始化之后调用,开发者可以通过该方法对 Bean 进行自定义处理,比如动态代理、AOP 增强等。此方法的第一个参数即本次创建的 Bean 对象,第二个参数即本次创建 Bean 的名字。需要注意处理完成后需要将 Bean 作为返回值返回,归还给 Spring 管理。 注意:在实战中很少对 Spring 中的对象进行初始化的操作,所以区分 Before 和 After 的意义不是很大,我们只需要实现一种即可,推荐实现 After 方法,另外一个 Before 方法就按默认即可。 注意:BeanPostProcessor 后置处理器会对 Spring 创建的所有对象进行处理。 示例代码在某些情况下,我们可能需要在应用程序启动时扫描特定的注解,并在 Bean 初始化时进行处理。例如,我们希望在 Spring Boot应用中扫描所有带有特定注解的类,并在它们被初始化时打印日志和执行方法。 @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface XUE {} @XUE@Componentpublic class XueWei { public void hello() { System.out.println("Hello XW!"); }} @Componentpublic class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> targetClass = bean.getClass(); if (targetClass.isAnnotationPresent(XUE.class)) { // 获取到了自定义注解标记的类 System.out.println("扫描到了标记 @XUE 注解的类:" + targetClass.getName()); Method[] methods = bean.getClass().getDeclaredMethods(); for (Method method : methods) { // 执行指定方法 if (!"hello".equals(method.getName())) { continue; } try { // 通过反射执行该类的所有的方法 method.invoke(bean); } catch (Exception e) { System.out.println("执行报错:" + e); } } } return bean; }} 基于 XML 的方式注解: <!-- 注册后置处理器 --><bean id="processor" class="world.xuewei.processor.MyProcessor"/> 优点与缺点先说优点灵活性:BeanPostProcessor 提供了一种灵活的机制,允许开发人员在 Spring 容器实例化、配置和初始化 Bean 的过程中介入,可以对 Bean 进行自定义的操作和处理。可扩展性:开发人员可以根据实际需求编写自定义的 BeanPostProcessor 实现,实现各种功能,如自动装配、依赖注入、AOP、事务管理等。AOP 实现基础:Spring AOP 的实现正是基于 BeanPostProcessor 机制。通过 BeanPostProcessor,Spring 可以在 Bean 初始化的过程中动态地生成代理对象,实现 AOP 的横切逻辑。再说缺点性能影响:使用 BeanPostProcessor 可能会对应用程序的性能产生一定的影响,特别是在大规模应用中,由于需要对所有的 Bean 进行处理,可能会增加应用程序的启动时间和内存消耗。复杂性:如果不正确地使用 BeanPostProcessor,可能会导致应用程序的复杂性增加,增加代码的维护成本和理解难度。潜在的问题:由于 BeanPostProcessor 可以对所有的 Bean 进行操作,因此可能会引入一些潜在的问题,如循环依赖、死锁等。注意事项避免循环依赖:使用 BeanPostProcessor 时,要注意避免循环依赖的问题,避免出现 Bean 依赖循环导致的应用程序启动失败。谨慎处理 Bean 初始化逻辑:在编写 BeanPostProcessor 实现时,要谨慎处理 Bean 初始化逻辑,确保不会影响到 Bean 的正常初始化过程。注意性能影响:如果应用程序启动时间较长或者内存消耗较高,可以考虑减少 BeanPostProcessor 的数量,或者优化 BeanPostProcessor 的实现,以减少性能影响。优先级设置:可以通过实现 Ordered 接口或者在 @Order 注解中设置优先级来控制多个 BeanPostProcessor 的执行顺序,确保处理顺序的正确性。测试与调试:在使用 BeanPostProcessor 时,建议进行充分的测试和调试,确保 BeanPostProcessor 的实现逻辑正确,不会引入潜在的问题。BeanFactoryPostProcessor 详解接口定义通过实现 BeanFactoryPostProcessor,可以在 Bean 实例化之前对 Bean 的定义进行修改,适用于对 Bean 配置的元数据进行全局修改,例如修改属性值,替换属性占位符等。 public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;} 方法详解postProcessBeanFactory在容器实例化任何 Bean 之前执行,可以修改应用程序上下文的内部 Bean 定义,调整 Bean 的配置元数据。 适用场景@Componentpublic class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 场景一:修改已存在的 BeanDefinition try { // 获取指定 Bean 的定义 BeanDefinition beanDefinition = beanFactory.getBeanDefinition("student"); // 修改 BeanDefinition 属性信息 MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); propertyValues.addPropertyValue("name", "薛伟"); // 修改作用域 beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); // 快速初始化 Bean Object student = beanFactory.getBean("student"); System.out.println(student); // ... } catch (Exception e) { System.err.println("修改 Student 的 BeanDefinition 属性失败!" + e); } // 场景二:动态注册 BeanDefinition(Spring 与 MyBatis 整合就是使用的这种) DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory; GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(Teacher.class); definition.getPropertyValues().addPropertyValue("name", "XUEW"); factory.registerBeanDefinition("teacher", definition); // 场景三:加载外部配置文件 try { Properties props = PropertiesLoaderUtils.loadAllProperties("db.properties"); for (String key : props.stringPropertyNames()) { System.setProperty(key, props.getProperty(key)); } } catch (IOException e) { throw new RuntimeException("Failed to load properties file", e); } // 场景四:条件性修改 Bean 定义 String os = System.getProperty("os.name"); if ("Mac OS X".equals(os)) { BeanDefinition osInfo = beanFactory.getBeanDefinition("osInfo"); MutablePropertyValues infoPropertyValues = osInfo.getPropertyValues(); infoPropertyValues.addPropertyValue("name", os); infoPropertyValues.addPropertyValue("desc", "苹果系统"); } // 场景五:快速初始化 Bean System.out.println(beanFactory.getBean("student")); System.out.println(beanFactory.getBean("teacher")); System.out.println(beanFactory.getBean("osInfo")); // 设置当前环境 System.setProperty("environment.active", "prod"); }} 示例场景假设我们有一个应用程序需要连接不同环境下的 Redis 实例,根据运行环境(如开发、测试、生产)动态修改 Redis 的连接信息。 创建一个名为 redis.properties 的配置文件,包含不同环境下的 Redis 连接信息。test.redis.host=test.xuewei.worldtest.redis.port=6379test.redis.password=123456prod.redis.host=prod-redis-serverprod.redis.port=16379prod.redis.password=P@ssW0RD 创建一个 Bean 类 RedisConfig,包含 Redis 连接属性。@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic class RedisConfig { private String host; private int port; private String password; } 实现一个自定义的 BeanFactoryPostProcessor,根据运行时的环境修改 Bean 定义。@Componentpublic class RedisEnvironmentBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware { private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String env = System.getProperty("environment.active"); BeanDefinition beanDefinition = beanFactory.getBeanDefinition("redisConfig"); MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); propertyValues.add("host", environment.getProperty(env + ".redis.host")); propertyValues.add("port", environment.getProperty(env + ".redis.port", Integer.class)); propertyValues.add("password", environment.getProperty(env + ".redis.password")); System.out.println(beanFactory.getBean("redisConfig")); }} 使用 @Configuration 和 @PropertySource 注解配置 Spring 上下文,并启用属性占位符解析。@Configuration@ComponentScan(basePackages = {"world.xuewei.dao", "world.xuewei.service", "world.xuewei.processor"})@EnableAspectJAutoProxy(proxyTargetClass = true)@PropertySource({"classpath:/student.properties", "classpath:/redis.properties"})public class AppConfig { @Bean public RedisConfig redisConfig() { return new RedisConfig(); }} 优点与缺点先说优点修改 Bean 定义:BeanFactoryPostProcessor 可以在 Bean 实例化之前修改 Bean 的定义属性,这提供了一种灵活的方式来调整 Bean 的配置。动态配置:它可以用于根据运行环境动态配置 Bean 属性,如使用外部配置文件的属性占位符进行配置。这有助于实现应用程序的配置灵活性和可维护性。增强功能:可以用来增加自定义逻辑,例如自动注册额外的 Bean 定义,动态修改现有的 Bean 配置,或者实现其他自定义处理逻辑。属性占位符解析:通过实现特定的 BeanFactoryPostProcessor(如 PropertyPlaceholderConfigurer),可以将外部配置文件中的属性值注入到 Spring Bean 中,这使得应用程序的配置管理更加灵活和集中化。再说缺点复杂性增加:使用 BeanFactoryPostProcessor 可能增加应用程序配置的复杂性,尤其是在处理大量的自定义逻辑时,可能会使配置难以理解和维护。调试困难:由于 BeanFactoryPostProcessor 在 Spring 容器初始化的早期阶段执行,调试可能会变得复杂。问题可能在 Bean 实例化之前发生,因此追踪和解决这些问题可能比较困难。依赖顺序问题:使用多个 BeanFactoryPostProcessor 时,需要注意它们的执行顺序,否则可能会导致不可预期的行为。正确管理多个 BeanFactoryPostProcessor 的执行顺序可能会增加开发复杂性。性能影响:由于 BeanFactoryPostProcessor 在 Spring 容器启动时执行,包含复杂逻辑的处理器可能会增加应用程序启动时间。如果处理逻辑比较重,会影响应用程序的启动性能。注意事项确保执行顺序:多个 BeanFactoryPostProcessor 实例可能会存在相互依赖关系。因此,了解并控制它们的执行顺序非常重要。可以实现 Ordered 接口或 PriorityOrdered 接口来指定执行顺序。官方文档中明确指出 BeanFactoryPostProcessor 不能使用 @Order 注解。 避免复杂逻辑:由于 BeanFactoryPostProcessor 在 Spring 容器启动期间执行,避免在其实现中引入复杂或耗时的逻辑。复杂逻辑可能会延长应用程序启动时间,影响性能。 确保线程安全:BeanFactoryPostProcessor 可能会在多线程环境中被调用,因此确保实现的线程安全非常重要。避免使用非线程安全的数据结构或方法。 处理依赖关系:避免在 BeanFactoryPostProcessor 中直接引用其他 Bean,因为这些 Bean 可能还没有被实例化。如果必须引用其他 Bean,可以通过 BeanFactory 进行延迟获取。 @Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { MyBean myBean = beanFactory.getBean(MyBean.class); // Use myBean} 避免循环依赖:由于 BeanFactoryPostProcessor 可能会修改 Bean 定义,从而影响其他 BeanFactoryPostProcessor 的行为,确保这些修改不会引起循环依赖。 使用正确的应用场景:BeanFactoryPostProcessor 适用于需要在 Bean 实例化之前修改 Bean 定义的场景。对于在 Bean 实例化之后需要执行的逻辑,考虑使用 BeanPostProcessor。 小心静态资源的使用:避免在 BeanFactoryPostProcessor 中使用静态资源或全局变量,除非这些资源是线程安全的并且适合在 Spring 应用程序启动期间使用。 记录日志和异常处理:在 postProcessBeanFactory 方法中添加适当的日志记录和异常处理,以便在启动时发生问题时能快速定位和解决问题。 @Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { // Your processing logic } catch (Exception e) { // Log and handle the exception throw new BeansException("Error processing bean factory", e); }} 配置管理:如果 BeanFactoryPostProcessor 依赖外部配置,确保配置文件加载顺序正确,并且配置文件中的值能够正确解析。 避免对 Bean 的直接操作:尽量避免在 BeanFactoryPostProcessor 中直接操作 Bean 实例。更多的操作应当在 Bean 定义层面进行,而非实例层面。———————————————— 原文链接:https://blog.csdn.net/weixin_53287520/article/details/139484810
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签