• [大赛资讯] 【区域初赛赛题问题】 本地编译通过,提交后编译失败
    本地可以正常运行,提交系统后报编译错误,能否帮忙排查~
  • [大赛资讯] 请问如何得知unexpected_elon出现的报错是内存超了还是程序超时了?
    请问如何得知unexpected_elon出现的报错是内存超了还是程序超时了?
  • [互动交流] 开始《Java智能助手设置》报错,不知道在哪设置
    命令"SmartAssist: Java智能助手设置"导致错误 (command 'java-smart-assist.settings.open' not found)
  • [大赛资讯] 咋改都是runtime_error, 做不了一点
    本地跑过数据不知道几次了,限制内存也能跑过,但是交上去就会 runtime_error。这是 debug 大赛吗 ?
  • [大赛资讯] 怎么该都是runtime_error
    为什么本地的两个数据都可以跑过,但是一交上去就runtime_error?
  • [技术干货] java的线程
    在 Java 中,线程是程序执行的最小单元。Java 提供了多线程编程的支持,允许你同时运行多个线程,从而提高程序的并发性和效率。每个线程都有自己的执行路径,可以独立执行任务。Java 中创建线程有两种主要方式:继承 Thread 类:通过继承 Thread 类并重写 run() 方法来定义线程的执行逻辑。实现 Runnable 接口:通过实现 Runnable 接口并将其传递给 Thread 类的构造函数来创建线程。实践示例1. 继承 Thread 类java复制class MyThread extends Thread { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } }}public class ThreadExample { public static void main(String[] args) { MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); // 启动线程 thread1.start(); thread2.start(); }}解释:MyThread 类继承了 Thread 类,并重写了 run() 方法。在 main 方法中,创建了两个 MyThread 对象 thread1 和 thread2,并调用 start() 方法启动线程。start() 方法会调用 run() 方法,线程开始执行。2. 实现 Runnable 接口java复制class MyRunnable implements Runnable { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } }}public class RunnableExample { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread1 = new Thread(myRunnable); Thread thread2 = new Thread(myRunnable); // 启动线程 thread1.start(); thread2.start(); }}解释:MyRunnable 类实现了 Runnable 接口,并实现了 run() 方法。在 main 方法中,创建了一个 MyRunnable 对象 myRunnable,然后将其传递给 Thread 类的构造函数,创建了两个线程 thread1 和 thread2。调用 start() 方法启动线程。线程的生命周期线程的生命周期包括以下几个状态:新建(New):线程对象被创建,但尚未启动。就绪(Runnable):线程已经启动,等待 CPU 调度执行。运行(Running):线程正在执行 run() 方法中的代码。阻塞(Blocked):线程因为某些原因(如等待资源)暂时停止执行。终止(Terminated):线程执行完毕或异常退出。线程的常用方法start():启动线程,使其进入就绪状态。run():线程执行的主体方法。sleep(long millis):使当前线程暂停执行指定的毫秒数。join():等待该线程终止。interrupt():中断线程。isAlive():检查线程是否处于活动状态。线程同步在多线程环境中,可能会出现多个线程同时访问共享资源的情况,导致数据不一致的问题。Java 提供了同步机制来解决这个问题,常用的同步方式有:synchronized 关键字:用于修饰方法或代码块,确保同一时间只有一个线程执行该代码。Lock 接口:提供了更灵活的锁机制。总结Java 线程是实现并发编程的基础。通过继承 Thread 类或实现 Runnable 接口,你可以创建并启动线程。理解线程的生命周期和同步机制对于编写高效、安全的多线程程序至关重要
  • [大赛资讯] java提交后一直显示runtime_error
    在本地跑两个样例都能过不报错,但是提交上去就报错。
  • [互动交流] CodeArts IDE For Java什么时候更新
    CodeArts IDE For Java 什么时候才能更新下一个版本呢?现在已经停留在2.3.0很久了,现在如果只运行一个工程的话问题不大,只不过单元测试那里有点问题。但是我们公司是那种一个总的模块,然后下面一堆子模块来一起开发的,然后这些模块的最上级都是同一个父工程的。但是因为每个人的负责的模块不同,像我是负责几个模块开发的。但是我们是没有最上级模块的代码权限的。如果我要用这个IDE来进行开发的话,那么我要开很多个窗口,然后同时如果我是有引用另外模块的api的话,那么我要将那个模块用maven进行打包,然后我当前模块也要重新install才能用到那个模块api里面的内容。这样的话会大大的降低开发的效率。这种情况有没有办法解决的?我试过将这些模块都拉到同一个窗口里面,但是这样的话他就无法正确的识别我的工程了。
  • [专题汇总] 2025年技术干货合集第二弹【2月份干货】
    25年的第二次合集来了,涵盖多方面内容,供大家阅读。1.解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题https://bbs.huaweicloud.com/forum/thread-0226176114797408069-1-1.html2.C 与 C++ 中的 const 常量与数组大小的关系对比分析【转】https://bbs.huaweicloud.com/forum/thread-0225176114407160080-1-1.html3. Java对象和JSON字符串之间的转换方法(全网最清晰)【转】https://bbs.huaweicloud.com/forum/thread-0218176114284451074-1-1.html4.Python中利用json库进行JSON数据处理详解【转】https://bbs.huaweicloud.com/forum/thread-0225176114218197079-1-1.html5.Python使用sys.path查看当前的模块搜索路径【转】https://bbs.huaweicloud.com/forum/thread-0225176047116377070-1-1.html6.PyTorch环境中CUDA版本冲突问题排查与解决方案【转】https://bbs.huaweicloud.com/forum/thread-0225176047065418069-1-1.html7.python 3.8 的anaconda下载方法【转】https://bbs.huaweicloud.com/forum/thread-02127176045871929063-1-1.html8.Python实现批量Excel拆分功能【转】https://bbs.huaweicloud.com/forum/thread-0218176045720424067-1-1.html9.Python中常用的四种取整方式分享【转】https://bbs.huaweicloud.com/forum/thread-0218176037491495065-1-1.html10.Python自动化处理手机验证码【转】https://bbs.huaweicloud.com/forum/thread-0210176037394123079-1-1.html11. Docker应用部署(Mysql、tomcat、Redis、redis)https://bbs.huaweicloud.com/forum/thread-0251176000289103057-1-1.html12.使用Python实现文件重命名的三种方法【转】https://bbs.huaweicloud.com/forum/thread-0210175967963879073-1-1.html13.python安装whl包并解决依赖关系的实现【转】https://bbs.huaweicloud.com/forum/thread-0226175967884244062-1-1.html14.Python轻松实现批量邮件自动化详解【转】https://bbs.huaweicloud.com/forum/thread-0251175967723301055-1-1.html15.Python脚本实现图片文件批量命名【转】https://bbs.huaweicloud.com/forum/thread-0226175967493359061-1-1.html16.Python中多线程和多进程的基本用法详解【转】https://bbs.huaweicloud.com/forum/thread-0251175967381425054-1-1.html17.鸿蒙NEXT开发案例:随机数生成https://bbs.huaweicloud.com/forum/thread-0226175856046738051-1-1.html18.鸿蒙NEXT开发案例:简体繁体转换器https://bbs.huaweicloud.com/forum/thread-0251175855906014051-1-1.html19.鸿蒙NEXT开发案例:血型遗传计算https://bbs.huaweicloud.com/forum/thread-0220175855724260049-1-1.html20.鸿蒙NEXT开发案例:数字转中文大小写https://bbs.huaweicloud.com/forum/thread-0225175850374977055-1-1.html 
  • [技术干货] 解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题【转】
    在 Spring 开发中,@Autowired 注解常用于实现依赖注入。它可以应用于类的 属性、构造器 或 setter 方法 上。然而,当 @Autowired 注解在 属性 上使用时,IntelliJ IDEA 等 IDE 会给出 Field injection is not recommended 的警告,而在 setter 方法 上使用 @Autowired 时却不会出现这个警告。1. 为什么 @Autowired 在属性上被警告?1.1 隐式依赖注入当 @Autowired 注解应用于类的 属性 上时,Spring 会直接注入该属性,而不通过构造函数或 setter 方法显式地传递依赖项。这种注入方式称为 字段注入(Field Injection)。字段注入 的缺点主要体现在以下几个方面:1.隐式依赖:通过字段注入,类的依赖关系是隐式的,无法在类的构造器或方法中显式地看到这些依赖。相对而言,构造器注入 和 setter 注入 可以使依赖关系更加明确。由于字段依赖是隐式注入的,开发者很难在不查看容器配置的情况下,快速了解一个类的所有依赖项。2.难以进行单元测试:字段注入的属性是隐式注入的,无法通过构造函数或 setter 方法显式传递。在单元测试中,手动注入模拟(mock)对象时,需要通过反射或者测试框架自动注入,这增加了测试的复杂性。与此相比,构造器注入和 setter 注入会使依赖关系显式可见,能够更方便地进行 单元测试。3.违反依赖倒置原则(DIP):在 依赖倒置原则 中,依赖关系应该通过 接口 或 抽象 进行注入,而不应该在类内部直接依赖于具体的实现。字段注入使得类的依赖更加隐式,可能会增加代码的耦合性。1.2 IDE 的警告:Field injection is not recommendedIntelliJ IDEA 等 IDE 会根据这些设计缺点发出警告,提示 @Autowired 注解不推荐使用在属性上。字段注入的方式可能会导致代码的可维护性差,容易出现一些潜在问题(如不清晰的依赖关系和难以测试的代码)。2. 为什么 @Autowired 在 setter 方法上不被警告?当 @Autowired 用于 setter 方法 时,Spring 会通过 setter 注入 方式将依赖项注入到对象的属性中。与字段注入不同,setter 注入方式具有以下优势:2.1 显式依赖注入显式依赖关系:使用 setter 方法注入,开发者可以明确看到类所依赖的组件。通过查看类的 setter 方法,其他开发者可以轻松理解该类的依赖关系。12345678public class MyService {    private MyRepository repository;         @Autowired    public void setRepository(MyRepository repository) {        this.repository = repository;    }}符合依赖注入的设计原则:通过构造函数或 setter 方法注入依赖项,可以使类的依赖关系更加清晰,符合面向对象设计中的 依赖注入 和 单一职责原则。2.2 可选的依赖注入setter 注入适用于一些 可选依赖 的场景。如果某个依赖是可选的,可以通过 setter 方法来灵活注入,而不需要在构造器中强制要求依赖项的传入。1234@Autowiredpublic void setOptionalDependency(Optional<Dependency> dependency) {    this.dependency = dependency.orElse(null);}2.3 易于测试由于 setter 方法可以手动设置对象的依赖,因此它可以使单元测试变得更简单。你可以通过 setter 方法为对象注入模拟(mock)依赖项,而不需要通过反射等复杂手段。12MyService myService = new MyService();myService.setRepository(mockRepository);3. 构造器注入 vs 字段注入 vs Setter 注入3.1 构造器注入(推荐)构造器注入 是 最推荐的依赖注入方式,它具有以下优势:强制依赖关系:通过构造器传递依赖项,可以确保所有的依赖项在对象创建时就已经被正确地注入。不可变性:构造器注入使得依赖项在对象创建时就被初始化,避免了运行时更改依赖项。易于测试:构造器注入使得所有的依赖项在构造时就显式提供,便于进行单元测试。12345678public class MyService {    private final MyRepository repository;         @Autowired    public MyService(MyRepository repository) {        this.repository = repository;    }}3.2 Setter 注入(次推荐)Setter 注入 是一个灵活的选择,适用于依赖关系较为可选或后期可更改的场景。它具有以下特点:灵活性:可以在对象创建后修改依赖项。适用于可选依赖:如果某些依赖项是可选的,setter 注入能够方便地管理。12345678public class MyService {    private MyRepository repository;     @Autowired    public void setRepository(MyRepository repository) {        this.repository = repository;    }}3.3 字段注入(不推荐)字段注入 是最简单的注入方式,但并不推荐使用,原因已在前面提到。字段注入具有以下缺点:不清晰的依赖关系:依赖项通过字段注入,难以通过构造器或 setter 明确看到类的依赖。难以测试:无法通过构造函数直接注入模拟对象,增加了单元测试的难度。1234public class MyService {    @Autowired    private MyRepository repository;}4. 总结字段注入不推荐,因为它将依赖关系隐藏在字段中,难以清晰表达依赖项,增加了测试的复杂性。推荐使用构造器注入,它提供了最强的类型安全性和不可变性,增强了代码的可维护性和测试性。Setter 注入适用于可选依赖,但在依赖较多时容易导致依赖关系变得模糊,因此需要谨慎使用。总体而言,使用构造器注入和 setter 注入能够使代码更清晰、易于维护,同时支持更好的单元测试。如果 IDE 提示 Field injection is not recommended,这意味着你可以考虑改用构造器注入或 setter 注入,以便提升代码质量。
  • [技术干货] Java对象和JSON字符串之间的转换方法(全网最清晰)【转】
    前言在 Java 中,将对象转换为 JSON 字符串通常使用一些流行的 JSON 库,如 Jackson 或 Gson。这两个库都非常强大,支持将 Java 对象转换为 JSON 字符串,也支持反向操作。接下来我会介绍一个基于 Jackson 的工具类,它可以非常方便地实现 Java 对象和 JSON 字符串之间的相互转换。1. 引入 Jackson 依赖首先,确保你的 pom.xml 文件中引入了 Jackson 相关依赖:12345678<dependencies>    <!-- Jackson 核心库 -->    <dependency>        <groupId>com.fasterxml.jackson.core</groupId>        <artifactId>jackson-databind</artifactId>        <version>2.15.2</version> <!-- 使用合适的版本 -->    </dependency></dependencies> 2. 创建 JSON 工具类以下是一个简单的 Jackson 工具类,实现了 Java 对象和 JSON 字符串之间的相互转换,并支持异常处理。 12345678910111213141516171819202122232425262728import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.core.JsonProcessingException; public class JsonUtils {     // 创建一个 ObjectMapper 实例,用于转换对象    private static final ObjectMapper objectMapper = new ObjectMapper();     // 将对象转换为 JSON 字符串    public static String toJson(Object object) {        try {            return objectMapper.writeValueAsString(object);  // 使用 ObjectMapper 将对象转成 JSON        } catch (JsonProcessingException e) {            e.printStackTrace();  // 打印异常            return null;  // 返回 null 或者可以抛出自定义异常        }    }     // 将 JSON 字符串转换为对象    public static <T> T fromJson(String jsonString, Class<T> valueType) {        try {            return objectMapper.readValue(jsonString, valueType);  // 使用 ObjectMapper 将 JSON 转回对象        } catch (JsonProcessingException e) {            e.printStackTrace();  // 打印异常            return null;  // 返回 null 或者可以抛出自定义异常        }    }} 3. 使用示例转换 Java 对象为 JSON 字符串假设你有一个 Java 类 Person,并希望将其转换为 JSON 字符串。然后你可以使用上述的这个 JsonUtils 工具类来将 Person 对象转换为 JSON 字符串: 12345678910public class Main {    public static void main(String[] args) {        Person person = new Person("Alice", 30);                 // 将对象转换为 JSON 字符串        String json = JsonUtils.toJson(person);        System.out.println(json);        // 输出: {"name":"Alice","age":30}    }} 将 JSON 字符串转换回 Java 对象12345678910public class Main {    public static void main(String[] args) {        String json = "{\"name\":\"Alice\",\"age\":30}";                 // 将 JSON 字符串转换为 Person 对象        Person person = JsonUtils.fromJson(json, Person.class);        System.out.println(person.getName());  // 输出: Alice        System.out.println(person.getAge());   // 输出: 30    }} 4. 扩展:自定义序列化和反序列化Jackson 提供了强大的自定义序列化和反序列化功能。如果你有特殊的需求,可以通过注解或自定义 Serializer 和 Deserializer 来实现。例如,假设你想控制 Person 对象的 JSON 输出格式,可以使用 @JsonFormat 注解:12345678910import com.fasterxml.jackson.annotation.JsonFormat; public class Person {    private String name;         @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")    private int age;     // 构造器,getter 和 setter} 这样,你可以根据需要灵活地调整 JSON 的生成方式。总结通过使用 Jackson 的 ObjectMapper,你可以轻松地将 Java 对象和 JSON 字符串之间进行转换。上述这个工具类提供了基本的功能,也支持更多复杂的自定义配置,适应不同的需求。 
  • [技术干货] 使用 Java 和 FreeMarker 实现自动生成供货清单,动态生成 Word 文档,简化文档处理流程
    引言在电缆行业,生成供货清单是一项常见但繁琐的任务。本教程将介绍如何使用现代Java技术栈自动化这一过程,大幅提高工作效率和准确性。我们将使用SpringBoot作为框架,Apache POI处理Word文档,以及FreeMarker作为模板引擎来实现这一功能!让我们先了解一下这个问题的背景:在电缆行业,手动创建供货清单是一个复杂且重复的过程。这个过程不仅耗时,还容易出错,影响工作效率和数据准确性。为了解决这个问题,我们提出了一个技术方案,结合了以下几个关键技术:SpringBoot: 作为我们的主要开发框架Apache POI: 用于生成和操作Word文档FreeMarker模板引擎: 用于生成Word文件的内容这个方案的主要优势包括:灵活性: 使用FreeMarker模板可以轻松调整文档格式,而无需修改程序代码。效率: 自动化生成过程大大减少了人工操作,提高了办公效率。准确性: 自动化处理确保了数据的准确性和一致性。适用性: 特别适合电缆行业的业务需求,生成符合要求的.doc文件。通过阅读这篇博客,您将学习如何实现这个解决方案,从而帮助您或您的团队简化工作流程,提高生产效率。效果图: 项目结构src/├── main/│   ├── java/│   │   └── com/│   │       └── pw/│   │           ├── WordController.java  #负责生成测试数据并调用WordUtil工具类来生成Word文档│   │           └── utils/│   │               └── WordUtil.java  #这个工具类封装了使用FreeMarker生成Word文档的核心功能│   └── resources/│       └── templates/│           └── template.ftl #模版定义了Word文档的结构和样式,使用HTML和CSS来格式化内容1.WordController类:这个类是我们应用的入口点,负责生成测试数据并调用WordUtil来生成Word文档。2.WordUtil类:这个工具类封装了使用FreeMarker生成Word文档的核心逻辑。3.FreeMarker模版(template.ftl):这个模版定义了Word文档的结构和样式,使用HTML和CSS来格式化内容。源代码展示1.WordControllerimport com.pw.utils.WordUtil; import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; public class WordController {     public static void main(String[] args) throws IOException {        // 指定保存Word文件的目录        String filePath = "F:\\Poi2Word\\src\\main\\resources\\output"; // 更改为您希望的目录        new WordController().generateWordFile(filePath);    }     public void generateWordFile(String directory) throws IOException {        List<Map<String, Object>> listMap = new ArrayList<>();         //测试数据        addTestData(listMap, "4600025747", "绝缘导线", "AC10kV,JKLYJ,300", 1500, "米", "盘号:A1");        addTestData(listMap, "4600025748", "绝缘导线", "AC10kV,JKLGYJ,150/30", 2500, "米", "盘号:A2");        addTestData(listMap, "4600025749", "绝缘导线", "AC10kV,JKLGYJ,150/30", 3500, "米", "盘号:A3");        addTestData(listMap, "4600025750", "绝缘导线", "AC10kV,JKLGYJ,150/30", 4500, "米", "盘号:A4");        addTestData(listMap, "4600025751", "绝缘导线", "AC10kV,JKLGYJ,150/30", 3800, "米", "盘号:A5");        addTestData(listMap, "4600025752", "绝缘导线", "AC10kV,JKLYJ,180", 2000, "米", "盘号:A6");        addTestData(listMap, "4600025753", "绝缘导线", "AC10kV,JKLYJ,120", 4200, "米", "盘号:A7");        addTestData(listMap, "4600025754", "绝缘导线", "AC10kV,JKLYJ,120", 3700, "米", "盘号:A8");        addTestData(listMap, "4600025755", "绝缘导线", "AC10kV,JKLYJ,120", 4300, "米", "盘号:A9");        addTestData(listMap, "4600025756", "绝缘导线", "AC10kV,JKLGYJ,100/20", 2800, "米", "盘号:A10");        addTestData(listMap, "4600025757", "绝缘导线", "AC10kV,JKLGYJ,100/20", 2400, "米", "盘号:A11");        addTestData(listMap, "4600025758", "绝缘导线", "AC10kV,JKLGYJ,100/20", 2600, "米", "盘号:A12");         HashMap<String, Object> map = new HashMap<>();        map.put("qdList", listMap);  // 添加供货清单数据        map.put("contacts", "张三");  // 联系人        map.put("contactsPhone", "13988887777");  // 联系电话        map.put("date", "2025年01月18日");  // 日期        map.put("company", "新电缆科技有限公司");  // 公司名称        map.put("customer", "国网北京市电力公司");  // 客户         String wordName = "template.ftl"; // FreeMarker模板文件名        String fileName = "供货清单" + System.currentTimeMillis() + ".doc"; // 带时间戳的文件名        String name = "name";  // 临时文件名         // 确保输出目录存在        File directoryFile = new File(directory);        if (!directoryFile.exists()) {            directoryFile.mkdirs();  // 如果目录不存在则创建        }         // 生成Word文件        WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);         System.out.println("文件成功生成在:" + directory + fileName);    }     private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {        Map<String, Object> item = new HashMap<>();        item.put("serNo", listMap.size() + 1);  // 序号        item.put("danhao", danhao);  // 单号        item.put("name", name);  // 产品名称        item.put("model", model);  // 规格型号        item.put("num", String.valueOf(num));  // 数量,转换为字符串        item.put("unit", unit);  // 单位        item.put("remark", remark);  // 备注        listMap.add(item);  // 将数据添加到列表    }}2.WordUtil工具类package com.pw.utils; import freemarker.template.Configuration;import freemarker.template.Template; import java.io.*;import java.util.Map; public class WordUtil {    private static Configuration configuration = null;     // 模板文件夹路径    private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();     static {        configuration = new Configuration();        configuration.setDefaultEncoding("utf-8");        try {            System.out.println(templateFolder);            configuration.setDirectoryForTemplateLoading(new File(templateFolder));  // 设置模板加载路径        } catch (IOException e) {            e.printStackTrace();        }    }     private WordUtil() {        throw new AssertionError();  // 防止实例化    }     /**     * 导出Word文档     * @param map Word文档中参数     * @param wordName 模板的名字,例如xxx.ftl     * @param fileName Word文件的名字 格式为:"xxxx.doc"     * @param outputDirectory 输出文件的目录路径     * @param name 临时的文件夹名称,作为Word文件生成的标识     * @throws IOException     */    public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {        Template freemarkerTemplate = configuration.getTemplate(wordName);  // 获取模板文件        File file = null;        try {            // 调用工具类的createDoc方法生成Word文档            file = createDoc(map, freemarkerTemplate, name);             // 确保输出目录存在            File dir = new File(outputDirectory);            if (!dir.exists()) {                dir.mkdirs();  // 如果目录不存在则创建            }             // 定义完整的文件路径            File outputFile = new File(outputDirectory, fileName);             // 重命名并移动文件到指定目录            file.renameTo(outputFile);             System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());        } finally {            if (file != null && file.exists()) {                file.delete();  // 删除临时文件            }        }    }     private static File createDoc(Map<?, ?> dataMap, Template template, String name) {        File f = new File(name);        try {            // 使用OutputStreamWriter来指定编码,防止特殊字符出问题            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");            template.process(dataMap, w);  // 使用FreeMarker处理模板            w.close();        } catch (Exception ex) {            ex.printStackTrace();            throw new RuntimeException(ex);        }        return f;  // 返回生成的文件    }}3.FreeMarker模版<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>${company}送货清单</title>    <style>        body { font-family: SimSun, serif; }  <!-- 设置字体 -->        table { border-collapse: collapse; width: 100%; }  <!-- 设置表格样式 -->        th, td { border: 1px solid black; padding: 5px; text-align: center; }  <!-- 设置表格的单元格样式 -->        th { background-color: #f2f2f2; }  <!-- 设置表头背景色 -->        .subtotal { font-weight: bold; }  <!-- 小计行加粗 -->        .total { font-weight: bold; font-size: 1.1em; }  <!-- 总计行加粗并设置字体大小 -->    </style></head><body><h1 style="text-align: center;">${company}送货清单</h1>  <!-- 顶部公司名称 --> <table>    <tr>        <th>序号</th>  <!-- 表头:序号 -->        <th>供货单号</th>  <!-- 表头:供货单号 -->        <th>产品名称</th>  <!-- 表头:产品名称 -->        <th>规格型号</th>  <!-- 表头:规格型号 -->        <th>数量</th>  <!-- 表头:数量 -->        <th>单位</th>  <!-- 表头:单位 -->        <th>备注</th>  <!-- 表头:备注 -->    </tr>    <#assign totalQuantity = 0>  <!-- 总数量初始化 -->    <#assign totalItems = 0>  <!-- 总项数初始化 -->    <#assign sortedList = qdList?sort_by("model")>  <!-- 按照规格型号排序 -->    <#assign currentModel = "">  <!-- 当前型号初始化 -->    <#assign subtotalQuantity = 0>  <!-- 小计数量初始化 -->    <#assign subtotalItems = 0>  <!-- 小计项数初始化 -->    <#list sortedList as item>  <!-- 遍历排序后的列表 -->        <#if item.model != currentModel>  <!-- 如果规格型号变了 -->            <#if currentModel != "">  <!-- 如果当前规格型号不是空 -->                <tr class="subtotal">                    <td colspan="4">小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴</td>                    <td>${subtotalQuantity}</td>                    <td>${sortedList[0].unit}</td>                    <td></td>                </tr>            </#if>            <#assign currentModel = item.model>  <!-- 更新当前型号 -->            <#assign subtotalQuantity = 0>  <!-- 重置小计数量 -->            <#assign subtotalItems = 0>  <!-- 重置小计项数 -->        </#if>        <tr>            <td>${item?counter}</td>  <!-- 序号 -->            <td>${item.danhao}</td>  <!-- 单号 -->            <td>${item.name}</td>  <!-- 产品名称 -->            <td>${item.model}</td>  <!-- 规格型号 -->            <td>${item.num}</td>  <!-- 数量 -->            <td>${item.unit}</td>  <!-- 单位 -->            <td>${item.remark}</td>  <!-- 备注 -->        </tr>        <#assign itemNum = item.num?replace(",", "")?number>  <!-- 将数量转为数字并处理逗号 -->        <#assign subtotalQuantity = subtotalQuantity + itemNum>  <!-- 累加小计数量 -->        <#assign subtotalItems = subtotalItems + 1>  <!-- 累加小计项数 -->        <#assign totalQuantity = totalQuantity + itemNum>  <!-- 累加总数量 -->        <#assign totalItems = totalItems + 1>  <!-- 累加总项数 -->    </#list>    <#if currentModel != "">  <!-- 如果当前规格型号不是空 -->        <tr class="subtotal">            <td colspan="4">小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴</td>            <td>${subtotalQuantity}</td>            <td>${sortedList[0].unit}</td>            <td></td>        </tr>    </#if>    <tr class="total">        <td colspan="4">合计:${totalQuantity}${qdList[0].unit} ${totalItems}轴</td>        <td>${totalQuantity}</td>        <td>${qdList[0].unit}</td>        <td></td>    </tr></table> <p>发货联系人:${contacts}</p>  <!-- 发货联系人 --><p>联系电话:${contactsPhone}</p>  <!-- 联系电话 --><p>日期:${date}</p>  <!-- 日期 --> <p style="text-align: right;">收货人(签字):_______________</p>  <!-- 收货人签字 --><p style="text-align: right;">联系电话:_______________</p>  <!-- 收货人联系电话 --><p style="text-align: right;">${customer}</p>  <!-- 客户 --></body></html>4.POM依赖<!-- freemarker依赖,用于模板引擎,方便进行页面的渲染和数据的展示等操作 --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-freemarker</artifactId></dependency><!-- Apache POI 的核心依赖,用于操作 Microsoft Office 格式的文档,如 Excel、Word 等文件 --><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi</artifactId>    <version>5.0.0</version></dependency><!-- Apache POI 的 OOXML 扩展依赖,主要用于处理 Office 2007 及以后版本的 OOXML 格式的文件,例如.xlsx 等 --><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>5.0.0</version></dependency><!-- OOXML 模式相关的依赖,提供了对 OOXML 文档结构和内容模式的支持,有助于 Apache POI 更好地操作 OOXML 格式文件 --><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>ooxml-schemas</artifactId>    <version>1.4</version></dependency>WordController类深度解析WordController类是整个应用的核心控制器,负责协调数据生成和文档创建的过程。让我们逐步分析它的主要组成部分:1.类结构public class WordController {    // 方法定义...}这个类没有继承任何其他类,也没有实现任何接口,是一个独立的控制器类。2.main方法public static void main(String[] args) throws IOException {    String filePath = "F:\\Poi2Word\\src\\main\\resources\\output";    new WordController().generateWordFile(filePath);}这是应用的入口点。它设置了输出文件的路径,然后调用generateWordFile方法。请注意:在常规的 Spring Boot 实际应用场景下,我们一般不会直接在控制器类中使用 main 方法。此处之所以将 main 方法置于控制器中,纯粹是出于演示目的,旨在让相关流程更加直观易懂。而当进入到正式开发环节时,有几个关键要点务必落实:其一,需要引入数据库集成功能,将当前所使用的测试数据全面替换为从数据库中精准查询获取的真实数据,以此确保数据的准确性与时效性;其二,要对控制器进行优化改造,摒弃现有的演示模式,将其转换为遵循标准规范的请求接口实现方式,进而满足实际业务需求,提升系统的稳定性与可扩展性。3.generateWordFile方法此方法的只要目的是生成Word文件,首先需要先收集和存储测试数据,存储表格数据是将一条数据存储在Map集合中,再将每一条数据存储到List集合中。将其他数据存储到单独的一个Map集合中。然后确保输出目录存在,最后调用WordUtil中的exportMillCertificateWord方法生成文件,并输出文件的生成位置。// 生成 Word 文件的方法public void generateWordFile(String directory) throws IOException {    // 存储测试数据的列表,每个元素都是一个 Map,存储了具体的信息    List<Map<String, Object>> listMap = new ArrayList<>();     // 添加测试数据,调用 addTestData 方法添加一条记录    addTestData(listMap, "4600025747", "绝缘导线", "AC10kV,JKLYJ,300", 1500, "米", "盘号:A1");    //... 可以继续调用 addTestData 方法添加更多测试数据...     // 存储最终要填充到 Word 模板的数据的 Map,包含各种信息    HashMap<String, Object> map = new HashMap<>();    // 将测试数据列表添加到 map 中,键为 "qdList"    map.put("qdList", listMap);    // 联系人信息    map.put("contacts", "张三");    // 联系人电话    map.put("contactsPhone", "13988887777");    // 日期信息    map.put("date", "2025年01月18日");    // 公司名称    map.put("company", "新电缆科技有限公司");    // 客户名称    map.put("customer", "国网北京市电力公司");     // Word 模板文件的名称    String wordName = "template.ftl";    // 生成的 Word 文件的名称,使用当前时间戳保证文件名的唯一性    String fileName = "供货清单" + System.currentTimeMillis() + ".doc";    // 名称信息,具体含义可能根据实际情况而定    String name = "name";     // 创建一个文件对象,用于表示输出目录    File directoryFile = new File(directory);    // 检查输出目录是否存在,如果不存在则创建目录    if (!directoryFile.exists()) {        directoryFile.mkdirs();    }     // 调用 WordUtil 的 exportMillCertificateWord 方法生成 Word 文件    // 传入目录、数据 Map、模板名称、生成的文件名称和名称信息    WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);     // 打印生成文件的成功信息    System.out.println("文件成功生成在:" + directory + fileName);}这个方法完成以下任务:创建一个一个List<Map<String,Object>>集合来存储供货清单数据使用addTestData方法添加多条测试数据创建一个Map集合来存储企业名称,发货联系人,联系电话等信息确保输出目录存在调用WordUtil.exportMillCertificateWord方法来生成Word文档4.addTestData方法这个方法用于创建单个供货项目的数据// 添加一条测试数据到 listMap 中private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {    // 创建一个新的 HashMap,用于存储每一条数据    Map<String, Object> item = new HashMap<>();        // 将数据项依次放入 HashMap 中,"serNo" 表示序号,使用 listMap 的大小+1 生成序号    item.put("serNo", listMap.size() + 1);  // 序号是当前列表的大小 + 1    item.put("danhao", danhao);  // 供货单号    item.put("name", name);  // 产品名称    item.put("model", model);  // 规格型号    item.put("num", String.valueOf(num));  // 数量,将整数转为字符串    item.put("unit", unit);  // 单位    item.put("remark", remark);  // 备注        // 将该条数据项添加到 listMap 列表中    listMap.add(item);}这个方法完成以下任务:它接收多个参数,代表一个供货项目的各个属性。创建一个新的Map来存储这个项目的数据。自动计算序号(serNo)基于当前列表的大小。将所有数据添加到Map中。将这个Map添加到供货清单列表中。WordUtil类深度解析WordUtil类是整个文档生成过程的核心,它封装了FreeMarker模板引擎的配置和使用逻辑。让我们逐步分析它的主要组成部分:1.类结构和静态成员public class WordUtil {    private static Configuration configuration = null;    private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();        // 其他方法...}configuration:这是FreeMarker的核心配置对象,用于设置模版加载路径。templateFolder:定义了模版文件的存储路径。使用getResource()方法确保在不同环境下都能正确找到模版文件。2.静态初始化块这段代码的作用是初始化FreeMarker的Configuration对象,设置模版加载目录以及编码格式,以便FreeMarker后续能够正确加载和处理模版文件。// 静态初始化块,用于初始化 FreeMarker 配置static {    // 创建一个 FreeMarker 配置对象,用于后续模板处理    configuration = new Configuration();        // 设置 FreeMarker 配置对象的默认编码为 "utf-8"    configuration.setDefaultEncoding("utf-8");        try {        // 输出模板文件夹路径,帮助调试        System.out.println(templateFolder);                // 设置模板加载目录为 templateFolder 指定的路径,模板文件会从该目录加载        configuration.setDirectoryForTemplateLoading(new File(templateFolder));    } catch (IOException e) {        // 如果加载模板目录时出现异常,打印错误堆栈信息        e.printStackTrace();    }}这个静态初始化块在类加载时执行,主要完成以下任务:创建FreeMarker的Configuration对象设置默认编码为UTF-8,确保正确处理中文等字符设置模版加载目录,这样FreeMarker就知道从哪里查找加载模版文件了错误处理:如果执行过程中出现了IO异常,就会打印堆栈跟踪3.私有构造函数这个构造函数防止类被实例化,确保WordUtil只能通过其静态方法使用。private WordUtil() {    throw new AssertionError();}私有构造函数的好处包括:防止类被实例化当类的构造函数被声明为private时,外部代码无法直接创建该类的实例。这就意味着该类只能公国静态方法访问,确保类的功能是全局共享的。实现单例模式的基础在一些设计模式中,例如单例模式,类只允许有一个实例,私有构造函数确保了这一点。通过private构造函数,我们可以控制类的实例化过程,并确保只有一个实例被创建。封装类的内部实现私有构造函数可以帮助隐藏类的具体实现细节,外部代码不需要关心如何创建类的实例,只需要使用类提供的静态方法即可。这增加了类的封装性,降低了与外部代码的耦合度。避免多余的对象创建由于无法实例化类,每次调用静态方法时,都会使用已有的类实例,这可以避免无意义的对象创建,节省内存和资源。4.exportMillCertificateWord方法这个方法的主要功能是通过加载指定的 FreeMarker 模板生成一个临时的 Word 文档,确保输出目录存在后,将临时文件重命名并保存到指定的位置,同时在过程结束后清理临时文件,并打印文件生成的成功消息。// 导出 Word 文档的方法public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {    // 获取 FreeMarker 模板文件    Template freemarkerTemplate = configuration.getTemplate(wordName);    // 初始化一个 File 对象,用于存储生成的临时文件    File file = null;        try {        // 使用模板和数据创建 Word 文档,返回临时文件        file = createDoc(map, freemarkerTemplate, name);                // 创建目标目录的 File 对象        File dir = new File(outputDirectory);                // 如果目录不存在,则创建该目录        if (!dir.exists()) {            dir.mkdirs();  // 创建目录及其父目录        }                // 定义最终输出文件的完整路径(包括目录和文件名)        File outputFile = new File(outputDirectory, fileName);                // 将临时生成的文件重命名为目标文件,并将其移动到指定目录        file.renameTo(outputFile);                // 打印输出文件的绝对路径,a通知文件生成成功        System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());    } finally {        // 最后,无论是否成功生成文件,都确保临时文件被删除        if (file != null && file.exists()) {            file.delete();  // 删除临时文件        }    }}这个方法是文档导出的主要入口,主要实现了以下功能:加载指定的FreeMarker模版调用createDoc方法生成临时文档文件确保输出目录存在将临时文件重命名并移动到指定的输出位置使用finally块确保临时文件被删除,无论过程是否成功5.createDoc方法这个方法是创建文档的核心方法,主要是通过创建一个临时文件,使用指定的FreeMarker模版和数据模型将内容填充到文件中,并确保文件使用UTF-8编码进行写入。该方法在执行过程中捕获异常并打印堆栈信息,确保发生错误时能够正确处理。最后。方法返回生成的文件对象,以便后续操作或保存。// 创建文档的方法,使用 FreeMarker 模板生成内容并写入文件private static File createDoc(Map<?, ?> dataMap, Template template, String name) {    // 创建一个新的 File 对象,表示生成的文档文件,文件名由参数 "name" 提供    File f = new File(name);        try {        // 使用 OutputStreamWriter 创建一个写入文件的 Writer 对象,设置编码为 "utf-8"        Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");                // 使用 FreeMarker 模板将数据填充到文件中        template.process(dataMap, w);                // 关闭 Writer,确保所有内容写入文件        w.close();    } catch (Exception ex) {        // 捕获异常并打印错误堆栈信息        ex.printStackTrace();                // 抛出 RuntimeException,确保错误被传播到调用者        throw new RuntimeException(ex);    }        // 返回生成的文件对象    return f;}这个方法是实际创建文档的核心,主要实现以下功能:创建一个临时文件。使用OutputStreamWriter设置UTF-8编码,确保正确处理所有字符。调用FreeMarker的template.process()方法,将数据模型(dataMap)应用到模板上。关闭写入器。如果过程中发生异常,打印堆栈跟踪并抛出RuntimeException。返回生成的文件对象。6.WordUtil类总结WordUtil 类通过封装 FreeMarker 模板引擎的配置和文件操作,提供了一个简洁的文档生成工具。它加载指定模板,使用数据模型填充内容,创建临时文件,并确保文件按照指定路径保存。该类通过静态方法确保全局共享功能,使用 UTF-8 编码处理字符,捕获异常并清理临时文件,确保文档生成过程的稳定性和高效性。FreeMarker模板深度解析FreeMarker模板是整个文档生成过程的核心,它定义了最终Word文档的结构和样式。让我们来逐步分析模板的主要组成部分1.文档结构和样式<!DOCTYPE html> <!-- 声明文档类型为 HTML5 --><html><head>    <!-- 设置文档字符编码为 UTF-8,支持中文和其他字符集 -->    <meta charset="UTF-8">    <!-- 设置页面标题,动态插入公司名称 -->    <title>${company}送货清单</title>    <style>        /* 设置页面正文的字体为 SimSun(宋体),如果没有则使用 serif */        body { font-family: SimSun, serif; }         /* 设置表格样式:表格边框合并,宽度100% */        table { border-collapse: collapse; width: 100%; }         /* 设置表格头部和单元格的边框、内边距和文本居中对齐 */        th, td { border: 1px solid black; padding: 5px; text-align: center; }         /* 设置表头背景色为浅灰色 */        th { background-color: #f2f2f2; }         /* 设置小计行字体加粗 */        .subtotal { font-weight: bold; }         /* 设置合计行字体加粗,字体大小稍大 */        .total { font-weight: bold; font-size: 1.1em; }    </style></head><body>    <!-- 页面标题,居中显示公司名称和送货清单 -->    <h1 style="text-align: center;">${company}送货清单</h1>    <!-- 表格内容将在这里生成,动态插入数据 --></body></html>这段代码通过HTML和内嵌CSS定义了页面布局和样式:动态公司名称:<title>标签使用${company}插入动态的公司名称,显示在浏览器标签中。字体和表格样式:设置页面字体为宋体(Simsun)定义表格边框合并、100%宽度,并使单元格内容居中小计和总计行样式:为小计行加粗字体,并为总计行加粗且增大字体,突出显示重要数据。2.表格结构和动态数据插入<table>    <!-- 表头,定义表格的列名 -->    <tr>        <th>序号</th>  <!-- 序号 -->        <th>供货单号</th>  <!-- 供货单号 -->        <th>产品名称</th>  <!-- 产品名称 -->        <th>规格型号</th>  <!-- 规格型号 -->        <th>数量</th>  <!-- 数量 -->        <th>单位</th>  <!-- 单位 -->        <th>备注</th>  <!-- 备注 -->    </tr>     <!-- 初始化总计和小计相关变量 -->    <#assign totalQuantity = 0>  <!-- 总数量 -->    <#assign totalItems = 0>  <!-- 总项数 -->    <#assign sortedList = qdList?sort_by("model")>  <!-- 按照规格型号对数据进行排序 -->    <#assign currentModel = "">  <!-- 当前规格型号 -->    <#assign subtotalQuantity = 0>  <!-- 小计数量 -->    <#assign subtotalItems = 0>  <!-- 小计项数 -->     <!-- 遍历排序后的列表 -->    <#list sortedList as item>        <!-- 如果当前项的规格型号与上一项不同,则输出上一项的小计 -->        <#if item.model != currentModel>            <#if currentModel != "">                <!-- 输出上一规格型号的小计行 -->                <tr class="subtotal">                    <td colspan="4">小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴</td>                    <td>${subtotalQuantity}</td>                    <td>${sortedList[0].unit}</td>                    <td></td>                </tr>            </#if>            <!-- 更新当前规格型号为当前项的规格型号,并重置小计 -->            <#assign currentModel = item.model>            <#assign subtotalQuantity = 0>            <#assign subtotalItems = 0>        </#if>         <!-- 输出当前行数据 -->        <tr>            <td>${item?counter}</td>  <!-- 序号,使用 FreeMarker 的 counter 计数 -->            <td>${item.danhao}</td>  <!-- 供货单号 -->            <td>${item.name}</td>  <!-- 产品名称 -->            <td>${item.model}</td>  <!-- 规格型号 -->            <td>${item.num}</td>  <!-- 数量 -->            <td>${item.unit}</td>  <!-- 单位 -->            <td>${item.remark}</td>  <!-- 备注 -->        </tr>         <!-- 更新小计和总计的数量和项数 -->        <#assign itemNum = item.num?replace(",", "")?number>  <!-- 将数量转为数字并处理逗号 -->        <#assign subtotalQuantity = subtotalQuantity + itemNum>  <!-- 累加小计数量 -->        <#assign subtotalItems = subtotalItems + 1>  <!-- 累加小计项数 -->        <#assign totalQuantity = totalQuantity + itemNum>  <!-- 累加总数量 -->        <#assign totalItems = totalItems + 1>  <!-- 累加总项数 -->    </#list>     <!-- 如果最后一项有数据,输出最后的规格型号小计 -->    <#if currentModel != "">        <tr class="subtotal">            <td colspan="4">小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴</td>            <td>${subtotalQuantity}</td>            <td>${sortedList[0].unit}</td>            <td></td>        </tr>    </#if>     <!-- 输出最终的合计行 -->    <tr class="total">        <td colspan="4">合计:${totalQuantity}${qdList[0].unit} ${totalItems}轴</td>  <!-- 显示合计的数量和项数 -->        <td>${totalQuantity}</td>  <!-- 合计数量 -->        <td>${qdList[0].unit}</td>  <!-- 单位 -->        <td></td>    </tr></table>表格结构:使用 <table> 标签创建表格,并通过 <th> 定义表头,包含7列:序号、供货单号、产品名称等。动态数据插入:使用 FreeMarker <#list> 遍历排序后的清单数据,并通过 ${item.属性名} 动态插入每项数据,如 ${item.danhao} 插入供货单号。小计和总计计算:通过 <#assign> 定义变量如 totalQuantity 和 subtotalQuantity,在循环中累加数量。使用 <#if> 判断条件,插入小计行,并在循环结束后插入总计行。数据处理:使用 sortedList = qdList?sort_by("model") 按型号对清单数据进行排序。处理数量 itemNum = item.num?replace(",", "")?number,移除逗号并转换为数字,确保计算正确。格式化输出:小计和总计行使用 colspan 属性合并单元格,确保表格显示整洁。使用 CSS 类 subtotal 和 total 为小计和总计行应用加粗和突出显示的样式。总结:此表格通过 FreeMarker 动态插入数据、计算小计和总计,并通过合适的排序和格式化样式,确保清单展示清晰且易于阅读。最后,模板还包括了一些额外信息:<p>发货联系人:${contacts}</p><p>联系电话:${contactsPhone}</p><p>日期:${date}</p> <p style="text-align: right;">收货人(签字):_______________</p><p style="text-align: right;">联系电话:_______________</p><p style="text-align: right;">${customer}</p>这部分添加了额外的联系信息和签名区域,进一步完善了文档的实用性。总的来,这个FreeMarker模板展示了如何结合HTML、CSS和FreeMarker的模板语法来创建一个复杂、动态且格式良好的文档。它不仅能够准确地呈现数据,还能执行必要的计算和格式化,从而生成一个专业的供货清单文档。总结通过使用SpingBoot、Apache POI和FreeMarker,我们成功自动化了电缆供货清单的生成过程。这不仅提高了效率,还减少了人为错误。本解决方案的模块化设计使其易于维护和扩展。希望本教程能够帮助您理解如何使用Java技术来解决实际业务问题。————————————————原文链接:https://blog.csdn.net/weixin_66401877/article/details/145230273
  • [技术干货] 【java-数据结构】Java 二叉树:代码世界里的神奇树形魔法
    前言在计算机科学领域,数据结构是算法实现的基石,而二叉树则是其中一颗璀璨的明珠。它以独特的树形结构,在众多场景中发挥着关键作用。二叉树由节点组成,每个节点最多包含两个子节点,这种简洁而强大的设计,使得数据的存储、检索与处理变得高效且有序。无论是数据库索引、编译器的语法分析,还是人工智能中的决策树算法,都离不开二叉树的身影。在Java编程世界里,掌握二叉树的生成与操作是迈向高级编程的重要一步。通过使用Java语言来构建二叉树,不仅能够深入理解数据结构的底层原理,还能提升解决复杂问题的能力。接下来,我们将一步步深入探索如何在Java中实现二叉树,从节点的定义到树的构建,再到各种遍历与操作方法,揭开这一重要数据结构的神秘面纱。一:什么是二叉树二叉树是一种每个节点最多有两个子节点的树形数据结构,这两个子节点通常被称为左子节点和右子节点。这种简洁而强大的结构,在许多算法和数据处理场景中发挥着关键作用。树是⼀种⾮线性的数据结构,它是由n(n>=0)个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。它具有以下的特点:有⼀个特殊的结点,称为根结点,根结点没有前驱结点除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、…、Tm,其中每⼀个集合Ti(1 <= i <= m) ⼜是⼀棵与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有0个或多个后继树是递归定义的。注意:树形结构中,⼦树之间不能有交集,否则就不是树形结构那么怎么区分什么是树,什么不是树呢?请看下面图片: 一些二叉树重要的概念,以下面这棵树为例:结点的度:⼀个结点含有⼦树的个数称为该结点的度; 如上图:A的度为6树的度:⼀棵树中,所有结点度的最⼤值称为树的度; 如上图:树的度为6叶⼦结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I…等节点为叶结点双亲结点或⽗结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:A是B的⽗结点孩⼦结点或⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图:B是A的孩⼦结点根结点:⼀棵树中,没有双亲结点的结点;如上图:A结点的层次:从根开始定义起,根为第1层,根的⼦结点为第2层,以此类推树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为4树的以下概念只需了解,在看书时只要知道是什么意思即可:⾮终端结点或分⽀结点:度不为0的结点; 如上图:D、E、F、G…等节点为分⽀结点兄弟结点:具有相同⽗结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点堂兄弟结点:双亲在同⼀层的结点互为堂兄弟;如上图:H、I互为兄弟结点结点的祖先:从根到该结点所经分⽀上的所有结点;如上图:A是所有结点的祖先⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙森林:由m(m>=0)棵互不相交的树组成的集合称为森林二:Java中二叉树的实现2.1定义二叉树节点类首先,我们需要定义一个类来表示二叉树的节点。每个节点包含一个数据元素,以及指向左子节点和右子节点的引用。class TreeNode {    int data;    TreeNode left;    TreeNode right;    TreeNode(int data) {        this.data = data;        this.left = null;        this.right = null;    }}2.2构建二叉树接下来,我们可以编写代码来构建一棵简单的二叉树。public class BinaryTreeMagic {    public static void main(String[] args) {        // 创建根节点        TreeNode root = new TreeNode(1);        // 创建左子节点        root.left = new TreeNode(2);        // 创建右子节点        root.right = new TreeNode(3);        // 为左子节点创建左子节点        root.left.left = new TreeNode(4);        // 为左子节点创建右子节点        root.left.right = new TreeNode(5);        // 至此,一棵简单的二叉树构建完成        //       1        //      / \        //     2   3        //    / \        //   4   5    }}2.3二叉树的遍历遍历二叉树是对树中每个节点进行访问的过程。常见的遍历方式有三种:前序遍历、中序遍历和后序遍历。学习⼆叉树结构,最简单的⽅式就是遍历。所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做⼀次且仅做⼀次访问。访问结点所做的操作依赖于具体的应⽤问题(⽐如:打印节点内容、节点内容加1)。 遍历是⼆叉树上最重要的操作之⼀,是⼆叉树上进⾏其它运算之基础。在遍历⼆叉树时,如果没有进⾏某种约定,每个⼈都按照⾃⼰的⽅式遍历,得出的结果就⽐较混乱,如果按照某种规则进⾏约定,则每个⼈对于同⼀棵树的遍历结果肯定是相同的。如果N代表根节点,L代表根节点的左⼦树,R代表根节点的右⼦树,则根据遍历根节点的先后次序有以下遍历⽅式:• NLR:前序遍历(Preorder Traversal 亦称先序遍历)⸺访问根结点—>根的左⼦树—>根的右⼦树。• LNR:中序遍历(Inorder Traversal)⸺根的左⼦树—>根节点—>根的右⼦树。• LRN:后序遍历(Postorder Traversal)⸺根的左⼦树—>根的右⼦树—>根节点2.3.1前序遍历前序遍历的顺序是先访问根节点,然后递归地访问左子树,最后递归地访问右子树。void preOrderTraversal(TreeNode node) {    if (node!= null) {        System.out.print(node.data + " ");        preOrderTraversal(node.left);        preOrderTraversal(node.right);    }}2.3.2中序遍历中序遍历的顺序是先递归地访问左子树,然后访问根节点,最后递归地访问右子树。void inOrderTraversal(TreeNode node) {    if (node!= null) {        inOrderTraversal(node.left);        System.out.print(node.data + " ");        inOrderTraversal(node.right);    }}2.3.3后序遍历后序遍历的顺序是先递归地访问左子树,然后递归地访问右子树,最后访问根节点。void postOrderTraversal(TreeNode node) {    if (node!= null) {        postOrderTraversal(node.left);        postOrderTraversal(node.right);        System.out.print(node.data + " ");    }}前序遍历结果:1 2 3 4 5 6中序遍历结果:3 2 1 5 4 6后序遍历结果:3 2 5 6 4 12.3.4 层序遍历层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。设⼆叉树的根节点所在层数为1,层序遍历就是从所在⼆叉树的根节点出发,⾸先访问第⼀层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历。以这棵树为例写下面题目:1.某完全⼆叉树按层次输出(同⼀层从左到右)的序列为 ABCDEFGH 。该完全⼆叉树的前序序列为(A)A: ABDHECFGB: ABCDEFGHC: HDBEAFCGD: HDEBFGCA2.⼆叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则⼆叉树根结点为(A)A: EB: FC: GD: H3.设⼀课⼆叉树的中序遍历序列:badce,后序遍历序列:bdeca,则⼆叉树前序遍历序列为(D)A: adbceB: decabC: debacD: abcde4.某⼆叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同⼀层从左到右)的序列为(A)A: FEDCBAB: CBAFEDC: DEFCBAD: ABCDEF2.4完整代码示例下面是一个包含二叉树构建和遍历功能的完整Java代码示例:class TreeNode {    int data;    TreeNode left;    TreeNode right;    TreeNode(int data) {        this.data = data;        this.left = null;        this.right = null;    }}public class BinaryTreeMagic {    void preOrderTraversal(TreeNode node) {        if (node!= null) {            System.out.print(node.data + " ");            preOrderTraversal(node.left);            preOrderTraversal(node.right);        }    }    void inOrderTraversal(TreeNode node) {        if (node!= null) {            inOrderTraversal(node.left);            System.out.print(node.data + " ");            inOrderTraversal(node.right);        }    }    void postOrderTraversal(TreeNode node) {        if (node!= null) {            postOrderTraversal(node.left);            postOrderTraversal(node.right);            System.out.print(node.data + " ");        }    }    public static void main(String[] args) {        BinaryTreeMagic tree = new BinaryTreeMagic();        // 创建根节点        TreeNode root = new TreeNode(1);        // 创建左子节点        root.left = new TreeNode(2);        // 创建右子节点        root.right = new TreeNode(3);        // 为左子节点创建左子节点        root.left.left = new TreeNode(4);        // 为左子节点创建右子节点        root.left.right = new TreeNode(5);        System.out.println("前序遍历:");        tree.preOrderTraversal(root);        System.out.println();        System.out.println("中序遍历:");        tree.inOrderTraversal(root);        System.out.println();        System.out.println("后序遍历:");        tree.postOrderTraversal(root);        System.out.println();    }}三:二叉树的应用场景搜索算法:二叉搜索树(BST)是一种特殊的二叉树,它满足左子树所有节点的值小于根节点的值,右子树所有节点的值大于根节点的值。这种特性使得在BST中进行搜索操作的时间复杂度为O(log n),大大提高了搜索效率。表达式求值:通过构建表达式二叉树,可以方便地对数学表达式进行求值。例如,对于表达式“(3 + 4) * 2”,可以构建相应的二叉树来进行计算。文件系统目录结构:可以用二叉树来模拟文件系统的目录结构,根节点表示根目录,子节点表示子目录或文件,方便进行文件管理和查找。⽂件系统管理(⽬录和⽂件)四:二叉树的重点4.1概念⼀棵⼆叉树是结点的⼀个有限集合,该集合:或者为空或者是由⼀个根节点加上两棵别称为左⼦树和右⼦树的⼆叉树组成从上图可以看出:1. ⼆叉树不存在度⼤于2的结点2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树 注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的:4.2大自然的一些奇观 4.3两种特殊的⼆叉树满⼆叉树: ⼀棵⼆叉树,如果每层的结点数都达到最⼤值,则这棵⼆叉树就是满⼆叉树。也就是说,如果⼀棵⼆叉树的层数为K,且结点总数是 ,则它就是满⼆叉树。完全⼆叉树: 完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度为K的,有n个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从0⾄n-1的结点⼀ 对应时称之为完全⼆叉树。 要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。4.4二叉树的一些性质1. 若规定根结点的层数为1,则⼀棵⾮空⼆叉树的第i层上最多有(i>0)个结点2. 若规定只有根结点的⼆叉树的深度为1,则深度为K的⼆叉树的最⼤结点数是(k>=0)3. 对任何⼀棵⼆叉树, 如果其叶结点个数为 n0, 度为2的⾮叶结点个数为 n2,则有n0=n2+14. 具有n个结点的完全⼆叉树的深度k为上取整5. 对于具有n个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的顺序对所有节点从0开始编号,则对于序号为i的结点有:◦ 若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,⽆双亲结点◦ 若2i+1<n,左孩⼦序号:2i+1,否则⽆左孩⼦◦ 若2i+2<n,右孩⼦序号:2i+2,否则⽆右孩⼦下面一些题目为例:某⼆叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该⼆叉树中的叶⼦结点数为( )A 不存在这样的⼆叉树B 200C 198D 199答案:B2.在具有 2n 个结点的完全⼆叉树中,叶⼦结点个数为( )A nB n+1C n-1D n/2答案:A3.⼀个具有767个节点的完全⼆叉树,其叶⼦节点个数为()A 383B 384C 385D 386答案:B4.⼀棵完全⼆叉树的节点数为531个,那么这棵树的⾼度为( )A 11B 10C 8D 12答案:B五:总结Java二叉树就像代码世界里的神奇魔法棒,通过巧妙地构建和操作树形结构,为我们解决各种复杂的编程问题提供了有力的工具。无论是高效的搜索算法,还是复杂的表达式求值,二叉树都展现出了其独特的魅力和强大的功能。希望通过本文的介绍和代码示例,你能对Java二叉树有更深入的理解和认识,在编程的道路上运用这神奇的树形魔法创造出更多精彩的代码。可以已经准备好进一步探索二叉树的更多高级特性和应用,比如平衡二叉树、红黑树等,它们将为你的编程技能库增添更多强大的武器。————————————————原文链接:https://blog.csdn.net/2301_80350265/article/details/145692883
  • [技术干货] Java的栈与队列以及代码实现
    一.栈(Stack)1.1栈的概念栈:一种特殊的线性表,其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO ( Last In First Out )的原则 压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。出栈:栈的删除操作叫做出栈。 出数据在栈顶 1.2栈的实现及模拟public class Test {    public static void main(String[] args) {        Stack<Integer>s=new Stack();//创建一个空栈        s.push(1);//往栈中存入1        s.push(2);//2        s.push(3);//3        s.push(4);//4        s.push(5);//5         System.out.println(s.size());//有效个数5        System.out.println(s.peek());//获取栈顶元素5         s.pop();//5出栈         System.out.println(s.peek());//此时栈顶元素变为4         System.out.println(s.empty());//判断是否为空栈,此时不为空 返回false    }} 这里我们用自己的方法来模拟实现上述的方法public class MyStack {    int[] elem;    int usedSize;     public MyStack(){        this.elem=new int[10];    }     public void push(int val){        if(isFull()){            //扩容            elem= Arrays.copyOf(elem,elem.length*2);        }        elem[usedSize]=val;        usedSize++;    }     public boolean isFull(){        return usedSize==elem.length;    }     public int pop(){        if(empty()){            return -1;        }        int oldVal=elem[usedSize-1];        usedSize--;        return oldVal;    }     public int peek(){        if(empty()){            return -1;        }        return elem[usedSize-1];    }     public boolean empty(){        return usedSize==0;    }}二.队列(Queue)2.1队列的概念队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front) 2.2队列的实现及模拟 在Java中,Queue是个接口,底层是通过链表实现的注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。   public class Test{    public static void main(String[] args) {        Queue<Integer>q=new LinkedList<>();        q.offer(1);//从队尾入        q.offer(2);        q.offer(3);        q.offer(4);         System.out.println(q.size());//有效个数 4        System.out.println(q.peek());//获取头元素 1         q.poll();//1从队列中出         System.out.println(q.peek());//2         System.out.println(q.isEmpty());//此时队列不为空,所以返回 false    }}这里我们进行模拟实现上述方法 public class MyQueue {    static class ListNode{        public int val;        public ListNode prev;        public ListNode next;         public ListNode(int val){            this.val=val;        }    }     public ListNode head;    public ListNode last;     public void offer(int val){        ListNode node=new ListNode(val);        if(head==null){            head=last=node;        }else{            last.next=node;            node.prev=last;            last=last.next;        }    }     public int poll(){        if(head==null){            return -1;        }        int ret=head.val;        if(head.next==null){            head=last=null;        }else{            head=head.next;            head.prev=null;        }        return ret;    }     public int peek(){        if(head == null) {            return -1;        }        return head.val;    }     public boolean isEmpty(){        return head==null;    }} 2.3循环队列实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。 2.4双端队列(Deque)双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队 Deque是一个接口,使用时必须创建LinkedList的对象 Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现————————————————                     原文链接:https://blog.csdn.net/2301_80288511/article/details/137435796
  • [技术干货] 带你深入了解前端【HTML+JavaScript】
    关于前端和大家推荐一个书籍,就是JavaScript高级程序设计,也叫红宝书,内容非常全面详细,大家可以买来看,以后面试工作的时候可能会用到,知识点什么的讲解的都挺好的也比较全面1.首先讲解一下src和href的区别:1.src是source的缩写,表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置,src会将其指向的资源下载并应用到文档当中,当浏览器解析到带有src属性的标签时,它会发起一个HTTP请求来加载指定的资源,并将其嵌入到文档中。例如,<img src="image.png">会使浏览器加载并显示图片,当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载,编译,执行完毕,所以一半js脚本会放在页面底部。2.href是hypertext reference的缩写,表示超文本引用,它指向一些网络资源,建立和当前元素或本文档的链接关系,当浏览器是识别到它指向的文件时,就会并行下载资源,不会停止对当前文档的处理,常用在a,link,等标签上。当用户点击带有 href属性的链接时,浏览器会导航到指定的URL。例如,<a href="https://example.com">Visit Example</a>会创建一个链接,点击后将用户带到 "example.com"。主要区别:src用于嵌入或替换当前元素的内容,并且src会阻塞页面的解析和渲染,直到资源加载,处理完毕href用于建立链接关系,会并行下载资源,不会阻塞页面的解析和渲染2.HTML语义化语义化是指内容的结构化(内容语义化),选择合适的标签(代码语义化)内容语义化:内容语义化是指在编写HTML时,根据页面的内容和结构,选择最能表达该内容本质的标签来标记内容例如对于文章的标签,应该使用<h1>到<h6>标签,而不是使用<span> 或<div> 并通过样式模拟标签样式。代码语义化:代码语义化是指使用具有明确语义的HTML标签,而不是仅仅依赖class和id或style属性来定义元素的外观和行为,使用<nav>标签来表示页面的导航部分,而不是使用<div>并添加class="nav" 3.script标签中defer和async的区别:如果没有defer和async属性,浏览器会立即加载并执行相应的脚本,它不会等待后续加载的文档元素,读取到就会加载和执行,这样就阻塞了后续的文档的加载。无defer和async属性的script标签当浏览器遇到该标签是,会暂停对HTML的解析和渲染,立即开始加载script.js并执行这会阻塞后续文档元素的加载和解析,直到脚本加载和执行完毕defer属性:当使用<script src="script.js" defer></script〉时,脚本的加载是异步的,不会阻塞 HTML 的解析脚本会在 HTML 文档的解析完成后,按照<script>标签在文档中的出现顺序依次执行在 DoMcontentLoaded 事件触发之前。对于多个带有 defer 属性的脚本,它们会按照在文档中的顺序依次执行,即使它们的加载完成顺序不同。<script src="script1.js" defer></script><script src="script2.is" defer></script><script src="script3.is" defer></script>script2.js和script3.js 会并行加载,script1.is 当HTML解析完成后,按照 script1.js、script2.js、script3.js的顺序依次执行DoMcontentLoaded 事件会在所有 defer 脚本执行完成后触发async属性:当使用<script src="script.js"async>/script〉时,脚本的加载也是异步的,不会阻塞 HTML 的解析。一旦脚本加载完成,会立即执行,不管 HTML 解析是否完成。对于多个带有 async 属性的脚本,它们的执行顺序不保证与在文档中的顺序一致,谁先加载完成谁先护行。<script src="script1.is" defer></script><script src="script2.is" defer></script><script src="script3.js" defer></script>script2.is 和 script3.is 会并行加载,script1.is、它们的执行顺序取决于各自的加载完成时间,可能是 script2.js先执行,然后是 script1.js ,最后是 script3.js,顺序不固定。DoMcontentLoaded 事件可能在某些 async 脚本执行之前或之后触发,因为 async 脚本的执行不依赖于HTML 解析完成,也不遵循<script> 标签的顺序。4.常用的meta标签有哪些,作用分别是什么meta 标签由 name 和 content 属性定义,用来描述网页文档的属性,比如网页的作者,网页描述关键词等,除了HTTP标准固定了一些name作为大家使用的共识,开发者还可以自定义name。charset用于指定 HTML 文档的字符编码,确保浏览器能够正确解析文档中的字符。<meta charset="UTF-8">UTF-8 是一种通用的字符编码,支持世界上大多数语言的字符,确保页面可以正确显示中文、英文、特殊字符等,避免出现乱码现象。keywords为搜索引擎提供页面的关键词信息,帮助搜索引擎更好地理解页面的主题和内容,以提高搜索排名<meta name="keywords" content="关键词1, 关键词2, 关键词3">content 属性包含了一系列用逗号分隔的关键词。例如,对于一个关于旅游的页面,可以使用<meta name="keywords"content="旅游,度假,景点,酒店">。搜索引擎会将这些关键词作为页面的主要搜索词,当用户搜索这些关键词时,该页面可能会出现在搜索结果中description提供页面的简短描述,该描述通常会显示在搜索引擎的搜索结果中,作为页面的摘要信息,<meta name="description" content='这是一个关于旅游景点推荐的页面,为你提供各种热门景点的信息和旅游攻略。">content 属性包含了页面的描述信息,长度通常在 150-160 个字符左右。它可以帮助用户快速了解页面的主要内容,吸引用户点击链接。refresh用于页面的自动刷新或重定向。<meta http-equiv="refresh" content="5;url=https://example.com">content 属性中的第一个值表示刷新或重定向的时间间隔(以秒为单位),上述示例中是5 秒。第二个部分( url=https://example.com )表示重定向的目标 URL。例如,<meta http-equiv="refresh" content="g;url=https://newpage.com">会立即将用户重定向至 https://newpage.com。viewport主要用于控制移动端设备的视口,确保页面在移动设备上的显示效果,使其更适应不同屏幕尺寸和分辨率,<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1.0,user-scalable=no">awidth=device-width :将视口的宽度设置为设备的屏幕宽度,确保页面宽度与设备屏幕宽度匹配initial-scale=1初始缩放比例为 1,即页面初始显示不进行缩放。maximum-scale=1.0限制用户可以将页面放大的最大比例为 1.0 倍,防止用户过度放大页面。user-scalable=no禁止用户手动缩放页面,对于某些特定的页面(如应用页面)可能需要此设置,但通常不建议,因为会影响用户体验。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2301_81253185/article/details/145422274
总条数:739 到第
上滑加载中