• [技术干货] java 基础问题 第二篇
    1.深拷贝和浅拷贝1.1.区别例子:你需要将一个对象拷贝到一个新的对象里(类型相同),你选择浅拷贝,那么它就是将对原先的对象原封不动的拷贝过去(引用类型共享内存地址),而深拷贝就是基本类型复制,引用类型是先创建一个新的对象再将值复制过来区别:引用类型拷贝区别:浅拷贝将地址拷过来(地址复用),深拷贝将值拷过来(创建一个新的对象,地址不复用)浅拷贝定义基本类型字段:直接复制值。引用类型字段:仅复制内存地址(新旧对象共享同一引用对象)。特点:修改原对象或拷贝对象中的引用字段时,另一方会同步变化。深拷贝定义基本类型字段:直接复制值。引用类型字段:递归创建新对象并复制所有层级数据(新旧对象引用独立对象)。特点:修改原对象或拷贝对象中的引用字段时,另一方不受影响。1.2.实现深拷贝的方式实现 Cloneable 接口并重写 clone() 方法class Person implements Cloneable {    String name;    Address address;     @Override    public Person clone() {        try {            Person cloned = (Person) super.clone();            // 深拷贝:递归克隆引用字段            cloned.address = this.address.clone();            return cloned;        } catch (CloneNotSupportedException e) {            throw new AssertionError(); // 不会发生        }    }} class Address implements Cloneable {    String city;     @Override    public Address clone() {        try {            return (Address) super.clone();        } catch (CloneNotSupportedException e) {            throw new AssertionError();        }    }}class Person implements Cloneable {    String name;    Address address;     @Override    public Person clone() {        try {            Person cloned = (Person) super.clone();            // 深拷贝:递归克隆引用字段            cloned.address = this.address.clone();            return cloned;        } catch (CloneNotSupportedException e) {            throw new AssertionError(); // 不会发生        }    }} class Address implements Cloneable {    String city;     @Override    public Address clone() {        try {            return (Address) super.clone();        } catch (CloneNotSupportedException e) {            throw new AssertionError();        }    }}使用序列化和反序列化import java.io.*; class Person implements Serializable {    String name;    Address address; // Address 也需实现 Serializable} public class DeepCopyUtils {    public static <T extends Serializable> T deepCopy(T obj) {        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();             ObjectOutputStream oos = new ObjectOutputStream(bos)) {            oos.writeObject(obj);            try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());                 ObjectInputStream ois = new ObjectInputStream(bis)) {                return (T) ois.readObject();            }        } catch (IOException | ClassNotFoundException e) {            throw new RuntimeException("Deep copy failed", e);        }    }} // 使用示例Person original = new Person();Person deepCopy = DeepCopyUtils.deepCopy(original);手动递归复制class Person {    String name;    Address address;     public Person deepCopy() {        Person copy = new Person();        copy.name = this.name;        copy.address = this.address.deepCopy(); // 手动递归复制        return copy;    }} class Address {    String city;     public Address deepCopy() {        Address copy = new Address();        copy.city = this.city;        return copy;    }} // 使用示例Person original = new Person();Person deepCopy = original.deepCopy();对比总结方法    性能    代码复杂度    适用场景    限制条件Cloneable 接口    高    中等    简单对象、可控的深拷贝    需递归处理引用字段序列化/反序列化    低    低    复杂对象图、完全深拷贝    必须实现 Serializable手动递归复制    高    高    精细控制拷贝逻辑、排除特定字段    代码维护成本高2.泛型2.1.定义泛型是java中的一个特性,它允许在定义类和方法和接口时只需要指定一个或者多个类型参数(泛型符号)就行,在使用时再指定具体的类型2.2.作用当多个类需要共享相同代码逻辑时,可以通过定义泛型来实现。泛型允许在使用时指定具体类型,使不同类都能复用相同的代码逻辑。--------由于泛型在定义时使用类型参数而非具体类型,它可以接收任何类型参数而无需强制类型转换,从而避免了类型安全问题 3.对象3.1.创建对象的方式通过new关键字创建对象通过克隆创建对象通过反射创建对象通过反序列化创建对象3.2.对象回收对象是由垃圾回收器回收,垃圾回收器会在程序运行时自动运行,周期性去检查对象是否被引用,没有就直接回收,释放内存垃圾回收器实现的主要的机制:----1.引用计数法:它会根据该对象的引用计数,如果为0,代表没有被引用直接回收释放---2.可达性分析算法:它会从根对象出发,根据根对象的属性和方法的引用链来判断,如果这个对象没有一个引用链那么代表没有被引用,直接回收释放内存---3.终结器:在对象里面你可以重写finalize()方法,如果重写了该方法,那么垃圾回收器在回收该对象之前会先执行该方法,但是会出现一个问题,你清楚你的这个对象什么时候被回收吗?不确定时间,那么该方法执行的时间也不确定,因此会出现一些可能会出现的问题( 可能导致性能问题、死锁和资源争用),就比如性能问题,如果你在finalize()方法中释放了某些资源,但是由于不确定时间释放,就会导致一些性能问题(可能都不会释放)3.3. 获取私有成员由于定义的私有成员,那么只能该类内部访问,如果外部需要访问你对外提供方法来访问(比getter方法)你通过反射直接获取该成员4.反射4.1.定义反射就是可以获取任何一个类里面的全部信息(父类,实现的接口都可以),并且反射可以调用任何一个类里面的成员4.2.特性运行时类信息全知    只要程序运行,那么通过反射就可以知道类里面全部信息动态的创建对象    由于你知道信息,那么你通过反射也可以创建对象动态的调用方法    也是因为知道全部信息,因此可以调用访问和修改字段值    一样4.3.原理编译器会将源代码先编译成字节码,然后JVM根据字节码翻译成机器码,而反射就是通过字节码从而知道类的全部信息5.异常5.1.异常的种类5.2.处理异常的方法使用try-catch-fianlly(包裹可能会出现异常的代码)手动抛出异常(throw)方法或类抛出异常(throws)如果你使用try-catch-fianlly那么你需要注意:当 try 代码块中的语句发生异常时,后续代码将不会执行,异常会立即传递给 catch 块。如果 catch 块未能捕获对应类型的异常,该异常会继续向上层抛出(例如方法b抛出的异常可被调用它的方法a捕获)。----catch接收到异常那么就会执行catch里面的对应代码----如果定义了fianlly,不管有没有异常,fianlly都会执行(常用于释放锁)----执行顺序规则(fianlly,如果return在前,fianlly在后)return 语句计算返回值:先将返回值存储到临时变量中。执行 finally 块:无论是否有 return 或异常,finally 都会执行。方法返回临时变量中的值:如果 finally 中没有新的 return,则返回最初的值;如果 finally 中有 return,则会覆盖原值。6.Object6.1.等于与equals()区别等于就是:比较基本类型比值,比较引用类型比地址(比表面的)而equals():你如果重写了该方法,那么比较引用类型时比较里面的属性值,没有重写与等于一样6.2.hashcode()与equals()关系前提:你如果要重写这其中的一个方法,那么另外一个也需要重写(约定)一致性:当equals()比较对象相等时,那么hashcode也一定相等(本质就是根据内部值计算出哈希值,而内部值都相等了,你的哈希值肯定相等)不一致性:当hasjcode相等时,equals()不一定相等,因为你不同的对象计算出来的哈希值可能相同(哈希冲突)6.3String、StringBuffer、StringBuilder的区别1.可变性-----解释:在Java中,String对象一旦创建就不可修改,具有不可变性。每次对String进行修改时,实际上都会创建一个新的String对象(数组也有类似特性)。而StringBuffer、StringBuilder可变2.线程安全性---解释:由于String不可变,因此天然的线程安全, 而StringBuilder是线程不安全的(单线程来访问最好),StringBuffer是线程安全的,其内部方法(在StringBuilder方法上)实现了悲观锁synchronized3.性能----解释:由于String每次修改就需要创建一个新的对象,因此性能低, StringBuffer由于内部方法实现了锁(而锁本身就会影响性能),因此性能一般,StringBuilder性能最好 7.序列化7.1.JVM之间的传递对象1.使用序列化和反序列化---解释:你先通过序列化将对象序列化成字节流存入文件中,再将文件传输,接收到文件后再将文件反序列化成对象即可2.使用消息队列---解释:使用消息队列( RabbitMQ、Kafka)来传递消息,另一边接收消息即可 3.使用远程方法(RPC)4.使用数据库(MYSQL)或缓存(Redis)---解释:比如将对象之间存入数据库或缓存中,直接访问数据即可  8.设计模式代理模式:目的:控制对象的访问或增加一些新的方法内容:抽象主题,真实主题,代理三个角色适配器模式:目的:转换接口,是不兼容的类可以并行执行内容:目标接口,适配器,被适配对象9.I/O9.1.实现网络IO高并发编程使用NIO,NIO是同步非阻塞执行的,NIO 是基于I/O多路复用实现的,它可以只用一个线程处理多个客户端I/O,如果你需要同时管理成千上万的连接,但是每个连接只发送少量数据,例如一个聊天服务器,用NIO实现会更好一些。9.2.BIO、NIO、AIO区别BIO    同步阻塞式执行NIO    同步非阻塞执行AIO    异步非阻塞执行解释:同步阻塞式执行:就是说你发完一个消息后,你会一直等待别人回你消息,并且在等待的过程中不会干任何事情同步非阻塞执行:就是说你发完一个消息后,你会一直等待别人回你消息,但是在等待的过程中你可以做一些自己的事情异步非阻塞执行:就是说你发完一个消息后,你不会等待别人回你消息————————————————原文链接:https://blog.csdn.net/2402_88700528/article/details/148211874
  • [技术干货] 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文件写入。本文还有配套的精品资源,点击获取 简介:在IT领域,文档格式转换是常见的任务之一,特别是在管理大量文本数据时。本文将详细探讨如何利用Java技术将Word文档(.docx)转换成PDF格式。转换过程包括文件读取、解析、格式转换等多个技术步骤,并涉及对第三方库的使用。文章假设存在一个名为 DoxcToPdf 的工具或库,用于完成这一转换任务,并对整个过程进行详细解析,包括错误处理和性能优化的考虑。————————————————原文链接:https://blog.csdn.net/weixin_42181686/article/details/146289533
  • [技术干货] Java内存区域与内存溢出异常分析与解决【转】
    一、Java 内存区域概述1.1 程序计数器(Program Counter Register)程序计数器是当前线程所执行的字节码的行号指示器。它不会出现内存溢出问题。1.2 Java 虚拟机栈(Java Virtual Machine Stacks)每个线程都有一个私有的虚拟机栈,用于存储方法调用过程中的局部变量、操作数栈等信息。栈溢出场景12345678910111213141516171819public class JavaVMStackSOF {    private int stackLength = 1;     public void stackLeak() {        stackLength++;        stackLeak();    }     public static void main(String[] args) throws Throwable {        JavaVMStackSOF oom = new JavaVMStackSOF();         try {            oom.stackLeak();        } catch (Throwable e) {            System.out.println("stack length: " + oom.stackLength);            throw e;        }    }}运行结果:stack length: 2402Exception in thread "main" java.lang.StackOverflowError1.3 本地方法栈(Native Method Stacks)本地方法栈与虚拟机栈类似,但用于存储本地方法(Native 方法)的调用信息。1.4 Java 堆(Java Heap)Java 堆是所有线程共享的内存区域,用于存储对象实例和数组。堆溢出场景123456789101112import java.util.ArrayList; public class HeapOOM {    static class OOMObject {}     public static void main(String[] args) {        ArrayList<OOMObject> list = new ArrayList<>();        while (true) {            list.add(new OOMObject());        }    }}运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space1.5 方法区(Method Area)方法区用于存储类的结构信息、常量池、方法数据等。方法区溢出场景1234567891011121314151617181920import java.util.ArrayList;import java.util.List; public class MethodAreaOOM {    public static void main(String[] args) {        List<Class<?>> list = new ArrayList<>();        int i = 0;        while (true) {            list.add(new MyClassLoader().findClass("com.example.DummyClass" + i++));        }    } ​​​​​​​    static class MyClassLoader extends ClassLoader {        @Override        protected Class<?> findClass(String name) {            byte[] b = new byte[0];            return defineClass(name, b, 0, b.length);        }    }}运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Metaspace1.6 本机直接内存(Direct Memory)本机直接内存用于直接内存操作,通常通过 ByteBuffer 使用。直接内存溢出场景123456789101112131415import java.nio.ByteBuffer; public class DirectMemoryOOM {    private static final int _1MB = 1024 * 1024;     public static void main(String[] args) {        try {            while (true) {                ByteBuffer.allocateDirect(_1MB);            }        } catch (Throwable e) {            e.printStackTrace();        }    }}运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory二、内存溢出异常及其解决方法2.1 Java 堆溢出原因Java 堆用于存储对象实例,当不断创建对象且 GC Roots 到对象之间有可达路径时,堆内存耗尽会引发 OutOfMemoryError。解决方法使用内存映像分析工具(如 Eclipse Memory Analyzer)分析堆转储快照,确定是内存泄漏还是内存溢出。调整虚拟机堆参数(-Xmx 和 -Xms)。优化代码,减少不必要的对象引用,缩短对象生命周期。2.2 虚拟机栈和本地方法栈溢出原因线程请求的栈深度超过虚拟机允许的最大深度会引发 StackOverflowError;如果动态扩展栈时无法申请到足够内存,则引发 OutOfMemoryError。解决方法调整栈大小参数(-Xss)。优化递归算法,减少栈深度。2.3 方法区溢出原因方法区存储类的结构信息,动态生成大量类(如使用 CGLib)会导致方法区溢出。解决方法调整方法区大小参数(-XX:PermSize 和 -XX:MaxPermSize)。优化类的加载和卸载机制。2.4 运行时常量池溢出原因运行时常量池存储字符串常量等数据,当常量池满且无法扩展时会引发溢出。解决方法调整方法区大小参数。避免大量动态生成字符串常量。2.5 本机直接内存溢出原因直接内存用于直接内存操作,当直接内存耗尽时会引发溢出。解决方法调整直接内存大小参数(-XX:MaxDirectMemorySize)。优化代码,及时释放直接内存。
  • [技术干货] shiro的active Directory认证
    Apache Shiro 提供了对 Active Directory (AD) 的直接支持,通过 ActiveDirectoryRealm 可以轻松集成企业级 AD 环境,实现集中式身份认证。以下是 Shiro 中实现 AD 认证的完整指南,包括配置、代码示例和常见问题解决方案。1. Active Directory 认证基础作用:通过 AD 服务器验证用户凭据(用户名/密码),并获取用户角色和权限。优势:直接利用企业现有的 AD 基础设施,无需单独维护用户数据库。支持组权限管理(通过 AD 的组策略)。适用于 Windows 域环境下的单点登录(SSO)。2. Shiro AD 认证实现步骤(1) 添加依赖确保项目中包含 Shiro 和 AD 相关依赖(Maven 示例):<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.12.0</version> <!-- 使用最新版本 --> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-activedirectory</artifactId> <version>1.12.0</version> </dependency> (2) 配置 shiro.ini 文件通过 shiro.ini 配置 AD 认证的核心参数:[main] # 配置 ActiveDirectoryRealm activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 # AD 服务器地址 activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com # 系统管理员 DN activeDirectoryRealm.systemPassword = admin123 # 系统管理员密码 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com # 用户搜索基路径 activeDirectoryRealm.groupRolesMap = "CN=Developers,CN=Users,DC=example,DC=com:developer,CN=Admins,CN=Users,DC=example,DC=com:admin" # AD 组到 Shiro 角色的映射 # 可选:启用调试日志 loggerFactory.logLevel = debug关键参数说明:url:AD 服务器地址(如 ldap://ad.example.com:389)。systemUsername 和 systemPassword:用于搜索用户的系统管理员凭据(需具有读取权限)。searchBase:用户搜索的基路径(通常是 CN=Users,DC=example,DC=com)。groupRolesMap:AD 组到 Shiro 角色的映射(格式:AD组DN:Shiro角色)。(3) 编写 Java 代码进行认证import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class AdAuthDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("user1", "password123"); try { // 执行认证 currentUser.login(token); System.out.println("AD 认证成功!"); System.out.println("用户角色: " + currentUser.getPrincipals().asList()); // 输出角色 } catch (Exception e) { System.out.println("AD 认证失败:" + e.getMessage()); } } } 3. 高级配置(1) 启用 SSL/TLS如果 AD 服务器使用安全连接:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldaps://ad.example.com:636 # 使用 ldaps activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com activeDirectoryRealm.systemPassword = admin123(2) 自定义搜索过滤器如果默认搜索不满足需求,可以自定义搜索过滤器:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com activeDirectoryRealm.searchFilter = (sAMAccountName={0}) # 使用 AD 的 sAMAccountName 属性(3) 动态角色映射如果需要动态映射 AD 组到 Shiro 角色,可以继承 ActiveDirectoryRealm 并重写方法:import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashMap; import java.util.Map; public class CustomAdRealm extends ActiveDirectoryRealm { @Override protected Map<String, String> getGroupRolesMap(PrincipalCollection principals) { Map<String, String> rolesMap = new HashMap<>(); // 动态映射逻辑(例如从数据库加载) rolesMap.put("CN=Developers,CN=Users,DC=example,DC=com", "developer"); rolesMap.put("CN=Admins,CN=Users,DC=example,DC=com", "admin"); return rolesMap; } } 然后在 shiro.ini 中配置:[main] customAdRealm = com.example.CustomAdRealm customAdRealm.url = ldap://ad.example.com:389 securityManager.realms = $customAdRealm4. 常见问题与解决方案(1) 认证失败:Invalid credentials原因:用户名或密码错误,或 AD 服务器配置不正确。解决:检查 systemUsername 和 systemPassword 是否正确。确认 AD 用户的 sAMAccountName 是否与输入的用户名匹配。(2) 认证失败:Connection refused原因:AD 服务器地址或端口错误。解决:确认 url 是否正确(如 ldap://ad.example.com:389)。检查防火墙是否放行 LDAP 端口(389 或 636)。(3) 角色映射不生效原因:groupRolesMap 配置错误或 AD 组路径不正确。解决:使用 AD 工具(如 Active Directory Users and Computers)确认组 DN。确保 groupRolesMap 的格式为 AD组DN:Shiro角色。(4) 性能问题原因:频繁的 AD 查询导致延迟。解决:使用缓存(如 Shiro 的 CachingRealm)。优化搜索过滤器,减少返回的数据量。5. 总结Shiro 的 Active Directory 认证通过 ActiveDirectoryRealm 实现,核心步骤包括:配置 shiro.ini 文件,设置 AD 服务器地址、系统管理员凭据、搜索基路径和角色映射。编写 Java 代码,通过 Subject.login() 执行认证。根据需求调整高级配置(如 SSL、自定义搜索、动态角色映射)。适用场景:企业级应用需要与现有 AD 环境集成。需要基于 AD 组管理用户角色和权限。适用于 Windows 域环境下的单点登录(SSO)。通过合理配置,Shiro 可以高效、安全地实现与 Active Directory 的认证集成。
  • [技术干货] Shiro 的 LDAP 认证
    Shiro 的 LDAP 认证方式详解Apache Shiro 提供了对 LDAP(轻量级目录访问协议)的集成支持,用于企业级系统的集中式身份认证。以下是 Shiro 中实现 LDAP 认证的完整指南,包括配置、代码示例和关键点说明。1. LDAP 认证基础作用:通过 LDAP 服务器(如 Active Directory、OpenLDAP)验证用户凭据(用户名/密码)。优势:集中式用户管理,无需在应用中维护密码。支持企业级目录服务,便于集成现有系统。2. Shiro LDAP 认证实现步骤(1) 添加依赖确保项目中包含 Shiro 和 LDAP 相关依赖(以 Maven 为例):<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.12.0</version> <!-- 使用最新版本 --> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ldap</artifactId> <version>1.12.0</version> </dependency> (2) 配置 shiro.ini 文件通过 shiro.ini 配置 LDAP 认证的核心参数:[main] # 配置 LDAP Realm ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldap://ldap.example.com:389 # LDAP 服务器地址 ldapRealm.contextFactory.authenticationMechanism = simple # 认证机制(simple/DIGEST-MD5) ldapRealm.userDnTemplate = uid={0},ou=users,dc=example,dc=com # 用户 DN 模板 ldapRealm.searchBase = dc=example,dc=com # 搜索基路径 ldapRealm.systemUsername = cn=admin,dc=example,dc=com # 系统管理员 DN(用于搜索) ldapRealm.systemPassword = admin123 # 系统管理员密码 # 可选:启用调试日志 loggerFactory.logLevel = debug关键参数说明:contextFactory.url:LDAP 服务器地址(如 ldap://ldap.example.com:389)。userDnTemplate:用户 DN 模板,{0} 会被替换为用户名。searchBase:搜索用户的基路径(如 dc=example,dc=com)。systemUsername 和 systemPassword:用于搜索用户的系统管理员凭据(可选,取决于 LDAP 配置)。(3) 编写 Java 代码进行认证import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class LdapAuthDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("user1", "password123"); try { // 执行认证 currentUser.login(token); System.out.println("LDAP 认证成功!"); } catch (Exception e) { System.out.println("LDAP 认证失败:" + e.getMessage()); } } } 3. 高级配置(1) 自定义 LDAP 搜索如果用户 DN 不能通过模板直接生成,可以自定义搜索逻辑:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldap://ldap.example.com:389 ldapRealm.searchBase = dc=example,dc=com ldapRealm.systemUsername = cn=admin,dc=example,dc=com ldapRealm.systemPassword = admin123 # 自定义搜索过滤器 ldapRealm.searchFilter = (uid={0}) # 搜索过滤器(默认是 (uid={0}))(2) 启用 SSL/TLS如果 LDAP 服务器使用安全连接:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldaps://ldap.example.com:636 # 使用 ldaps ldapRealm.contextFactory.environment[java.naming.security.protocol] = ssl # 启用 SSL (3) 结合 Active DirectoryActive Directory 的配置略有不同:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com activeDirectoryRealm.systemPassword = admin123 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com4. 常见问题与解决方案(1) 认证失败:Invalid credentials原因:用户名或密码错误,或 LDAP 服务器配置不正确。解决:检查 userDnTemplate 或 searchFilter 是否正确。使用工具(如 Apache Directory Studio)测试 LDAP 连接。(2) 认证失败:Connection refused原因:LDAP 服务器地址或端口错误。解决:确认 contextFactory.url 是否正确。检查防火墙是否放行 LDAP 端口(389 或 636)。(3) 性能问题原因:频繁的 LDAP 查询导致延迟。解决:使用缓存(如 Shiro 的 CachingRealm)。优化搜索过滤器,减少返回的数据量。5. 总结Shiro 的 LDAP 认证通过 JndiLdapRealm 或 ActiveDirectoryRealm 实现,核心步骤包括:配置 shiro.ini 文件,设置 LDAP 服务器地址、用户 DN 模板等。编写 Java 代码,通过 Subject.login() 执行认证。根据需求调整高级配置(如 SSL、自定义搜索)。适用场景:企业级应用需要与现有 LDAP/AD 集成。需要集中式用户管理和单点登录(SSO)。通过合理配置,Shiro 可以轻松实现安全、可靠的 LDAP 认证。
  • [技术干货] shiro细粒度资源访问控制
    细粒度资源访问控制是 Shiro 的核心功能之一,允许开发者基于资源实例(如数据库记录、文件路径等)和操作(如读取、编辑、删除)定义权限。以下是完整的实现步骤和示例代码:1. 核心概念权限字符串格式:资源:操作(如 user:edit:123 表示编辑 ID 为 123 的用户)授权流程:通过 Subject.isPermitted("权限字符串") 动态判断权限。2. 完整实现步骤(1) 自定义 Realm 实现权限校验import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class PermissionRealm extends AuthorizingRealm { // 模拟数据库存储的用户角色和权限 private static final Map<String, Set<String>> USER_ROLES = new HashMap<>(); private static final Map<String, Set<String>> ROLE_PERMISSIONS = new HashMap<>(); static { // 初始化角色和权限数据 Set<String> adminPermissions = new HashSet<>(); adminPermissions.add("user:create"); adminPermissions.add("user:edit:123"); // 允许编辑ID为123的用户 adminPermissions.add("user:delete"); ROLE_PERMISSIONS.put("admin", adminPermissions); Set<String> userPermissions = new HashSet<>(); userPermissions.add("user:view"); ROLE_PERMISSIONS.put("user", userPermissions); USER_ROLES.put("admin", new HashSet<>(Collections.singletonList("admin"))); USER_ROLES.put("user1", new HashSet<>(Arrays.asList("user", "guest"))); } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 模拟从数据库获取用户信息 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); if (!"admin".equals(username) && !"user1".equals(username)) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(username, "password", getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); Set<String> roles = USER_ROLES.get(username); if (roles == null) { return null; } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); // 根据角色添加权限 for (String role : roles) { Set<String> permissions = ROLE_PERMISSIONS.get(role); if (permissions != null) { info.addStringPermissions(permissions); } } return info; } } (2) 配置 Shiro 使用自定义 Realmshiro.ini 配置文件:[main] permissionRealm = com.example.PermissionRealm securityManager.realms = $permissionRealm(3) 测试细粒度权限校验import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class PermissionDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 模拟用户登录 currentUser.login(new org.apache.shiro.authc.UsernamePasswordToken("admin", "password")); // 测试细粒度权限 System.out.println("是否有创建用户权限: " + currentUser.isPermitted("user:create")); // true System.out.println("是否有编辑ID为123的用户权限: " + currentUser.isPermitted("user:edit:123")); // true System.out.println("是否有删除用户权限: " + currentUser.isPermitted("user:delete")); // true System.out.println("是否有编辑ID为456的用户权限: " + currentUser.isPermitted("user:edit:456")); // false(未授权) // 模拟普通用户登录 currentUser.logout(); currentUser.login(new org.apache.shiro.authc.UsernamePasswordToken("user1", "password")); System.out.println("普通用户是否有编辑权限: " + currentUser.isPermitted("user:edit:123")); // false } } 3. 关键点说明权限字符串设计:user:create:全局用户创建权限。user:edit:123:仅允许编辑 ID 为 123 的用户,实现实例级控制。动态权限校验:通过 Subject.isPermitted("权限字符串") 动态判断,无需硬编码权限逻辑。扩展性:可从数据库加载权限数据,支持动态权限变更。4. 输出结果运行 PermissionDemo 的输出:是否有创建用户权限: true 是否有编辑ID为123的用户权限: true 是否有删除用户权限: true 是否有编辑ID为456的用户权限: false 普通用户是否有编辑权限: false 总结通过自定义 Realm 和权限字符串设计,Shiro 可以轻松实现细粒度资源访问控制。关键点包括:权限字符串格式化:用 资源:操作:实例 定义权限。动态校验:通过 Subject.isPermitted() 实时判断权限。数据驱动:权限数据可从数据库动态加载,适应复杂业务场景。这种设计既灵活又安全,适用于企业级应用中的权限管理需求。
  • [技术干货] shiro的OGNL 授权策略
    Shiro 的 OGNL (Object-Graph Navigation Language) 授权策略是一种高级的权限控制方式,允许开发者通过表达式语言定义复杂的权限规则。OGNL 表达式可以访问 Java 对象的方法和属性,从而动态判断权限。1. OGNL 授权策略概述作用:通过 OGNL 表达式定义权限条件,例如 user.age > 18 或 resource.owner == currentUser。使用场景:需要基于对象属性或业务逻辑动态判断权限时。2. 实现步骤(1) 自定义 Realm 支持 OGNL 表达式在 doGetAuthorizationInfo 方法中返回 OGNL 表达式对应的权限信息。import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class OgnlPermissionRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 模拟从数据库获取用户信息 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); if (!"admin".equals(username)) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(username, "password", getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加一个 OGNL 表达式权限 info.addObjectPermission(new WildcardPermission("user:edit:[id]")); return info; } } (2) 配置 Shiro 使用自定义 Realmshiro.ini 配置文件:[main] ognlPermissionRealm = com.example.OgnlPermissionRealm securityManager.realms = $ognlPermissionRealm(3) 自定义 PermissionResolver 解析 OGNL 表达式实现 PermissionResolver 接口,解析 OGNL 表达式。import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.permission.PermissionResolver; import org.apache.shiro.authz.permission.WildcardPermission; public class OgnlPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String permissionString) { // 解析 OGNL 表达式 if (permissionString.startsWith("user:edit:")) { return new OgnlPermission(permissionString); } return new WildcardPermission(permissionString); } } (4) 自定义 Permission 类import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.StringUtils; public class OgnlPermission implements Permission { private final String permissionString; public OgnlPermission(String permissionString) { this.permissionString = permissionString; } @Override public boolean implies(Subject subject, Permission permission) { // 实现 OGNL 表达式逻辑 if (permission instanceof OgnlPermission) { String otherPermission = ((OgnlPermission) permission).getPermissionString(); // 示例:解析 ID 并比较 if (otherPermission.startsWith("user:edit:")) { String id = StringUtils.substringAfter(otherPermission, "user:edit:"); // 假设从上下文中获取当前用户和资源 ID // 这里简化逻辑,直接返回 true(实际需根据业务实现) return true; } } return false; } public String getPermissionString() { return permissionString; } } (5) 测试 OGNL 权限import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class OgnlPermissionDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 模拟用户登录 currentUser.login(new UsernamePasswordToken("admin", "password")); // 测试 OGNL 权限 boolean hasPermission = currentUser.isPermitted("user:edit:123"); System.out.println("是否有编辑ID为123的用户权限: " + hasPermission); // true } } 3. 关键点说明OGNL 表达式解析:通过 PermissionResolver 解析自定义的 OGNL 表达式。在 implies 方法中实现具体的业务逻辑。动态权限判断:权限字符串可以包含变量(如 [id]),在运行时动态解析。扩展性:可以结合上下文信息(如当前用户、资源实例)实现复杂权限逻辑。4. 总结Shiro 的 OGNL 授权策略提供了一种灵活的方式来实现细粒度的权限控制,特别适合需要动态解析权限表达式的场景。通过自定义 PermissionResolver 和 Permission 类,可以轻松扩展 Shiro 的权限系统以满足业务需求。
  • [技术干货] shiro认证方式介绍
    Apache Shiro 支持多种认证方式,包括基于用户名/密码、自定义 Realm、第三方系统(如 LDAP、Active Directory)等。以下是几种常见的认证方式及其使用示例:1. 基于用户名/密码的认证这是最常见的认证方式,通常通过 UsernamePasswordToken 实现。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; public class UsernamePasswordAuthDemo { public static void main(String[] args) { // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } 2. 自定义 Realm 认证如果需要自定义认证逻辑(例如从数据库获取用户信息),可以实现 Realm 接口。自定义 Realm 示例:import org.apache.shiro.authc.*; import org.apache.shiro.realm.Realm; public class CustomRealm implements Realm { @Override public String getName() { return "CustomRealm"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); String password = new String(upToken.getPassword()); // 模拟从数据库获取用户信息 if (!"admin".equals(username) || !"123456".equals(password)) { throw new UnknownAccountException("用户名或密码错误"); } // 返回认证信息(用户名、密码、盐、Realm 名称) return new SimpleAuthenticationInfo(username, password, getName()); } } 配置 Shiro 使用自定义 Realm:import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class CustomRealmDemo { public static void main(String[] args) { // 配置 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } shiro.ini 配置文件:[main] customRealm = com.example.CustomRealm securityManager.realms = $customRealm3. 集成 LDAP 认证Shiro 支持与 LDAP 集成,用于企业级系统的认证。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class LdapAuthDemo { public static void main(String[] args) { // 配置 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-ldap.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } shiro-ldap.ini 配置文件:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.url = ldap://localhost:389 ldapRealm.contextFactory.url = ldap://localhost:389 ldapRealm.userDnTemplate = uid={0},ou=users,dc=example,dc=com4. 记住我(RememberMe)功能Shiro 支持“记住我”功能,允许用户在关闭浏览器后重新登录时无需再次输入凭据。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; public class RememberMeDemo { public static void main(String[] args) { // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token,并启用“记住我”功能 UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); token.setRememberMe(true); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } 总结以上是 Shiro 中几种常见的认证方式及其使用示例。Shiro 的灵活性使其能够轻松集成到各种应用程序中,并根据需求选择合适的认证方式。
  • [技术干货] Apache Shiro 框架介绍
    Apache Shiro 简介Apache Shiro 是一个强大且易于使用的 Java 安全框架,旨在简化应用程序的身份验证、授权、加密和会话管理。它提供了全面的安全功能,同时保持了简洁性和灵活性,适用于各种规模的应用程序,从小型独立应用到大型企业级系统。Shiro 的核心功能Shiro 的核心功能可以归纳为四个主要方面:认证(Authentication)、授权(Authorization)、加密(Cryptography) 和 会话管理(Session Management)。以下是对这些功能的详细介绍:1. 认证(Authentication)认证是验证用户身份的过程,确保用户是他们声称的那个人。Shiro 提供了灵活的认证机制,支持多种认证方式,如用户名/密码、数字证书、OpenID 等。特点:支持多种认证数据源(如数据库、LDAP、Active Directory 等)。支持多因素认证。易于集成到现有应用程序中。示例:UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token); // 执行认证 2. 授权(Authorization)授权是控制用户访问权限的过程,决定用户可以执行哪些操作或访问哪些资源。Shiro 提供了细粒度的授权控制,支持基于角色、权限和实例级别的授权。特点:支持基于角色的访问控制(RBAC)。支持基于权限的访问控制(如细粒度的资源访问控制)。支持表达式语言(如 Shiro 的 OGNL 表达式)进行灵活的授权策略定义。示例:// 检查用户是否具有特定角色 if (currentUser.hasRole("admin")) { // 用户具有管理员角色,执行相应操作 } // 检查用户是否具有特定权限 if (currentUser.isPermitted("user:create")) { // 用户具有创建用户的权限,执行相应操作 } 3. 加密(Cryptography)Shiro 提供了对加密操作的支持,包括哈希(Hashing)、加密(Encryption)和解密(Decryption)。这使得应用程序能够安全地存储密码、敏感数据等。特点:提供多种哈希算法(如 MD5、SHA-256 等)。支持对称加密和非对称加密。提供密码盐(Salt)支持,增强安全性。示例:// 使用 MD5 哈希算法对密码进行哈希处理 String hashedPassword = new Md5Hash("password", "salt").toString(); 4. 会话管理(Session Management)Shiro 提供了完整的会话管理功能,类似于 HTTP 会话,但不限于 Web 环境。它允许应用程序在非 Web 环境中(如桌面应用、移动应用等)也能管理用户会话。特点:支持会话的创建、销毁、持久化等操作。提供会话超时、会话监听等功能。支持分布式会话管理(通过 Redis 等缓存系统)。示例:// 获取当前用户的会话 Session session = currentUser.getSession(); // 设置会话属性 session.setAttribute("key", "value"); // 获取会话属性 Object value = session.getAttribute("key"); Shiro 的其他特性除了上述核心功能外,Shiro 还具有以下特性:Web 支持:提供对 Web 应用程序的支持,包括过滤器、Servlet 集成等。缓存支持:可以与多种缓存系统(如 Ehcache、Redis 等)集成,提高性能。“记住我”功能:支持用户登录后的“记住我”功能,提升用户体验。集成 Spring:提供与 Spring 框架的紧密集成,方便在 Spring 应用中使用 Shiro。总结Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它提供了全面的身份验证、授权、加密和会话管理功能。通过使用 Shiro,开发者可以轻松地为应用程序添加安全功能,保护应用程序免受未经授权的访问和攻击。无论是小型独立应用还是大型企业级系统,Shiro 都是一个值得考虑的安全解决方案。
  • [问题求助] 【问题求助】 算法精英实战营
    提交作品后,经历2小时后报错:internal_error,中间没有出现任何超时现象,而在本地跑是没有问题的。想问下,这种情况,是华为云平台本身的问题,还是我提交的作品的问题呀?如果是我作品的问题,这种情况,是什么原因导致的呢?
  • [体验官] CodeArts的Java版本和C/C++和ShellRemote是一个安装包吗?
    CodeArts的Java版本和C/C++和ShellRemote是一个安装包吗, 为啥我下载这三个的安装包,安装下来都是一个软件呢?
  • [技术干货] Java内存分析全面指南
    Java内存分析是诊断性能问题和内存泄漏的关键技能。下面我将从内存结构、分析工具、常见问题和实战案例等方面进行全面介绍。一、Java内存结构详解1. 堆内存(Heap)新生代(Young Generation)Eden区:新对象分配区域Survivor区(From/To):存活对象过渡区默认比例:Eden:From:To = 8:1:1老年代(Old Generation)存放长期存活对象大对象直接进入老年代元空间(Metaspace)存储类元数据取代永久代(PermGen)默认不限制大小(受物理内存限制)2. 非堆内存JVM栈:线程私有,存储栈帧本地方法栈:Native方法调用程序计数器:线程执行位置二、内存分析工具1. JDK自带工具工具命令用途jpsjps -lvm查看Java进程jstatjstat -gcutil <pid> 1000实时GC统计jmapjmap -heap <pid>堆内存快照jhatjmap -dump:format=b,file=heap.hprof <pid> + jhat heap.hprof堆转储分析jstackjstack -l <pid>线程堆栈分析2. 图形化工具VisualVM:多功能监控/分析JConsole:基础监控Eclipse MAT:专业堆分析JProfiler:商业级分析工具Arthas:阿里开源的在线诊断工具3. 生产环境推荐Prometheus + Grafana:时序监控HeapHero:自动化堆分析GCeasy:GC日志分析三、常见内存问题分析1. 内存泄漏(Memory Leak)特征:堆内存持续增长Full GC后内存不释放OOM错误诊断步骤:获取堆转储:jmap -dump:live,format=b,file=leak.hprof <pid>使用MAT分析查找"Accumulation Point"(积累点)检查大对象和GC Roots引用链2. 内存溢出(OOM)常见类型:java.lang.OutOfMemoryError: Java heap space:堆内存不足java.lang.OutOfMemoryError: Metaspace:元空间不足java.lang.OutOfMemoryError: Unable to create new native thread:线程过多解决方案:增加对应区域内存(-Xmx, -XX:MaxMetaspaceSize)优化代码(减少对象创建)调整线程池配置3. GC相关问题症状:应用停顿(STW时间过长)CPU使用率高(GC线程占用)吞吐量下降分析方法:收集GC日志:-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps使用GCeasy或GCEViewer分析四、实战分析案例案例1:内存泄漏分析场景:Web应用运行一段时间后OOM步骤:获取堆转储使用MAT打开,选择"Leak Suspects"报告发现HashMap占用了80%内存检查引用链发现静态Map缓存未清理解决方案:改用WeakHashMap或添加清理逻辑案例2:元空间溢出场景:动态生成类导致Metaspace不足解决:增加Metaspace大小:-XX:MaxMetaspaceSize=256m使用jcmd <pid> VM.metaspace监控优化动态类生成逻辑案例3:GC频繁症状:Young GC每分钟超过20次调优:增加新生代大小:-Xmn512m调整Survivor比例:-XX:SurvivorRatio=6优化短命对象创建五、高级分析技巧1. 堆外内存分析使用NMT(Native Memory Tracking):-XX:NativeMemoryTracking=detailjcmd <pid> VM.native_memory detail检查DirectByteBuffer使用2. 线程堆栈分析jstack <pid> > thread.txt查找死锁(deadlock)和阻塞(blocked)线程结合top -Hp <pid>查找CPU高的线程3. 内存分配分析使用JFR(Java Flight Recorder):-XX:StartFlightRecording=duration=60s,filename=alloc.jfr分析对象分配热点
  • [互动交流] CodeArts IDE for java 什么时候支持 java21 版本呢
    CodeArts IDE最新只能选择 17 的特性,什么时候支持 21
  • [问题求助] 算法精英实战营-第二十四期-问题求助
    有2个问题需要求助:1.压缩包内需要存放整个项目还是单独的文件就可以呀?2.上传作品后得分为-1,是因为编译没通过吗?还是其他什么原因呀?
  • [技术干货] 解析Java根基:Object类核心方法
    Object类常见方法解析在Java编程中,Object类是所有类的根类,它包含了许多实用的方法,这些方法在不同的场景下发挥着重要作用。下面我们来详细了解一下Object类中的一些常见方法。1. toString方法toString方法是用于将对象转换为字符串表示形式的方法。在默认情况下,toString方法返回的结果是类名加上@符号,再跟上该对象对应哈希码的十六进制表示。例如,当我们打印一个对象时,如果没有重写toString方法,就会得到类似这样的结果:com.example.MyClass@12345678。然而,在实际开发中,我们通常需要根据对象的具体属性来定制它的字符串表示形式,以便更清晰地展示对象的信息。这时,我们就需要重写toString方法。比如,对于一个表示学生信息的类Student,我们可以这样重写toString方法:public class Student {    private String name;    private int age;     // 构造方法和其他方法省略     @Override    public String toString() {        return "Student{name='" + name + "', age=" + age + "}";    }}这样,当我们打印一个Student对象时,就会得到包含学生姓名和年龄信息的字符串。2. equals和hashcode方法equals方法和hashcode方法在对象比较和集合操作中起着关键作用。equals方法:在进行对象比较的时候,我们通常会使用equals方法。如果不重写equals方法,默认情况下它会根据对象的地址进行比较,即只有两个引用指向同一个对象时,它们才被认为是相等的。但在很多实际场景中,我们需要根据对象的属性和方法来判断它们是否相等。例如,对于两个表示学生信息的对象,如果它们的姓名和年龄都相同,我们就认为这两个学生是相等的。这时,我们就需要重写equals方法,如下所示:@Overridepublic boolean equals(Object obj) {    if (this == obj) return true;    if (obj == null || getClass()!= obj.getClass()) return false;    Student student = (Student) obj;    return age == student.age && Objects.equals(name, student.name);}hashcode方法:hashcode方法用于返回对象的哈希码值。哈希码是一个整数,它在许多数据结构中都有重要作用,比如HashMap和HashSet。当我们对对象进行存储和查找操作时,哈希码可以帮助我们快速定位对象的位置。需要注意的是,当我们重写equals方法时,通常也需要同时重写hashcode方法。这是因为如果两个对象根据equals方法判断是相等的,那么它们的哈希码也必须相等。如果不重写hashcode方法,可能会导致哈希冲突的概率增大。例如,在HashMap中,如果只重写了equals方法而没有重写hashcode方法,那么具有相同属性的两个对象可能会被存储在不同的哈希桶中,这会使得一些哈希桶中的对象个数过多,从而降低搜索效率。另外,如果只重写了hashcode方法而没有重写equals方法,当发生哈希冲突时,会导致对象的安全性受到威胁。因为在哈希冲突的情况下,不同的对象可能会被存储在同一个哈希桶中,如果不重写equals方法,就无法正确判断这些对象是否真的相等,从而可能导致一些错误的结果。3. wait、notify和notifyAll方法wait、notify和notifyAll方法是用于线程间通信的方法,它们只能在同步代码块或同步方法中使用。 wait方法:当一个线程调用某个对象的wait方法时,它会解除对该对象的锁,并进入等待状态,直到其他线程调用该对象的notify或notifyAll方法来唤醒它。例如,在生产者-消费者模型中,当消费者发现缓冲区为空时,它会调用缓冲区对象的wait方法进入等待状态,直到生产者生产了新的数据并调用notify或notifyAll方法来唤醒它。notify方法:notify方法用于唤醒陷入等待状态的某一进程。它会随机选择一个正在等待该对象锁的线程,并将其唤醒。需要注意的是,notify方法只会唤醒一个线程,如果有多个线程在等待,那么具体唤醒哪个线程是不确定的。notifyAll方法:notifyAll方法用于唤醒陷入等待状态的所有进程。它会唤醒所有正在等待该对象锁的线程,这些线程会竞争获取对象的锁,然后继续执行。在这个示例中,生产者线程先生产数据,然后通过notify方法唤醒消费者线程,消费者线程在收到通知后开始消费数据。Java为什么被称为平台无关性语言Java实现平台无关性的核心机制在于JVM(Java虚拟机)的中间层设计。当Java源代码通过javac编译器生成字节码文件(.class文件)后,这些包含平台中立中间代码的文件可以在任何安装有对应平台JVM的设备上运行。各个操作系统虽然使用不同的机器指令集,但通过针对特定平台实现的JVM,字节码会被实时转换为所在系统的本地机器指令执行。这种"一次编译,到处运行"的特性,使得开发者无需针对不同操作系统修改源代码,JVM作为抽象层有效隔离了底层硬件和操作系统的差异。= =和equals有什么区别?== 和 equals 的区别基本概念== 是一个操作符,用于比较两个变量的值或引用。equals 是 Object 类的一个方法,用于比较两个对象的内容。如果 equals 方法没有被重写,它的默认行为与 == 相同。比较规则对于基本数据类型(如 int、char 等):== 比较的是变量的值。例如:int a = 5;int b = 5;System.out.println(a == b); // 输出 true对于对象类型(如 String、Integer 等):== 比较的是对象的引用(即内存地址)。例如:String str1 = new String("hello");String str2 = new String("hello");System.out.println(str1 == str2); // 输出 falseequals 方法比较的是对象的内容。例如:System.out.println(str1.equals(str2)); // 输出 trueequals 方法的重写默认情况下,equals 方法比较的是对象的引用(与 == 相同)。如果类重写了 equals 方法,则可以根据自定义的逻辑比较对象的内容。例如,String 类重写了 equals 方法,用于比较字符串的内容。重写 equals 方法时,通常需要同时重写 hashCode 方法,以确保对象在哈希表等数据结构中的行为一致。equals()与hashcode()equals和hashCode属于Object的方法,equals默认情况下和==等效,hashCode()是根据一定的规则根据对象返回的值,比如对象的地址,经过处理,返回哈希值在进行对象的比较的时候,可以进行自定义比较方法,需要重写equals方法,为了使HashMap、HashSet等方法正常存储,还需要对HashCode()进行重写HashMap存储对象的时候,会先调用HashCode方法进行比较,当HashCode值相等的时候,再使用equals方法再进行比较Student对象,有name和height两个成员变量,使用HashSet存储两个name和height相等的对象,判断Student对象是否相等就看这两个变量,当没有重写HashCode的时候,存储到HashSet中也会当作两个不同的对象,调用HashCode方法,因为没有重写,默认哈希值不会相同,所以会被认为两个对象不相同解决方法就是重写HashCode(),最简单的就是返回两个变量哈希值的积equals() 与 hashCode() 的核心机制equals() 和 hashCode() 是定义在 java.lang.Object 类中的基础方法。equals() 方法:默认实现与 == 运算符等价,用于判断两个对象是否严格相等(即是否指向同一内存地址)。开发中需根据业务需求重写此方法,以实现自定义的相等性逻辑。hashCode() 方法:默认返回对象的内存地址经过哈希算法处理后的整数值。其作用是为对象生成一个紧凑的哈希标识,主要用于快速定位数据存储位置(如哈希表)。集合框架中的关键作用当使用 HashMap、HashSet 等基于哈希的集合存储对象时:哈希优先原则:集合会先调用对象的 hashCode() 方法计算哈希值,若哈希值冲突,则进一步通过 equals() 方法比较对象的实际内容。一致性要求:若重写了 equals(),必须同步重写 hashCode()。若二者逻辑不一致(例如两个对象通过 equals() 判断相等,但 hashCode() 返回不同值),会导致集合无法正确维护元素唯一性,引发潜在 bug。Student 对象的典型场景分析假设存在如下 Student 类:public class Student {        private String name;        private int height;}未重写 hashCode() 的问题向 HashSet<Student> 中添加两个属性完全相同的对象时:Student s1 = new Student("Alice", 160);Student s2 = new Student("Alice", 160);Set<Student> students = new HashSet<>();students.add(s1);students.add(s2); // 实际执行结果:s2 会被视为新元素加入集合原因:默认的 hashCode() 实现基于对象内存地址,两次 new 操作生成的 s1 和 s2 地址不同,导致哈希值冲突被判定为不同对象。解决方案:重写 hashCode()通过覆盖 hashCode() 方法,将关键字段纳入哈希值计算:@Overridepublic int hashCode() {return Objects.hash(name, height); // 或简化为 name.hashCode() * 31 + height}原理:将 name 和 height 的哈希值按规则组合,确保内容相同的对象生成相同的哈希码,从而解决集合存储异常问题。重载和重写重载(Overloading)重载指的是在同一个类中,允许存在多个方法名相同但参数列表不同的方法。这些方法的参数个数、顺序或类型必须有所区别。通过这种方式,可以为同一操作提供多种不同的实现方式,以适应不同的输入需求。特点:方法签名不同:方法名相同,但参数列表(参数的个数、顺序或类型)不同。编译时决定:重载的解析是在编译期间完成的,编译器根据调用方法时提供的参数类型来决定具体调用哪一个重载的方法。静态绑定:由于在编译时已经确定了调用的具体方法,因此重载属于静态绑定。重写(Overriding)重写是指子类对父类中已有的方法进行重新定义,方法名、参数列表以及返回类型都必须与父类中的方法完全一致。通过重写,子类可以改变父类方法的行为,以适应子类的特定需求。特点:方法签名相同:方法名、参数列表和返回类型必须与父类中的方法完全一致。运行时决定:重写的解析是在运行期间完成的,具体执行哪一个方法取决于对象的实际类型。动态绑定:由于在运行时才确定调用的具体方法,因此重写属于动态绑定。抽象和接口抽象类与接口的对比分析【核心概念】抽象类使用 abstract 关键字修饰,用于定义具有部分实现的基类;接口使用 interface 关键字声明,作为纯粹的行为契约。二者在代码组织层面存在本质区别:【抽象类的应用】当多个类存在代码复用需求时,通过抽象类进行逻辑抽取可提升代码简洁性与可维护性。其核心价值体现在:代码复用:将公共实现逻辑提取到抽象基类中,子类通过继承复用代码强制约束:通过抽象方法(public abstract 修饰)强制子类实现特定功能类型标识:表达严格的继承关系,体现"是"(is-a)的语义特征修改时只需调整抽象基类即可影响所有子类,但需注意普通类也能实现类似效果,抽象类的核心区别在于其无法实例化并具有强制约束力。【接口的演进】接口作为规范标准,体现"具有...能力"(like-a)的语义特征,其发展历经多个阶段:传统接口(Java 7-)仅包含隐式 public abstract 修饰的抽象方法只允许声明 public static final 常量增强接口(Java 8+)引入 default 方法(含具体实现)支持静态方法定义允许定义私有方法(Java 9+)【实现规范】类实现接口时需遵循:必须实现所有抽象方法(未实现则类需声明为 abstract)可选择性覆盖 default 方法常量字段自动继承且不可修改支持多接口实现,体现灵活的组合特性Finalfinal 是 Java 的关键字,用于限制类、方法或变量的可修改性,具体用法如下:修饰类当类被 final 修饰时,该类不能被继承。核心目的:防止通过子类继承修改原有类的行为,常用于保护核心 API 的稳定性。示例:public final class String { ... } // String 类不可被继承修饰方法当方法被 final 修饰时,该方法不能被子类重写。核心目的:确保关键方法的行为不被破坏,常用于模板方法模式中的固定步骤。示例:public class Parent {        public final void lock() { ... } // 子类无法重写 lock()}修饰变量基本类型变量:变量值初始化后不可修改,成为常量。示例:final int MAX_VALUE = 100;// MAX_VALUE = 200; // 编译报错引用类型变量:引用类型变量变量指向的内存地址不可变,但对象内部状态可以修改。示例:final List list = new ArrayList<>();list.add("Java"); // 允许修改内容 // list = new LinkedList<>();  编译报错(地址不可变) static final 联合使用 static final 修饰的变量为全局常量,在类加载时初始化。 优势: 编译时常量(如字符串字面量)会被 JVM 优化,直接存入常量池,提升访问效率。 示例: public static final String VERSION = "1.0";不可变设计的经典案例:String 类的不可变性: String 类被 final 修饰,禁止继承。 内部存储数据的 char[] value 数组被 private final 修饰,且不提供修改方法(如 setter)。 所有修改操作(如 substring、replace)均返回新对象,原对象不变。 优势: 线程安全(无需同步锁)。 哈希值可缓存,提升性能。多线程场景下的应用使用 final 修饰变量可确保该变量的值在线程间可见且不可变,避免数据竞争问题。示例:        public class SafePublication {                private final int safeValue; // 安全发布,防止指令重排序                public SafePublication(int value) {                this.safeValue = value;        }}异常的理解异常是指在程序运行过程中可能发生的非正常事件,这类事件会中断程序的正常执行流程。通过异常处理机制,开发者可以捕获并处理这些异常,在保障程序核心功能不受致命影响的前提下完成错误恢复操作。异常的种类Java 的异常类继承体系以 Throwable 为顶层父类,具体分为两类:Error(错误)由 JVM 或底层系统资源引发的严重问题(如 StackOverflowError 栈溢出、OutOfMemoryError 内存耗尽),属于不可恢复的致命错误。Exception(异常)可被捕获处理的非致命性问题,进一步细分为:编译时异常(Checked Exception)编译器强制要求处理的异常类型,如 FileNotFoundException(文件未找到)、IOException(输入输出异常)。运行时异常(Runtime Exception)由代码逻辑缺陷引发的异常,编译器不强制处理,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(数组越界异常)。thow和throws的区别throw是在方法内部使用,进行抛出异常,是一个动作。throws是在方法声明的后面,表示可能会抛出的异常,是一种声明那么直接try-catch不就行了,为什么还要抛出异常呢?首先我们要分情况说明,如果该异常可以被try-catch处理的话,就可以直接try-catch,比如说虽然A方法抛出了一个异常,但是基于他异常处理的方法在其他地方,并不需要处理,不需要处理的情况下,就抛出异常给调用者。其次,当调用的几个方法中都存在相同的异常,如果在这几个方法内部进行try-catch的话,会使得整体变得冗余,可以让调用方统一进行捕获异常,并进行处理,简洁化。最后还有可能就是上层调用方处理String不可变类的核心优势线程安全:String的不可变性天然规避了多线程同步问题,无需额外锁机制即可保证线程安全。哈希缓存:首次计算哈希值后缓存,作为Map键时无需重复计算,显著提升查找效率(如HashMap的键比较场景)。字符串创建与常量池机制共享优化:常量池通过引用复用已存在的字符串,避免重复创建。例如:String s1 = "hello"; // 常量池新建对象(若不存在)String s2 = "hello"; // 直接引用常量池现有对象对象创建示例:String a = new String("aa") + "bb"; 的详细过程:常量池新建"aa"对象(若不存在)。new String("aa")在堆中创建新对象(非池引用)。常量池新建"bb"对象(若不存在)。拼接生成"aabb",堆中创建新对象并可能更新常量池(若未存在),共创建了四个对象。字符串存储与内存限制底层结构:使用char[]存储,理论最大长度为Integer.MAX_VALUE(2³¹-1)。实际限制:编译期:最大长度为2¹⁶-2(65534,受CONSTANT_Utf8_info限制)。运行时:受JVM可用内存限制(如堆大小)。字符串常量池的演进Java 7前:位于永久代(PermGen),易引发OOM。Java 8+:迁移至元空间(Metaspace),降低内存溢出风险。字符串拼接效率对比String的+操作:String result = "a" + "b"; // 编译后等效于:String result = new StringBuilder().append("a").append("b").toString();隐含创建StringBuilder和临时String对象,效率较低。StringBuffer:直接操作内部可变数组,避免频繁对象创建。线程安全(同步开销),适合多线程场景。StringBuilder(补充建议):单线程下更高效(非线程安全)。————————————————              原文链接:https://blog.csdn.net/tulingtuling/article/details/147128918
总条数:764 到第
上滑加载中