-
1. 踩坑经历假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为:先根据订单量倒序排列,再根据城市名称正序排列。示例代码:import lombok.Getter;import lombok.Setter;import lombok.ToString;@Getter@Setter@ToStringpublic class OrderStatisticsInfo { private String cityName; private Integer orderCount; public OrderStatisticsInfo(String cityName, Integer orderCount) { this.cityName = cityName; this.orderCount = orderCount; }}public static void main(String[] args) { List<OrderStatisticsInfo> orderStatisticsInfoList = Arrays.asList( new OrderStatisticsInfo("上海", 1000), new OrderStatisticsInfo("北京", 1000), new OrderStatisticsInfo("成都", 700), new OrderStatisticsInfo("常州", 700), new OrderStatisticsInfo("广州", 900), new OrderStatisticsInfo("深圳", 800) ); orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName)); orderStatisticsInfoList.forEach(System.out::println);}预期结果:北京 1000上海 1000广州 900深圳 800常州 700成都 700实际结果:OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)从以上结果可以看出,根据订单量倒序排列没啥问题,但根据城市名称正序排列不符合预期:上海竟然排到了北京的前面,但常州与成都的顺序又是对的。2. 原因分析Comparator.comparing对字符串类型进行排序时,默认使用的是字符串的自然排序,即String的compareTo方法,该方法是基于Unicode编码值进行比较的,未考虑语言特定的字符顺序(如中文拼音)。先看下String的compareTo方法的源码:public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2;}以上海与北京的比较为例,先比较第一个字符,也就是字符上和字符北,字符上对应的Unicode编码值是19978,因此c1 = 19978,字符北对应的Unicode编码值是21271,因此c2 = 21271,因为c1 != c2,所以返回值为-1293,也就是说上海小于北京(要排在北京的前面),不符合预期。以常州与成都的比较为例,先比较第一个字符,也就是字符常和字符成,字符常对应的Unicode编码值是24120,因此c1 = 24120,字符成对应的Unicode编码值是25104,因此c2 = 25104,因为c1 != c2,所以返回值为-984,也就是说常州小于成都(要排在成都的前面),符合预期。可以通过Character.codePointAt方法获取字符的Unicode编码值:// 输出:19978System.out.println(Character.codePointAt("上海", 0));// 输出:21271System.out.println(Character.codePointAt("北京", 0));// 输出:24120System.out.println(Character.codePointAt("常州", 0));// 输出:25104System.out.println(Character.codePointAt("成都", 0));3. 解决方案Java提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName, Collator.getInstance(Locale.CHINA)));orderStatisticsInfoList.forEach(System.out::println);此时的输出结果为:OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)可以看到,北京排到了上海的前面,符合预期。上述代码指定了Collator.getInstance(Locale.CHINA),在排序比较时不再执行String的compareTo方法,而是执行Collator的compare方法,实际上是RuleBasedCollator的compare方法。可以执行以下代码单独看下上海与北京的比较结果:Collator collator = Collator.getInstance(Locale.CHINA);// 输出:1,代表上海大于北京,也就是要排在北京的后面System.out.println(collator.compare("上海", "北京"));转载自https://www.cnblogs.com/zwwhnly/p/18846441
-
1. 前言在我接触过的大部分Java项目中,经常看到使用@Autowired注解进行字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService;}在IDEA中,以上代码@Autowired注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,翻译过来就是不建议使用字段注入。关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用@Resource注解的,但这都不是该问题的本质。该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项进行了检测并给以提示。所以该提示背后的本质问题是:为什么Spring官方推荐构造器注入而不是字段注入?2. 推荐构造器注入的理由相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题2.1 支持不可变性构造器注入允许将依赖字段声明为final,确保对象一旦创建,其依赖关系不再被修改。字段注入无法使用final,依赖可能在对象生命周期中被意外修改,破坏状态一致性。构造器注入示例:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService; @Autowired public OrderService(PaymentService paymentService, InventoryService inventoryService) { this.paymentService = paymentService; this.inventoryService = inventoryService; }}说明:如果Spring版本是4.3或者更高版本且只有一个构造器,构造器上的@Autowired注解可以省略。2.2 依赖明确构造器注入通过在类的构造函数中显式声明依赖,并且强制要求在创建对象时必须提供所有必须的依赖项,通过构造函数参数,使用者对该类的依赖一目了然。字段注入通过在类的字段上直接使用@Autowired注解注入依赖,依赖关系隐藏在类的内部,使用者无法直接看到该类的依赖。2.3 单元测试友好构造器注入允许直接通过new创建对象,无需依赖Spring容器或反射,降低了测试复杂度。字段注入需要依赖Spring容器或反射,增加了测试复杂度。2.4 循环依赖检测前置,提前暴露问题构造器注入在应用启动时直接暴露循环依赖,强制开发者通过设计解决问题。字段注入在应用启动时不会暴露循环依赖,直到实际调用时才可能暴露问题,增加调试难度。示例:假设项目中有以下两个Service存在循环依赖:import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; public OrderService(PaymentService paymentService) { this.paymentService = paymentService; }}import org.springframework.stereotype.Service;@Servicepublic class PaymentService { private final OrderService orderService; public PaymentService(OrderService orderService) { this.orderService = orderService; }}此时启动项目会报错,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException异常,大致的异常信息如下所示:Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?将以上两个Service修改为字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService;}import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class PaymentService { @Autowired private OrderService orderService;}此时启动项目不会报错,可以启动成功。3. @RequiredArgsConstructor注解的使用及原理为了避免样板化代码或者为了简化代码,有的项目中可能会使用@RequiredArgsConstructor注解来代替显式的构造方法:import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;@RequiredArgsConstructor@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService;}接下来简单讲解下@RequiredArgsConstructor注解的原理。@RequiredArgsConstructor注解用于在编译时自动生成包含特定字段的构造方法。字段筛选逻辑如下所示:被final修饰的未显式初始化的非静态字段被@NonNull注解标记的未显式初始化的非静态字段示例:import lombok.NonNull;import lombok.RequiredArgsConstructor;@RequiredArgsConstructorpublic class User { private final String name; @NonNull private Integer age; private final String address = ""; private String email; private static String city; @NonNull private String sex = "男";}以上代码在编译时自动生成的构造方法如下所示:public User(String name, @NonNull Integer age) { if (age == null) { throw new NullPointerException("age is marked non-null but is null"); } else { this.name = name; this.age = age; }}从生成的构造方法可以看出:1)如果字段被lombok.NonNull注解标记,在生成的构造方法内会做null值检查。2)address字段虽然被final修饰,但因为已初始化,所以未包含在构造方法中。3)email字段既没被final修饰,也没被lombok.NonNull注解标记,所以未包含在构造方法中。4)city字段是静态字段,所以未包含在构造方法中。5)sex字段虽然被lombok.NonNull注解标记,但因为已初始化,所以未包含在构造方法中。4. 总结@Autowired注解在IDEA中提示:Field injection is not recommended,其背后的本质问题是:Spring官方推荐构造器注入而不是字段注入。而Spring官方推荐构造器注入,是因为相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题使用构造器注入时,为了避免样板化代码或者为了简化代码,可以使用@RequiredArgsConstructor注解来代替显式的构造方法,因为@RequiredArgsConstructor注解可以在编译时自动生成包含特定字段的构造方法。至于项目中要不要使用构造器注入,使用显式的构造方法还是使用@RequiredArgsConstructor注解来简化代码,可以根据个人喜好及团队规范自行决定。
-
@RestController 和 @Controller 是Spring框架中用于定义控制器(Controller)的两个非常重要的注解,它们都用于处理HTTP请求,但它们之间存在一些关键的区别。1.@Controller@Controller 注解是Spring MVC的一部分,用于定义一个控制器类。当Spring MVC接收到一个请求时,它会根据请求的URL映射到相应的控制器类上。@Controller 注解的类中的方法返回的是字符串(通常是视图名)或ModelAndView对象,这些返回值会用于渲染视图(通常是JSP页面)。@Controller 注解通常与@RequestMapping或它的变体(如@GetMapping, @PostMapping等)一起使用来定义请求处理的方法。如果你希望将MVC模式中的“控制器”部分与“视图”部分分离,并且希望由Spring MVC来管理视图的渲染,那么你应该使用@Controller。2.@RestController@RestController 是Spring 4引入的一个方便的注解,它实际上是@Controller和@ResponseBody的组合注解。它意味着,当控制器中的方法返回一个对象时,Spring会自动将这个对象转换为JSON或XML(取决于请求的Accept头部)并写入HTTP响应体中。@RestController更适合构建RESTful Web服务,因为它简化了返回JSON或XML数据的过程。使用@RestController注解的控制器类中的方法通常会返回一个对象或对象列表,而不是视图名或ModelAndView对象。@RestController也常与@RequestMapping或它的变体一起使用来定义请求处理的方法。3.注意如果你正在构建一个需要渲染视图的Web应用(如基于JSP的Web应用),那么你应该使用@Controller。如果你正在构建一个RESTful Web服务,希望直接返回JSON或XML等数据格式,那么@RestController将是更好的选择。@RestController简化了返回数据的过程,因为它自动将返回的对象转换为JSON或XML,而@Controller则需要额外的步骤来渲染视图。上代码和效果图@Controller注解@Controller@RequestMapping("/user")public class UserController { @Autowired private UserService userService; @RequestMapping("/init") public String init() { return "login"; }}@RestController注解@RestController@RequestMapping("/user")public class UserController { @Autowired private UserService userService; @RequestMapping("/init") public String init() { return "login"; }}转载自https://www.cnblogs.com/sailCoding/p/18420217
-
在 Java 中操作 Map 时,高效遍历和安全删除数据可以通过以下方式实现:一、遍历 Map 的 4 种高效方式1. 传统迭代器(Iterator)Map<String, Integer> map = new HashMap<>();map.put("key1", 5);map.put("key2", 3);Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + ": " + value);}2. Java 8+ forEach + Lambdamap.forEach((key, value) -> { System.out.println(key + ": " + value);});3. 增强 for 循环(遍历 EntrySet)for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); // ...}4. Stream API(Java 8+)map.entrySet().stream() .filter(entry -> entry.getValue() > 3) // 过滤条件 .forEach(entry -> { System.out.println(entry.getKey(); });二、安全删除 Map 中的数据1. 遍历时删除Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() < 3) { iterator.remove(); }}2. Java 8+ removeIfmap.entrySet().removeIf(entry -> entry.getValue() < 3);3. 直接删除(已知 Key)map.remove("key1");三、关键注意事项避免并发修改异常遍历时直接调用 map.remove(key) 会导致 ConcurrentModificationException,必须使用 Iterator.remove() 或 removeIf。性能优化对 HashMap,优先遍历 entrySet()(直接获取 Key-Value)。对只读操作,forEach 和 Stream 性能接近;需过滤/删除时优先用 removeIf。并发场景多线程环境下使用 ConcurrentHashMap 并结合 Iterator.remove() 或原子操作。四、完整示例代码Map<String, Integer> map = new HashMap<>(Map.of( "key1", 5, "key2", 3, "key3", 2));// 遍历并删除 value < 3map.entrySet().removeIf(entry -> entry.getValue() < 3);// 输出结果:{key1=5, key2=3}System.out.println(map);通过上述方法,可以高效且安全地操作 Java 中的 Map 数据结构。转载自https://www.cnblogs.com/sailCoding/p/18933466
-
在Java的Stream API中,anyMatch和allMatch是终端操作(Terminal Operation),用于对流中的元素进行布尔值匹配检查。它们的核心区别在于匹配逻辑和短路行为:🚀1. anyMatch(Predicate)功能:检查流中是否至少有一个元素满足给定的断言条件。返回值:boolean(找到第一个匹配项时立即返回true,否则遍历完所有元素后返回false)。短路特性:具有短路能力,找到第一个匹配项后立即终止流处理。典型场景:// 检查是否有至少一个偶数List<Integer> numbers = List.of(1, 3, 5, 7, 9);boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // 返回false// 检查是否有权限(用户权限列表包含"ADMIN")List<String> permissions = List.of("READ", "WRITE");boolean isAdmin = permissions.stream().anyMatch("ADMIN"::equals); // 返回false🔒2. allMatch(Predicate)功能:检查流中是否所有元素都满足给定的断言条件。返回值:boolean(发现第一个不匹配项时立即返回false,否则遍历完所有元素后返回true)。短路特性:具有短路能力,发现第一个不匹配项后立即终止流处理。典型场景:// 检查是否所有数字都是偶数List<Integer> numbers = List.of(2, 4, 6, 8);boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // 返回true// 检查所有用户是否已激活(用户状态列表全为"ACTIVE")List<String> userStatuses = List.of("ACTIVE", "INACTIVE");boolean allActive = userStatuses.stream().allMatch("ACTIVE"::equals); // 返回false🔑核心对比特性anyMatchallMatch匹配逻辑至少一个元素匹配所有元素都必须匹配短路行为找到第一个匹配项后终止找到第一个不匹配项后终止典型返回值常见true(易满足)常见false(难满足)性能敏感场景适合快速失败的正向检查适合严格的验证场景🔐底层原理这两个方法都通过迭代流中的元素进行判断,但实现上有关键差异:anyMatch:一旦遇到true立即返回,后续元素不再处理。allMatch:一旦遇到false立即返回,后续元素不再处理。💎注意事项空流处理:对空流调用时,anyMatch返回false,allMatch返回true(符合逻辑学中的"存在量词"和"全称量词"定义)。与findAny的区别:anyMatch返回布尔值,而findAny返回Optional<T>元素。性能影响:在大数据量场景下,短路特性可以显著减少计算量。🔍代码示例: /** * 判断listA中的所有字符串是否包含listB中的所有字符串(子串匹配) * @param listA 待检查的主列表(长字符串) * @param listB 需要被包含的子串列表 * @return true 如果listB所有元素都是listA中某个元素的子串 */ public static boolean containsAllSubstrings(List<String> listA, List<String> listB) { return listB.stream() .filter(Objects::nonNull) // 过滤listB中的null元素 .allMatch(b -> listA.stream() .filter(Objects::nonNull) // 过滤listA中的null元素 .anyMatch(a -> a.contains(b)) // 检查子串存在性 ); } //注意:如果list中没有null的情况下可以不加.filter(Objects::nonNull)处理 public static void main(String[] args) { // 初始化测试数据(根据您提供的示例) List<String> listA = List.of( "READ.USER-KBN-KNJ", "READ.USER-ID.", // ...(此处省略中间元素) "READ.USER-GUIDE-MSG.", "READ.USER-ERR-MSG." ); List<String> listB = List.of( "KBN-KNJ", "USER-ID", // ...(此处省略中间元素) "GUIDE-MSG", "ERR-MSG" ); // 执行匹配检查 boolean result = containsAllSubstrings(listA, listB); System.out.println("listA是否包含listB的所有子串: " + result); }转载自https://www.cnblogs.com/sailCoding/p/18950812
-
一、Deflater实现(原始DEFLATE格式)1. 压缩方法 public static String compress(String rawData) { Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION, true); // nowrap=true try { deflater.setInput(rawData.getBytes(StandardCharsets.UTF_8)); deflater.finish(); try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; while (!deflater.finished()) { int compressedBytes = deflater.deflate(buffer); bos.write(buffer, 0, compressedBytes); } return Base64.getUrlEncoder().withoutPadding() .encodeToString(bos.toByteArray()); } } finally { deflater.end(); } }2. 解压方法 public static String decompress(String compressedData) { Inflater inflater = new Inflater(true); try { byte[] compressedBytes = Base64.getUrlDecoder().decode(compressedData); inflater.setInput(compressedBytes); try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; while (!inflater.finished()) { try { int decompressedBytes = inflater.inflate(buffer); bos.write(buffer, 0, decompressedBytes); } catch (DataFormatException e) { throw new RuntimeException("数据损坏", e); } } return bos.toString(StandardCharsets.UTF_8); } } finally { inflater.end(); } }二、GZIP实现(标准gzip格式)1. 压缩方法 public static String compress(String rawData) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(bos)) { gzip.write(rawData.getBytes(StandardCharsets.UTF_8)); gzip.finish(); return Base64.getEncoder().encodeToString(bos.toByteArray()); } }2. 解压方法 public static String decompress(String compressedData) throws IOException { byte[] compressedBytes = Base64.getDecoder().decode(compressedData); try (ByteArrayInputStream bis = new ByteArrayInputStream(compressedBytes); GZIPInputStream gzip = new GZIPInputStream(bis); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; int len; while ((len = gzip.read(buffer)) > 0) { bos.write(buffer, 0, len); } return bos.toString(StandardCharsets.UTF_8); } }三、优缺点对比特性Deflater (DEFLATE)GZIP压缩率≈95%(无头部,极限压缩)≈93%(含18字节头部)速度稍快(无头部开销)稍慢(需处理头部)兼容性需特殊解析器通用解压工具支持典型应用场景网络传输、内存敏感型应用文件存储、通用数据交换头部开销无18字节(含时间戳等元数据)校验和无有(CRC32)流式处理需手动管理缓冲区支持流式API四、选型建议优先选GZIP的场景:需要与其他系统交互时处理文件存储或日志压缩时需要内置校验和验证数据完整性时优先选Deflater的场景:追求极限压缩率时进行网络传输(尤其对延迟敏感时)需要自定义协议格式时通用原则:短文本(<1KB)建议直接存储中等长度文本(1KB-1MB)优先GZIP大文本(>1MB)可结合缓冲流处理实际测试显示,对于典型英文文本,Deflater比GZIP压缩率高约2-5%,但解压速度慢约10-15%。
-
前言递归(Recursion)在编程中是一个非常重要的概念。简单来说,递归指的是一个函数在其定义中直接或间接调用自身。这种调用机制允许函数通过分解问题为更小的相似子问题来解决复杂问题。递归的定义:递归是一种在函数定义中调用函数自身的方法。它通常包含一个或多个基准情况(base case),用于结束递归调用链,以及一个或多个递归步骤,用于将问题分解为更小的子问题。递归的原理:1.基准情况:这是递归调用的终止条件。当满足基准情况时,函数将不再调用自身,而是返回结果。2.递归步骤:在递归步骤中,函数会调用自身来解决一个规模更小的相似问题。这个过程会一直持续,直到达到基准情况。递归的示例:现在我将获取部门树作为示例1.示例数据准备/* Navicat Premium Data Transfer Source Server : localhost-本地 Source Server Type : MySQL Source Server Version : 80036 Source Host : localhost:3306 Source Schema : test Target Server Type : MySQL Target Server Version : 80036 File Encoding : 65001 Date: 22/04/2025 01:41:05*/SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for sys_dept-- ----------------------------DROP TABLE IF EXISTS `sys_dept`;CREATE TABLE `sys_dept` ( `dept_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '部门id', `parent_id` bigint(0) NULL DEFAULT 0 COMMENT '父部门id', `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '祖级列表', `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '部门名称', `order_num` int(0) NULL DEFAULT 0 COMMENT '显示顺序', `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '负责人', `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '联系电话', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`dept_id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 210 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_dept-- ----------------------------INSERT INTO `sys_dept` VALUES (100, 0, '0', '中国移动', 0, '张三', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', 'admin', '2025-03-04 10:15:38');INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (103, 100, '0,100', '研发部门', 1, '若依', '', '', '0', '0', 'admin', '2025-02-17 08:58:32', 'admin', '2025-03-12 10:26:18');INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);2.获取组装的部门树:public List<SysDept> selectDeptList(SysDept dept) { // 最终的部门树结果 List<SysDept> deptTree = new ArrayList<>(); try { // 1.获取所有部门信息 List<SysDept> sysDebts = sysDeptMapper.selectDeptList(dept); // 2.获取根节点的部门信息(parentId=0的部门为根节点) List<SysDept> rootDept = sysDebts.stream().filter(s-> s.getParentId().equals(0L)) .collect(Collectors.toList()); for (SysDept root : rootDept) { // 3.递归获取子节点 SysDept tree = getChildren(root, sysDebts); // 4.添加到部门树中 deptTree.add(tree); } return deptTree; } catch (Exception e) { throw new RuntimeException(e); } }3.递归获取子部门:/** * 递归获取子节点 * @param rootDept 根节点 * @param sysDepts 所有部门信息 * @return 树形结构的部门信息 */ private SysDept getChildren(SysDept rootDept, List<SysDept> sysDepts) { ArrayList<SysDept> children = new ArrayList<>(); for (SysDept dept : sysDepts) { if (rootDept.getDeptId().equals(dept.getParentId())){ children.add(getChildren(dept, sysDepts)); } } rootDept.setChildren(children); return rootDept; }4.接口数据返回:[ { "deptId": 100, "parentId": 0, "ancestors": "0", "deptName": "中国移动", "orderNum": 0, "leader": "张三", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 101, "parentId": 100, "ancestors": "0,100", "deptName": "深圳总公司", "orderNum": 1, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 104, "parentId": 101, "ancestors": "0,100,101", "deptName": "市场部门", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 105, "parentId": 101, "ancestors": "0,100,101", "deptName": "测试部门", "orderNum": 3, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 106, "parentId": 101, "ancestors": "0,100,101", "deptName": "财务部门", "orderNum": 4, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 107, "parentId": 101, "ancestors": "0,100,101", "deptName": "运维部门", "orderNum": 5, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 103, "parentId": 100, "ancestors": "0,100", "deptName": "研发部门", "orderNum": 1, "leader": "若依", "phone": "", "email": "", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 102, "parentId": 100, "ancestors": "0,100", "deptName": "长沙分公司", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 108, "parentId": 102, "ancestors": "0,100,102", "deptName": "市场部门", "orderNum": 1, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 109, "parentId": 102, "ancestors": "0,100,102", "deptName": "财务部门", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }]注意事项效率问题:递归可能引发栈溢出或重复计算(如斐波那契数列的朴素递归),需结合尾递归优化或动态规划改进。理解难度:需通过“递进+回归”过程分析,如阶乘的逐步展开与回溯。总结:递归是一种强大的编程范式,适用于问题可分解为自相似结构的场景,但需谨慎设计终止条件和优化性能。转载自https://www.cnblogs.com/songweipeng/p/18830967
-
前言1. Flowable中Java服务任务的依赖注入方法,比如使用Spring的@Autowired或构造函数注入,并确保服务任务类由Spring管理。2. 流程引擎配置中启用依赖注入和表达式解析的相关设置。3. 异步任务执行器的配置,确保其与Spring上下文集成,处理依赖注入的Bean。4. 常见问题排查,如检查Bean的作用域、是否正确使用delegateExpression等。5. 可能的示例代码或配置片段,帮助用户理解如何正确配置。另外,可能需要提醒用户注意流程定义文件中的服务任务配置是否正确,比如是否使用了expression或delegateExpression来引用Spring Bean,或者是否直接指定了Java类导致Spring的依赖注入不生效。最后,如果有必要,可以比较一下在非Spring环境下如何手动处理依赖注入,但大部分情况下用户可能在使用Spring,所以重点放在Spring集成方面。一、Java服务任务依赖注入场景:在实现JavaDelegate的类中注入Spring管理的Bean(如Service、Repository)。正确配置方法:步骤1:将服务类注册为Spring Bean@Component("myServiceTask") // 使用名称便于流程中引用public class MyServiceTask implements JavaDelegate { private final MyService myService; @Autowired // 构造器注入推荐 public MyServiceTask(MyService myService) { this.myService = myService; } @Override public void execute(DelegateExecution execution) { myService.businessMethod(); }}步骤2:在流程定义中引用Bean使用delegateExpression指向Spring Bean的名称:<serviceTask id="serviceTask1" name="My Task" flowable:delegateExpression="${myServiceTask}" />常见问题解决:Bean注入为null:确保类被Spring扫描(位于@ComponentScan路径下)。检查流程定义中是否错误使用class属性而非delegateExpression。确认流程引擎配置启用了Spring集成。表达式解析失败:在FlowableConfig中启用表达式解析和Spring注入:@Beanpublic SpringProcessEngineConfiguration processEngineConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setTransactionManager(transactionManager); config.setDatabaseSchemaUpdate(Flowable.DB_SCHEMA_UPDATE_TRUE); config.setAsyncExecutorEnabled(true); // 启用异步执行器 config.setExpressionManager(new SpringExpressionManager(applicationContext, null)); // 启用Spring表达式 return config;}二. 异步任务执行器配置当服务任务标记为异步(flowable:async="true")时,任务由异步执行器处理。配置异步执行器:@Beanpublic AsyncExecutor asyncExecutor(DataSource dataSource) { DefaultAsyncExecutor asyncExecutor = new DefaultAsyncExecutor(); asyncExecutor.setDataSource(dataSource); asyncExecutor.setCorePoolSize(5); // 核心线程数 asyncExecutor.setMaxPoolSize(10); // 最大线程数 asyncExecutor.setQueueSize(100); // 任务队列大小 return asyncExecutor;}在processEngineConfiguration中启用:config.setAsyncExecutor(asyncExecutor);config.setAsyncExecutorEnabled(true);异步任务依赖注入要点:Bean作用域:异步任务可能在新线程中运行,确保注入的Bean是线程安全的或使用@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)。事务管理:若异步任务涉及数据库操作,需配置@Transactional并确保事务传播正确。三、通过ApplicationContextAware接口的方式获取ApplicationContext对象实例可能的错误情况包括:没有在流程引擎配置中设置asyncExecutorEnabled为true,或者在服务任务中没有正确使用表达式导致注入失败。另外,Bean的作用域问题也可能导致依赖注入失败,例如,如果Bean的作用域是原型(prototype),但在注入时可能需要不同的处理方式。以下是我通过ApplicationContextAware接口的方式获取ApplicationContext对象实例,再通过applicationContext.getBean("myService")方法获取对应的bean代码示例:@Componentpublic class MyListener implements TaskListener, ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { applicationContext = arg0; } @Override public void notify(DelegateTask delegateTask) { String processInsId = delegateTask.getProcessInstanceId(); MyService myService = (MyService) applicationContext.getBean("myService"); // TODO 执行service方法 System.out.println("==========执行监听器======"); }}四. 常见问题排查错误:无法解析表达式${myServiceTask}检查Bean名称是否匹配。确认流程引擎配置中设置了SpringExpressionManager。异步任务不执行检查asyncExecutor是否启动:调用asyncExecutor.start()。查看日志中是否有任务提交异常。事务不生效确保异步执行器配置了事务管理器:asyncExecutor.setTransactionManager(transactionManager);五. Spring Boot集成若使用flowable-spring-boot-starter,简化配置如下:(1) application.yml:flowable: async-executor-enabled: true database-schema-update: true async-executor: core-pool-size: 5 max-pool-size: 10 queue-size: 100(2) 服务任务类:@Componentpublic class EmailServiceTask implements JavaDelegate { private final EmailService emailService; public EmailServiceTask(EmailService emailService) { this.emailService = emailService; } @Override public void execute(DelegateExecution execution) { String recipient = (String) execution.getVariable("email"); emailService.send(recipient, "流程通知", "您的任务已处理完成。"); }}(3) 流程定义XML:<serviceTask id="sendEmail" flowable:delegateExpression="${emailServiceTask}" />六.总结依赖注入:确保服务任务类为Spring Bean,流程中使用delegateExpression引用。异步执行:配置AsyncExecutor并启用,注意线程安全和事务。ApplicationContextAware接口的方式获取ApplicationContext对象实例Spring集成:正确配置SpringProcessEngineConfiguration以支持表达式和Bean解析。转载自https://www.cnblogs.com/songweipeng/p/18820536
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba</artifactId> <version>2023.0.1.2</version> <relativePath/> </parent> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-backend</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>intelligent-teaching-commons</module> <module>intelligent-teaching-user</module> <module>intelligent-teaching-chat</module> <module>intelligent-teaching-exam</module> <module>intelligent-teaching-chat/intelligent-teaching-chat-api</module> <module>intelligent-teaching-chat/intelligent-teaching-chat-shared</module> <module>intelligent-teaching-exam/intelligent-teaching-exam-api</module> <module>intelligent-teaching-exam/intelligent-teaching-exam-shared</module> <module>intelligent-teaching-oss</module> <module>intelligent-teaching-job</module> <module>intelligent-teaching-search</module> <module>intelligent-teaching-ai</module> </modules> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-ai.version>1.0.3</spring-ai.version> </properties> <repositories> <repository> <id>aliyunmaven</id> <url>https://maven.aliyun.com/repository/public</url> </repository> </repositories> <dependencies> <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-commons</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.4</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.1.2</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> <scope>compile</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-annotation</artifactId> <version>3.5.9</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.23</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.6.0</version> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.33</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.zxing/core --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.17.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.minio/minio --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.13</version> </dependency> <!-- https://mvnrepository.com/artifact/io.netty/netty-all --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.115.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.43.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.4.2</version> </dependency> <!-- https://mvnrepository.com/artifact/co.elastic.clients/elasticsearch-java --> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.17.1</version> </dependency> <dependency> <groupId>com.luhuiguo</groupId> <artifactId>aspose-words</artifactId> <version>23.1</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>3.0.4</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-core</artifactId> <version>9.1.0</version> <type>pom</type> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.12.1</version> </dependency> <dependency> <groupId>io.springboot.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>${spring-ai.version}</version> </dependency> <dependency> <groupId>io.springboot.ai</groupId> <artifactId>spring-ai-ollama</artifactId> <version>${spring-ai.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.32.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.milvus/milvus-sdk-java --> <dependency> <groupId>io.milvus</groupId> <artifactId>milvus-sdk-java</artifactId> <version>2.5.10</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-oss</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-job</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-search</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-ai</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-chat-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-exam-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-user-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.4</version> </dependency> </dependencies> </dependencyManagement> </project>
-
在Java中,当你尝试从Map<String, Object>中移除一个不存在的键时,不会抛出异常。Map接口的remove方法被设计为安全地处理这种情况 - 如果键不存在,方法会简单地返回null(或指定的默认值,如果使用remove(Object key, V defaultValue)方法),而不会抛出任何异常。示例代码:import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { Map<String, Object> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", 123); // 尝试移除存在的键 Object removedValue = map.remove("key1"); System.out.println("Removed value: " + removedValue); // 输出: Removed value: value1 // 尝试移除不存在的键 Object nonExistentValue = map.remove("nonExistentKey"); System.out.println("Removed value: " + nonExistentValue); // 输出: Removed value: null // 使用Java 8+的remove方法,可以指定默认值 Object defaultValue = map.remove("anotherNonExistentKey", "default"); System.out.println("Removed value with default: " + defaultValue); // 输出: Removed value with default: default } } 关键点:标准remove方法:map.remove(key) - 如果键存在,返回对应的值;如果键不存在,返回null。带默认值的remove方法(Java 8+):map.remove(key, defaultValue) - 如果键存在,返回对应的值;如果键不存在,返回指定的默认值。不会抛出异常:无论键是否存在,remove方法都不会抛出异常。因此,你可以安全地尝试移除任何键,而不必担心会因为键不存在而导致程序抛出异常。
-
要使用Hutool生成一个包含0到10的随机顺序的数组,你可以按照以下步骤操作:首先创建一个包含0到10的有序数组然后使用ArrayUtil.shuffle方法打乱数组顺序以下是完整的代码示例:import cn.hutool.core.util.ArrayUtil; public class Main { public static void main(String[] args) { // 创建0-10的有序数组 Integer[] array = ArrayUtil.range(0, 11); // 包含0和10 // 打乱数组顺序 ArrayUtil.shuffle(array); // 输出结果 System.out.println(ArrayUtil.toString(array)); } } 代码说明:ArrayUtil.range(0, 11) 创建一个包含0到10(包括0和10)的Integer数组ArrayUtil.shuffle(array) 打乱数组的顺序ArrayUtil.toString(array) 将数组转换为字符串形式输出输出示例:每次运行程序都会得到不同的随机顺序,例如:[5, 2, 9, 0, 7, 10, 3, 1, 4, 6, 8] 注意事项:确保项目中已经引入了Hutool的依赖如果你使用的是基本类型数组(如int[]),需要先转换为Integer[]或使用其他方法ArrayUtil.range方法在Hutool 5.x版本中可用,如果你使用的是较旧版本,可以手动创建数组:Integer[] array = new Integer[11]; for (int i = 0; i < 11; i++) { array[i] = i; } ArrayUtil.shuffle(array);
-
方法一:使用ArrayUtil.shuffle方法import cn.hutool.core.util.ArrayUtil; public class Main { public static void main(String[] args) { Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 打乱数组顺序 ArrayUtil.shuffle(array); // 输出打乱后的数组 System.out.println(ArrayUtil.toString(array)); } } 方法二:使用ArrayUtil.shuffle方法并指定随机数生成器import cn.hutool.core.util.ArrayUtil; import java.util.Random; public class Main { public static void main(String[] args) { String[] array = {"A", "B", "C", "D", "E"}; Random random = new Random(); // 使用指定的随机数生成器打乱数组 ArrayUtil.shuffle(array, random); System.out.println(ArrayUtil.toString(array)); } } 方法三:对于基本类型数组Hutool也支持基本类型数组的打乱操作:import cn.hutool.core.util.ArrayUtil; public class Main { public static void main(String[] args) { int[] intArray = {1, 2, 3, 4, 5}; // 打乱基本类型数组 ArrayUtil.shuffle(intArray); System.out.println(ArrayUtil.toString(intArray)); } } 注意事项ArrayUtil.shuffle方法会直接修改原始数组,而不是返回一个新的数组对于对象数组和基本类型数组都适用默认使用Random类作为随机数生成器,但可以自定义替代方案如果你不想使用Hutool,也可以使用Java标准库实现:import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; public class Main { public static void main(String[] args) { Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 转换为List后打乱顺序 List<Integer> list = Arrays.asList(array); Collections.shuffle(list); list.toArray(array); // 如果需要,可以转换回数组 System.out.println(Arrays.toString(array)); } } Hutool的ArrayUtil.shuffle方法提供了更简洁的方式来实现数组乱序操作。
-
Java 引入虚拟线程(Virtual Threads)后,确实显著提升了高并发 I/O 密集型任务的性能,使其在并发编程模型上更接近 Go 的 Goroutine,但性能是否与 Go 相当仍需从多个维度综合分析。以下是两者的对比和关键差异:一、虚拟线程 vs. Goroutine:相似性轻量级线程模型虚拟线程:由 JVM 管理,用户态线程,依赖少量操作系统线程(通过协作式调度)。Goroutine:由 Go 运行时管理,用户态线程,依赖 M:N 调度器(用户态线程与操作系统线程的动态映射)。共同点:两者均通过减少线程栈大小和优化调度,支持高并发(数百万级)。同步编程模型虚拟线程:保持同步代码风格(如 try-with-resources 管理执行器)。Goroutine:同样支持同步风格(通过 go 关键字启动协程)。共同点:开发者无需显式处理异步回调,代码更简洁。阻塞行为优化虚拟线程:阻塞时释放底层操作系统线程,避免闲置。Goroutine:类似机制,阻塞时调度器将协程挂起,切换到其他就绪协程。共同点:两者均通过协作式调度减少线程切换开销。二、性能差异分析尽管虚拟线程和 Goroutine 在设计上类似,但实际性能可能因以下因素存在差异:1. 调度器实现Java 虚拟线程:基于 JVM 的调度器,依赖操作系统线程池(如 ForkJoinPool 的变种)。调度策略可能不如 Go 运行时精细(Go 的调度器经过多年优化,对协程的调度效率极高)。Go Goroutine:内置的 M:N 调度器直接集成在运行时中,对协程的挂起、恢复和负载均衡有更精细的控制。调度器能动态调整协程与操作系统线程的映射关系,适应不同场景。2. 内存占用虚拟线程:默认栈大小可通过 -XX:VirtualThreadStackSize 配置(默认 1MB,但可动态压缩)。栈内存占用可能略高于 Goroutine(Go 的栈初始大小通常为 2KB,动态扩展)。Goroutine:极小的初始栈大小(2KB)和高效的动态扩展机制,进一步降低内存开销。3. 启动与销毁开销虚拟线程:创建和销毁速度较快,但仍需 JVM 层面的调度开销。Goroutine:创建和销毁几乎无开销(通过运行时直接管理)。4. 垃圾回收(GC)影响Java:虚拟线程与 JVM 的 GC 紧密集成,GC 暂停可能影响高并发任务的响应时间。Go:并发垃圾回收(Concurrent GC)设计更轻量,对协程的干扰较小。5. 生态与工具链Java:虚拟线程是 Java 21 的新特性,生态工具(如监控、调试)可能需要时间适配。Go:Goroutine 是 Go 的核心特性,生态工具(如 pprof、trace)对其支持完善。三、性能对比的结论I/O 密集型任务虚拟线程:性能接近 Goroutine,尤其在 Java 21 优化后(如减少调度开销、优化栈内存)。Goroutine:仍可能略胜一筹,因其调度器更成熟、内存占用更低。CPU 密集型任务Java:普通线程仍是更优选择(虚拟线程无法直接提升 CPU 计算效率)。Go:Goroutine 适合并发 CPU 任务,但需注意 Goroutine 的调度开销可能影响密集计算。综合性能Go:在轻量级协程、调度效率和生态工具链上仍有优势。Java:虚拟线程显著缩小了差距,尤其在需要与现有 Java 生态(如 Spring、数据库驱动)集成的场景中。四、何时选择 Java 虚拟线程 vs. Go?场景推荐选择原因高并发 I/O 密集型Java 虚拟线程需与现有 Java 生态集成,或团队熟悉 Java。超轻量级协程需求Go Goroutine需要极致的并发性能和更低的内存占用。CPU 密集型任务Java 普通线程Java 的 JIT 优化和原生线程对 CPU 密集型任务更高效。跨平台与生态JavaJava 生态更丰富(如企业级应用、大数据工具)。快速开发与部署GoGo 的编译速度、部署简单性和内置工具链更适合微服务和小型项目。五、总结Java 虚拟线程的引入确实让 Java 在高并发 I/O 密集型场景中具备了与 Go 竞争的能力,但**性能是否“差不多”**取决于具体场景:优势场景:Java 虚拟线程在需要与现有 Java 生态集成的场景中表现优异。Go 的优势:在超轻量级协程、调度效率和生态工具链上仍有领先。未来,随着 JVM 对虚拟线程的进一步优化(如减少调度开销、改进 GC),两者性能差距可能会进一步缩小。但目前,选择仍需根据团队技能、项目需求和生态兼容性综合决定。
-
Java 虚拟线程(Virtual Threads)是 Java 19 引入的一项重大特性(预览版,Java 21 正式发布),旨在简化高并发编程。它与普通线程(也称为平台线程,Platform Threads)在实现机制、资源占用和使用场景上有显著区别。以下是两者的详细对比:一、核心区别维度虚拟线程(Virtual Threads)普通线程(Platform Threads)实现机制由 JVM 管理,用户态线程(User-Mode Threads),依赖操作系统线程池调度。直接映射到操作系统线程(OS Threads),由内核调度。资源占用每个虚拟线程仅占用少量内存(KB 级别),可创建数百万甚至更多。每个普通线程占用大量内存(MB 级别,默认栈大小通常为 1MB),数量受限(通常数千)。调度方式协作式调度(Cooperative Scheduling),由 JVM 调度器在阻塞操作时挂起/恢复。抢占式调度(Preemptive Scheduling),由操作系统内核调度。阻塞行为阻塞时释放底层操作系统线程,避免线程闲置。阻塞时操作系统线程被占用,无法执行其他任务。使用场景高并发 I/O 密集型任务(如 Web 服务器、微服务)。CPU 密集型任务或需要直接操作系统资源的场景。二、深入对比1. 资源占用与并发能力普通线程:每个线程需要独立的操作系统线程,占用大量内存(如栈空间、线程局部存储等)。线程数量受限于操作系统和硬件(通常数千个线程会导致性能下降)。虚拟线程:每个虚拟线程仅占用少量内存(栈大小可通过 -XX:VirtualThreadStackSize 配置,默认 1MB,但可动态压缩)。可轻松创建数百万个虚拟线程,适合高并发场景。2. 调度与阻塞行为普通线程:阻塞操作(如 I/O、网络请求)会导致操作系统线程被占用,无法执行其他任务。线程池(如 ThreadPoolExecutor)通过复用线程缓解此问题,但需手动管理线程池大小。虚拟线程:阻塞时自动释放底层操作系统线程,允许其他虚拟线程复用。阻塞操作完成后,JVM 调度器恢复虚拟线程的执行。3. 性能与吞吐量普通线程:高并发时,线程切换开销大,可能导致 CPU 资源浪费(如线程等待 I/O 时仍占用 CPU)。虚拟线程:减少线程切换开销,提高吞吐量(尤其在 I/O 密集型任务中)。示例:一个普通线程池可能因线程阻塞导致吞吐量下降,而虚拟线程可充分利用 CPU。4. 编程模型普通线程:需显式管理线程生命周期(如 start()、join())。异步编程(如 CompletableFuture)需回调或协程,代码复杂。虚拟线程:保持同步编程模型(如 try (var executor = Executors.newVirtualThreadPerTaskExecutor()))。无需异步回调,代码更简洁。三、代码示例1. 普通线程示例// 使用线程池管理普通线程 ExecutorService executor = Executors.newFixedThreadPool(10); // 线程数量受限 for (int i = 0; i < 1000; i++) { executor.submit(() -> { try { Thread.sleep(1000); // 阻塞操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); 问题:线程池大小固定,高并发时需排队或拒绝任务。2. 虚拟线程示例// 使用虚拟线程执行器(Java 21+) try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 1_000_000; i++) { // 轻松创建百万级线程 executor.submit(() -> { try { Thread.sleep(1000); // 阻塞时释放底层线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } } // 自动关闭执行器 优势:无需管理线程池大小,阻塞时自动释放资源。四、适用场景虚拟线程:高并发 I/O 密集型任务(如 Web 服务器、微服务、爬虫)。需要简化异步编程的场景。普通线程:CPU 密集型任务(如科学计算、图像处理)。需要直接操作系统资源(如文件锁、硬件设备)。五、总结特性虚拟线程普通线程资源占用低(KB 级别)高(MB 级别)并发能力高(数百万级)低(数千级)调度方式协作式(JVM 调度)抢占式(内核调度)阻塞行为释放底层线程占用线程编程模型同步(类似单线程)显式线程管理或异步回调适用场景I/O 密集型CPU 密集型或直接操作系统资源六、为什么选择虚拟线程?简化高并发编程:无需异步回调,代码更简洁。资源高效利用:减少线程切换开销,提高吞吐量。弹性扩展:轻松应对突发流量(如 Web 请求)。虚拟线程是 Java 对高并发编程的重大改进,尤其适合现代云原生和微服务架构。然而,对于 CPU 密集型任务,普通线程仍是更优选择。
-
显示文件catalina.sh 必须可执行
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签