-
pagehelper查询慢是因为,这个插件会自动在原来的查询逻辑上加一个count的sql,导致整体查询变慢。推荐方案是覆盖原来的SQL,重新自己写一个count sql覆盖只需要在查询的mapper文件中,写一个已 _COUNT结尾的查询就会覆盖默认的。selectActivityList 是查询语句的名称
匿名用户
发表于2022-09-21 16:22:22
2022-09-21 16:22:22
最后回复
匿名用户
2022-09-21 16:22:22
50 0 -
SDK是要下载本地jar包还是maven直接引入的SDK,哪里可以找到 #java
-
开源项目地址:cid:link_0当我们写了一个YANG文件,想要验证一下YANG文件正确性的时候,所有的YANG解析器都会要求用户准备好该YANG文件的依赖文件,比如a模块依赖了b模块,那么在编译的时候用户需要自己去找到b模块的文件,但是YANG文件的依赖关系非常复杂,用户很难一下子找全,这样就会经常出现编译失败的事情。yang compiler支持自动分析YANG文件依赖关系,当编译目录不存在依赖文件时,会自动从网络上下载匹配的YANG文件(目前默认从yang catalog上下载),这些文件可以缓存在用户本地目录中,下次再用到时就无需再次下载了。这样yang compiler就支持无论待编译文件在哪里,都可以成功编译YANG文件,无需自己寻找依赖文件。另外yang compiler还支持插件系统,开发者可以按照yang compiler的要求开发个性化的扩展功能,比如生成yang tree或者根据YANG生成java或者python的类等等。
-
本文导读 中秋节是中国民间的传统节日,中秋节源自天象崇拜由上古时代秋夕祭月演变而来。中秋节自古便有祭月、赏月、吃月饼等民俗,流传至今,经久不息。 说到月饼,我们现在吃的都是工厂、小作坊、自己家里制作,在电商平台售卖或者有一些营销活动赠送,我们来看看Java是怎么制作月饼的,我们是否可以在代码实现一个月饼售卖的架构设计和设计模式。 借此机会,我们用Lambda实现一遍月饼制作,下单,售卖的开发设计模式,主要有制作月饼的工厂模式、 一、使用工厂模式制作月饼 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思维方式。使用工厂模式,我们就无需向消费者们暴露,我们的月饼(实例化月饼对象)的制作逻辑,就能完成月饼的制作。 1、使用工厂创建月饼实例 我们需要一个月饼工厂(MoonCakeFactory)或者叫月饼小作坊,在工厂里我们可以创建一个map 将产品名映射到对应的构造函数中,提供一个像工厂模式一样,利用map来实例化不同对象。 /** * 一个简单的月饼制作工厂 * @author XiaoMing */ public class MoonCakeFactory { /** * 我们可以创建一个map 将产品名映射到对应的构造函数中 */ final static Map> map = new HashMap<>(); static { // 五仁的,我的最爱 map.put("wuRen", WuRenMoonCakeInfo::new); // 豆沙 map.put("redBeanPaste", RedBeanPasteMoonCakeInfo::new); // 枣泥 map.put("jujubePaste", JujubePasteMoonCakeInfo::new); // 蛋黄的 map.put("eggYolk", EggYolkMoonCakeInfo::new); } /** * 像工厂模式一样,利用map来实例化不同对象 * * @param moonCakeType 月饼类型 */ public static MoonCakeInfo createMoonCake(String moonCakeType) { // 获取月饼供应商 Supplier moonCakeInfoSupplier = map.get(moonCakeType); if (null != moonCakeInfoSupplier) { return moonCakeInfoSupplier.get(); } throw new IllegalArgumentException("No such MoonCakeInfo" + moonCakeType); } } /** * @author XiaoMing */ public class WuRenMoonCakeInfo extends MoonCakeInfo { private String wuRen; public String getWuRen() { return wuRen; } public void setWuRen(String wuRen) { this.wuRen = wuRen; } } 2、工厂模式剖析 现在我们就可以试着拿到对应的,实例化的月饼对象了 WuRenMoonCakeInfo wuRen = (WuRenMoonCakeInfo) createMoonCake("wuRen"); 我们看完了这个例子,现在思考内部的问题,这么做有什么好处?为什么createMoonCake是一个static的方法? 我们制作月饼有很多馅,除了馅我们还可以取对应馅(moonCakeType)的时候,就可以获取到一个实例化的对象,不需要我们在使用的地方创建,将工厂定义为静态方法是一个常用技巧,称为静态工厂,不需要创建方法来实例化对象,将实例化对象和创建方法解耦。 总结一下,工厂模式是用一个专业类(工厂类)来负责一种产品的对象创建。这样做的好处是,1、把对象的创建和使用分开;2、将生产过程集中后,便于集中管理(增删改);3、当实体类有变动时,使用者不需要再去修改代码。 二、使用策略模式限制购买月饼类型 一个礼盒中有很多类型的月饼,有些馅并不我待见,博主曾经吃过一个咖啡馅月饼,这里面我们把奇奇怪怪的馅扣除去。校验是否为有效订单,设置该策略不允许咖啡馅月饼和榴莲馅的下单 1、实战代码 策略模式可以理解为一种通过算法解决一类问题的通用方案,策略模式包括该算法的接口,一个或多个接口的实现逻辑,以及策略对象,下面我们用lambda表达式实现,Validator 相当于一个中转站,strategy.validatorOrder(orderType);可以通过下属lambda实现,也可以通过实现类,继承Strategy 接口实现 /** * 假设为下单主流程 * 我们主要关注 策略模式下订单类型的校验,这几个步骤 */ public void submitOrder(OrderInfo orderInfo) { // ... // 校验是否为有效订单,设置该策略不允许咖啡馅月饼和榴莲馅的下单 Validator v1 = new Validator(orderType -> !orderType.equals("Coffee") || !orderType.equals("Durian")); v1.validatorOrder(orderInfo.getOrderType()); // ... } /** * @author XiaoMing */ public class Validator { private Strategy strategy; public Validator(Strategy strategy) { this.strategy = strategy; } public boolean validatorOrder(String orderType) { return strategy.validatorOrder(orderType); } } /** * @author XiaoMing */ public interface Strategy { /** * 函数式接口,通过调用 Validator时实现 */ boolean validatorOrder(String orderType); } 一般我们也可以使用实现类实现 /** * @author xiaoming * @date 2022/8/27-10:33 */ public class RealOrderImpl implements Strategy { @Override public boolean validatorOrder(String orderType) { System.out.println("real"); return orderType.equals("real"); } } public class O2OOrderImpl implements Strategy { @Override public boolean validatorOrder(String orderType) { System.out.println("O2O"); return orderType.equals("O2O"); } } 2、策略模式剖析 策略模式需要做的就是当请求进来时候,同一个入口让他根据这个人请求的行为去执行其中某一个类中的方法。 策略接口的定义,通常包含两个方法:获取策略类型的方法和处理策略业务逻辑的方法。策略接口的实现,每种支付类都实现了上述接口(基于接口而非实现编程),这样我们可以灵活的替换不同的支付方式(上文代码段就是使用lambda表达式实现的)。 接口只负责业务策略的定义,每个策略的具体实现单独放在实现Impl中,我们可以使用工厂类 Factory 只负责获取具体实现类,而具体调用代码则负责业务逻辑的编排。这些实现用到了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。 三、模板方法模式设计月饼的优惠决策 我们购买月饼在电商业务中有很多优惠信息,例如红包、平台券、商家券、满减、秒杀活动等等, 如果每个优惠写一个方法那么就不方便阅读和管理,我们可以用这种方式将代码扩展性做的很好假设为下单主流程,我们主要关注 优惠决策与核销->优惠信息落库,这几个步骤。 1、使用模板方法实现优惠活动 让我们从模板方法模式设计月饼的优惠决策的例子着手,看看这个模式是如何工作的。 /** * 假设为下单主流程 * 我们主要关注 优惠决策与核销->优惠信息落库,这几个步骤 */ public void submitOrder(List orderList) { // ... // 优惠决策与核销 writeOff(orderList, couponInfos -> { // 将优惠信息落库 transaction(orderList); }); // ... } /** * 我们购买月饼在,电商业务中有很多优惠信息,例如红包、平台券、商家券、满减、秒杀活动等等 * 如果每个优惠写一个方法那么就不方便阅读和管理,我们可以用这种方式将代码扩展性做的很好 */ public void writeOff(List orderList, Consumer> consumer) { // 每个优惠信息,由自己的实现类实现 List couponInfos = consume(orderList); // 此处将结果保存 consumer.accept(couponInfos); } /** * 每个优惠信息自己实现 ServiceImpl */ private List consume(List orderList) { List couponInfos = new ArrayList<>(); // 核销后返回信息(实现业务逻辑) // ... return couponInfos; } /** * 将优惠信息落库 */ private static void transaction(List orderInfos) { } 2、模板方法剖析 模板方法模式,如果你需要采用某个算法的框架,同时又希望有一定的灵是活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。这样讲有些抽象。换句话说,模板方法模式在你希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果时是非常有用的。 优点,1、提高代码复用性,可以将相同部分的代码放在抽象的父类中;2、提高了拓展性,将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为;3、实现了反向控制,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为。 缺点,1、引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。 场景,1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;2、各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。 总结 借此中秋节机会,我们实现一个月饼售卖的设计模式。主要使用了Lambda和设计模式的思想,实现月饼制作的工厂模式,下单的策略模式校验,优惠券操作的模板方法模式的开发。 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。 ———————————————— 版权声明:本文为CSDN博主「小明java问道之路」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/FMC_WBL/article/details/126533844
-
描述 只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。 通常 1 为第一个丑数,求按从小到大的顺序的第 n 个丑数。 示例 输入:n = 10 输出:12 解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。 题解 1. 小顶堆 要得到从小到大的第 n 个丑数,可以使用最小堆实现。 初始时堆为空。首先将最小的丑数 1 加入堆。 每次取出堆顶元素 x,即堆中最小的丑数,由于 2x, 3x, 5x 也是丑数,因此将 2x, 3x, 5x 加入堆。 上述做法会导致堆中出现重复元素的情况。为了避免重复元素,可以使用哈希集合去重,避免相同元素多次加入堆。 在排除重复元素的情况下,第 n 次从最小堆中取出的元素即为第 n 个丑数。 时间复杂度:O ( n log n ) O(n \log n)O(nlogn)。得到第 n 个丑数需要进行 n 次循环,每次循环都要从最小堆中取出 1 个元素以及向最小堆中加入最多 3 个元素,因此每次循环的时间复杂度是 O ( log ( 3 n ) + 3 log ( 3 n ) ) O(\log (3n) + 3 \log (3n))O(log(3n)+3log(3n)),总时间复杂度是 O ( n log n ) O(n \log n)O(nlogn)。 空间复杂度:O ( n ) O(n)O(n)。空间复杂度主要取决于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不会超过 3n。 class Solution { public: int nthUglyNumber(int n) { vector factors = {2, 3, 5}; // 因子 unordered_set hash; // 哈希 priority_queue, greater> heap; // 小顶堆 hash.insert(1L); heap.push(1L); int uglyn = 1; for(int i = 1; i <= n; ++i){ long top = heap.top(); heap.pop(); uglyn = (int)top; // 第n个丑数 for(int factor : factors){ // 堆中最小的丑数 * 因子2/3/5 long next = top * factor; if(hash.count(next) == 0){ hash.insert(next); heap.push(next); } } } return uglyn; } }; 2. 动态规划 方法一使用最小堆,会预先存储较多的丑数,维护最小堆的过程也导致时间复杂度较高。 可以使用动态规划的方法进行优化。 定义数组 uglys,其中 uglys[i-1] 表示第 i 个丑数。 uglys[0] = 1; 如何得到其余的丑数呢? 每个丑数都有一次与 2/3/5 相乘的机会,定义三个指针 p2、p3、p5,表示下一个丑数是三个指针指向的丑数乘以对应的质因数得到的最小值。初始时,三个指针的值都是 0,每轮将对应指针执行 +1 即可。 uglys[i] = min(min(num2, num3), num5); 在进行指针判断移动操作时,对每个指针得到的结果都进行一次对比,去除重复操作,比如 5*2 与 2*5,那么 p2 和 p5 都右移。 时间复杂度:O(n)。需要计算数组中的 n 个元素,每个元素的计算都可以在 O(1) 的时间内完成。 空间复杂度:O(n) class Solution { public: int nthUglyNumber(int n) { vector uglys(n, 1); int p2 = 0, p3 = 0, p5 = 0; for(int i = 1; i < n; ++i){ int num2 = uglys[p2]*2, num3 = uglys[p3]*3, num5 = uglys[p5]*5; uglys[i] = min(min(num2, num3), num5); if(uglys[i] == num2) ++p2; if(uglys[i] == num3) ++p3; if(uglys[i] == num5) ++p5; // 不用else,去重,2*5 跟 5*2的情况,p2与p5++ } return uglys[n-1]; } }; ———————————————— 版权声明:本文为CSDN博主「keep_fan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_19887221/article/details/126689247
-
1、基本查找方法 基本查找核心:从0索引开始挨个往后查找 需求:定义一个方法,利用基本查找,查询某个元素是否存在 代码如下 /** * @authorDesc * @author * @date * @version 1.0.0 * @description 基本查找 * 核心:从0索引开始挨个往后查找 * 需求:定义一个方法,利用基本查找,查询某个元素是否存在 */ public class BasicSearch { public static void main(String[] args) { int[] numArray = {1,2,3,4,5,6,4,4}; int number = 4; System.out.println(search(numArray,number)); } /** * @description * @author * @date * @param numArray 数组 * @param number 要查找的数字 * @retur {@link int} 返回元素下表 */ public static int search(int[]numArray,int number){ for (int i = 0; i < numArray.length; i++) { if (numArray[i] == number){ return i; } } return -1; } 需求:定义一个方法,利用基本查找,查询某个元素是否存在,如果有重复的元素,把所有元素找出 /** * @description * @author * @date * @param numArray 数组 * @param number 要查找的数字 * @return {@link List} 返回元素下标集合 */ public static List search(int[]numArray,int number){ List listnum = new ArrayList<>(); for (int i = 0; i < numArray.length; i++) { if (numArray[i] == number){ listnum.add(i); } } return listnum; } } 2、二分查找 前提条件:数组中的数据必须是有序的 核心:每次排除一半的查找范围 代码如下 /** * @authorDesc * @author * @date * @version 1.0.0 * @description 二分查找 * 核心:每次排除一半的查找范围 * 需求:定义一个方法利用二分查找,查询某个元素是否在数组中 */ public class BinarySearch { public static void main(String[] args) { int [] numArray = {1,2,3,4,5,6}; int number = 3; System.out.println(search(numArray,number)); } /** * @description * @author * @date * @param numArray 数组 * @param number 查找的数字 * @return {@link int} 返回在数组中的下标 */ public static int search(int [] numArray, int number){ //定义两个变量记录要查找的范围 min数组第一个元素的索引,max 数组最后一个元素的索引 int min = 0; int max = numArray.length - 1; //利用循环查找 while (true){ if (min > max){ return -1; } //找到min和max的中间值 int mid = (min + max) / 2; // 用mid 指向的元素和要查找的元素进行比较 if (numArray[mid] > number){ //要查找的数字在mid的左边 //min不变, max = mid - 1 max = mid - 1; }else if (numArray[mid] < number){ //要查找的数字在mid的右边 //max不变,min = mid + 1 min = mid + 1; }else { //查找的元素和mid指向得到元素一样 return mid; } } } } 二分查找的优势:提高查找效率 二份查找条件:1、数据必须是有序的 2、如果数据是乱的,先排序再用二分法查找得到的索引没有意义,只能确定当前数字在数组中是否存在,因为排序后的数字位置发生了变化 二分查找的过程:min和max表示当前要查找的范围 mid是在min和max中间的数字 如果要查找的元素在mid左边,缩小范围时,min不变,max等于mid减1 如果要查找的元素在mid右边,缩小范围时,max不变,min等于mid减1 3、插值查找 //插值查找 int mid = min + (number - numArray[min]) /(numArray[max] - numArray[min]) * (max - min); 和二分查找相似,将中间值mid改为: int mid = min + (number - numArray[min]) /(numArray[max] - numArray[min]) * (max - min); 4、分块查找 原则:1、前一块中的最大数据,小于后一块中的所有数据(块内无序,块间有序) 2、块数数量一般等于元素个数的开根号,比如16个数字一般分为4块 核心:先确定要查找的元素在哪一块,然后在挨个查找 代码如下 /** * @authorDesc * @author * @date * @version 1.0.0 * @description 使用分块查找数组中的元素 */ public class BlockSearch { public static void main(String[] args) { int[] numArray = {2,3,1,4, 8,6,7,5, 9,11,10,12, 15,14,13,16}; ———————————————— 版权声明:本文为CSDN博主「qi3415」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qi341500/article/details/126688184
-
一 最小字典序 问题:给定一个字符串(仅包含小写字母),只允许交换一次两个任意位置的字符,输出这样交换的最小的字符串。 要求:仅允许一次交换 100%通过。 输入: abcdfe 输出: abcdef 输入: aaaafhd 输出: aaaadhf 思路:将字符串分为字符数组进行排序,将排序后的数组与原数组对比,出现不同时就是交换的下标,首相更新的那个钱下表为排序后当前下标的字符,关键是和那个位置的字符交换,因为是和排序后的字符比较,所以要交换的字符肯定大于原字符串中当前下标的字符,所以为了使得字符串的字典序最小(从左到右字母依次变大),要从字符串末位开始寻找(这里让我头疼了半天)。 package HuaWeiOD; import java.util.Arrays; import java.util.Scanner; /** * @ClassName Problem11 * @Description TODO * @Author shenxinyuan * @Date 2022/8/27 $ {TIME} * @Version 1. 0 **/ public class Problem1 { //100% public static void main(String[] args) { Scanner scan = new Scanner(System.in); String str = scan.next(); char[] chars = new char[str.length()]; char[] ret = new char[str.length()]; for (int i = 0; i < str.length(); i++) { chars[i] = str.charAt(i); ret[i] = str.charAt(i); } Arrays.sort(chars); //最小字典序 chars for (int i = 0; i < ret.length; i++) { if (ret[i] != chars[i]) { char tmp = ret[i]; ret[i] = chars[i]; for(int j =ret.length-1;j>=i+1;j--){ //很关键 要倒序寻找那个字符,保证一次交换的情况下整个串的字典序是最小 if(ret[j] == chars[i]){ ret[j] = tmp; break; } } break; } } for(int i =0;i System.out.print(ret[i]); } } } 二 第k长的连续子串的长度 这个题目仅仅通过了70% 问题:给定一个字符串,字符串(仅包含大写字母)中存在有相同字符的连续字串,再输入一个数字k,输出字符串中第k长的有重复字符的字串长度。 要求:同一个字母值计算呢一个重复字串的最大长度。还有,如果k的值大于含有重复字符的字串的数量,输出-1。 输入: AAAAHHHBBCDHHHH 3 输出: 2 解释: H字符出现了两个字串,仅计算最长的长度4,长度为3的字串跳过,因此结果为BB串,长度为2 思路:用字符数组记录包含重复字符的最长字串的长度(每一个字母只能记录最长字串长度的数值),最后排序,输出对应第k大的字串长度 package HuaWeiOD; /** * @ClassName Problem22 * @Description TODO * @Author shenxinyuan * @Date 2022/8/27 $ {TIME} * @Version 1. 0 **/ import java.util.*; public class Problem22 { //70% public static void main(String[] args) { Scanner scan = new Scanner(System.in); int[] arr = new int[90]; String str = scan.next(); int k = scan.nextInt(); int tmp = 1; arr[str.charAt(0)]++; for (int i = 1; i < str.length(); i++) { if (str.charAt(i) == str.charAt(i - 1)) { tmp++; } else { tmp = 1; } //以下代码块每次循环都执行 也许是这里导致剩余的30用例没有通过 { if (arr[str.charAt(i)] != 0) { //如果是相同字母,更新长度 arr[str.charAt(i)] = Math.max(tmp, arr[str.charAt(i)]); } else { //没有出现过最长连续字串,直接记录 arr[str.charAt(i)] = tmp; } } } int num = 0; //记录字串数目 for (int i = 0; i < arr.length; i++) { if (arr[i] > 0) { num++; } } //升序 Arrays.sort(arr); System.out.println(num >= k ? arr[arr.length - k] : -1); } } 三 两个人均分积木重量 没来得及调试通过。自测符合预期。 问题:koko和它的兄弟的积木重量必须一样,否则koko会哭泣,按照koko的理解两个数字的加法为:两个数字的二进制对应相加的过程中不计权重(也就是每一位符合异或的计算方法)。例如:5 + 6 =11 koko的计算方式为 0101 + 0110 = 0011 (十进制的3,也就是koko认为5+6=3) 要求:koko认为两个人的积木重量“相同”的情况下,另一个人可以获得的最大重量 输入: 3 3 5 6 输出: 11 解释: koko认5+6“等于”3,此时koko拿3个,另一个人那5+6实际上是11个,输出结果为11 思路:首先要实现新的koko的计算方式,plus方法实现如下。然后要计算给定数组的全部子集,buildSubSet实现方法如下。在遍历全部子集比较记录koko认为重量相同的情况下:它的兄弟可获得的最大的实际重量。 package HuaWeiOD; /** * @ClassName Problem3 * @Description TODO * @Author shenxinyuan * @Date 2022/8/27 $ {TIME} * @Version 1. 0 **/ import com.sun.org.apache.xpath.internal.operations.Plus; import org.junit.Test; import java.lang.reflect.Array; import java.util.*; import static HuaWeiOD.ZiJi.buildSubSet; public class Problem3 { /* 测试用例: 输入: 3 3 5 6 输出: 11 5 + 6 = 11 但是koko认为 5+6=3 认为两个人的重量“相同”的情况下11最大 */ //相当于按照新的加法运算方式,计算最大的分段和 public static void main(String args[]) { Scanner scan = new Scanner(System.in); int n = scan.nextInt(); int[] arr = new int[n]; for(int i =0;i arr[i] = scan.nextInt(); } int newSuma = 0; int newSumb = 0; int max =0; int suma = 0; int sumb = 0; //全集 List> res = buildSubSet(arr); for(int i =0;i List tmp = res.get(i); for(int j =0;j if(tmp.contains(arr[j])){ newSuma = plus(newSuma,arr[j]); suma += arr[j]; }else { newSumb = plus(newSumb,arr[j]); sumb += arr[j]; } } if(newSuma!=0&&newSuma==newSumb){ max = Math.max(Math.max(suma,sumb),max); System.out.println("max = " + max); System.out.println("newSuma = " + newSuma); System.out.println("----------------"); } newSuma = 0; newSumb = 0; suma =0; sumb=0; } System.out.println(max); } //自测成功 @Test public void test(){ System.out.println("plus(5,6) = " + plus(5, 6)); // System.out.println("plus(3,5) = " + plus(3, 5)); } //按照koko的加法运算方式(即二进制加法情况加不做加权,新的二进制位是连个二进制位的异或) public static int plus(int a, int b) { StringBuilder tmp = new StringBuilder(); String stra = Integer.toBinaryString(a); String strb = Integer.toBinaryString(b); //记录最小的二进制位长度 int length = Math.min(stra.length(), strb.length()); for (int i = 0; i < length; i++) { //新的二进制位是连个二进制位的异或 tmp.insert(0,Integer.parseInt(String.valueOf(stra.charAt(stra.length()-1-i))) ^ Integer.parseInt(String.valueOf(strb.charAt(strb.length()-1-i)))); } //多余的二进制位头插如string int k = stra.length() > length ? stra.length() : strb.length(); String kk = stra.length() > length ? stra : strb; for (int i = length; i < k; i++) { tmp.insert(0, kk.charAt(kk.length()-1-i)); } //计算新的二进制串的十进制数值 int res = 0; for (int i = tmp.length() - 1; i >= 0; i--) { //方式一:自定义逻辑 二进制串转为十进制数值 int num = (int) (Integer.parseInt(String.valueOf(tmp.charAt(i))) * Math.pow(2, tmp.length() - i - 1)); res += num; //方式二:API实现 二进制串转为十进制数值 // res = Integer.parseInt(tmp.toString(),2); } return res; } public static List> buildSubSet(int[] nums){ List> result = new ArrayList<>(); //先添加一个空集 result.add(new ArrayList<>()); for (int i = 0;i //获取当前子集个数 int size = result.size(); //依次取出当前子集并为每一子集添加元素nums[i] //最后再添加回result for (int j = 0;j List clone = new ArrayList<>(result.get(j)); clone.add(nums[i]); result.add(clone); } } return result; } } ———————————————— 版权声明:本文为CSDN博主「FHLD666」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/sxy3180911/article/details/126563776
-
一、首先有两个开源工具https://github.com/everit-org/json-schema和https://github.com/java-json-tools/json-schema-validator 区别:从性能上来说everit完全是碾压fge,官方说的至少两倍,实际测试过程中,差不多有20倍的差距。虽然fge使用的是jackson json,相对来说学习成本可能较低,但是使用下来发现everit的使用也并不复杂,需要注意的是包需要导入正确(org.json)。fge唯一的优势在于错误信息比较详细。还有一点区别在于,everit验证失败是抛出异常,而fge是判断返回一个boolean类型的值。二、Spring boot 自定义注解集成jsonSchema 1.首先说一下ConstraintValidator这个类主要是用于验证bean里面的属性与@Valid或者@Validated注解配套使用,如果不加这个注解,那么你自定义的注解也不会生效,这一点很重要。 2.一般项目中controller方法中的参数都bean来接收,那么你就可以用ConstraintValidator来验证字段属性是否合法。那么如果controller传入的参数是Map对象就不能使用这个方法校验字段是否合法,这个时候就需要我们在代码里面写入一堆的if else判断,这样增加了代码的冗余。三、话不多说,直接上我写的校验代码案例: 第一步:创建注解类 package com.example.demo.config; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.PARAMETER,ElementType.METHOD}) public @interface JsonValid { String schemaName(); String message() default "Json数据校验失败"; }第二步:校验规则切面类 package com.example.demo.service; import com.alibaba.fastjson.JSONObject; import com.example.demo.config.JsonValid; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.everit.json.schema.Schema; import org.everit.json.schema.ValidationException; import org.everit.json.schema.loader.SchemaLoader; import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.io.InputStream; /** * @author zhouxiang * @describe * @create 2021/10/20 14:47 * @since 1.0.0 */ @Component @Aspect public class JsonValidatedAop { private final Logger logger = LoggerFactory.getLogger(JsonValidatedAop.class); @Pointcut("@annotation(com.example.demo.config.JsonValid)") private void pointcut() { } @Before("pointcut() && @annotation(jsonValid)") public void before(JoinPoint joinPoint, JsonValid jsonValid) { Object[] args = joinPoint.getArgs(); if(args != null) { String jsonString = JSONObject.toJSONString(args[0]); /** 验证的结果,可以返回,也可以写个全局捕捉的异常 */ String validMsg = validJson(jsonString, jsonValid.schemaName()); } logger.info("数据:"+ JSONObject.toJSONString(args) + jsonValid.schemaName()); } /** * 使用开源工具https://github.com/java-json-tools/json-schema-validator校验json * * @param jsonString 被校验数据 * @param schemaName 校验schema名称 * @return 返回失败消息,为空则校验成功 */ public String validJson(String jsonString, String schemaName) { StringBuilder sBuilder = new StringBuilder(); try { //InputStream inputStream = getClass().getResourceAsStream("/schema/hello.json"); //org.json.JSONObject rawSchema = new org.json.JSONObject(new JSONTokener(inputStream)); org.json.JSONObject rawSchema = new org.json.JSONObject(jsonString); InputStream in1 = getClass().getResourceAsStream("/schema/"+schemaName); org.json.JSONObject sSchema = new org.json.JSONObject(new JSONTokener(in1)); Schema schema = SchemaLoader.load(sSchema); schema.validate(rawSchema); } catch (ValidationException e) { logger.error(e.getMessage()); sBuilder.append(e.getMessage()); } return sBuilder.toString(); } }第三步:controller类 package com.example.demo.controller; import com.example.demo.config.JsonValid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * @author zhouxiang * @describe * @create 2021/8/23 11:08 * @since 1.0.0 */ @RestController public class ValidatorController { private final Logger logger = LoggerFactory.getLogger(ValidatorController.class); @PostMapping("/test1") @JsonValid(schemaName = "s.json") public Map test(@RequestBody Map map) { logger.info("接收的数据:"+map.toString()); Map responseMap = new HashMap<>(); responseMap.put("ResponseStatusListObject", "success"); return responseMap; } }第四步:测试数据 url:http://ip:8083/demo/test1 POST json数据: { "rectangle" : { "a" : 6, "b" : "21" } } Schema测试文件放在resources/schema/s.json: { "type" : "object", "properties" : { "rectangle" : {"$ref" : "#/definitions/Rectangle" } }, "definitions" : { "size" : { "type" : "number", "minimum" : 0 }, "Rectangle" : { "type" : "object", "properties" : { "a" : {"$ref" : "#/definitions/size"}, "b" : {"$ref" : "#/definitions/size"} } } } }作者:祥子_b5dd链接:https://www.jianshu.com/p/7044dad582b6
-
一、背景动态插件化编程是一件很酷的事情,能实现业务功能的 解耦 便于维护,另外也可以提升 可扩展性 随时可以在不停服务器的情况下扩展功能,也具有非常好的 开放性 除了自己的研发人员可以开发功能之外,也能接纳第三方开发商按照规范开发的插件。常见的动态插件的实现方式有 SPI、OSGI 等方案,由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象,例如主程序中已经集成了 Redis 但是在插件中无法使用。本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。二、热加载 jar 包通过指定的链接或者路径动态加载 jar 包,可以使用 URLClassLoader 的 addURL 方法来实现,样例代码如下:ClassLoaderUtil 类public class ClassLoaderUtil { public static ClassLoader getClassLoader(String url) { try { Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); if (!method.isAccessible()) { method.setAccessible(true); } URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader()); method.invoke(classLoader, new URL(url)); return classLoader; } catch (Exception e) { log.error("getClassLoader-error", e); return null; } }}其中在创建 URLClassLoader 时,指定当前系统的 ClassLoader 为父类加载器 ClassLoader.getSystemClassLoader() 这步比较关键,用于打通主程序与插件之间的 ClassLoader ,解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。三、动态注册 Bean将插件 jar 中加载的实现类注册到 Spring 的 IOC 中,同时也会将 IOC 中已有的 Bean 注入进插件中;分别在程序启动时和运行时两种场景下的实现方式。3.1. 启动时注册 Bean使用 ImportBeanDefinitionRegistrar 实现在 Spring Boot 启动时动态注册插件的 Bean,样例代码如下:PluginImportBeanDefinitionRegistrar 类public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { private final String targetUrl = "file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar"; private final String pluginClass = "com.plugin.impl.PluginImpl"; @SneakyThrows @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl); Class clazz = classLoader.loadClass(pluginClass); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); BeanDefinition beanDefinition = builder.getBeanDefinition(); registry.registerBeanDefinition(clazz.getName(), beanDefinition); }}3.2. 运行时注册 Bean程序运行时动态注册插件的 Bean 通过使用 ApplicationContext 对象来实现,样例代码如下:@GetMapping("/reload")public Object reload() throws ClassNotFoundException { ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl); Class clazz = classLoader.loadClass(pluginClass); springUtil.registerBean(clazz.getName(), clazz); PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName()); return plugin.sayHello("test reload");}SpringUtil 类@Componentpublic class SpringUtil implements ApplicationContextAware { private DefaultListableBeanFactory defaultListableBeanFactory; private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext; this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory(); } public void registerBean(String beanName, Class clazz) { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz); defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition()); } public Object getBean(String name) { return applicationContext.getBean(name); }}四、总结本文介绍的插件化实现思路通过 共用 ClassLoader 和 动态注册 Bean 的方式,打通了插件与主程序之间的类加载器和 Spring 容器,使得可以非常方便的实现插件与插件之间和插件与主程序之间的 类交互,例如在插件中注入主程序的 Redis、DataSource、调用远程 Dubbo 接口等等。但是由于没有对插件之间的 ClassLoader 进行 隔离 也可能会存在如类冲突、版本冲突等问题;并且由于 ClassLoader 中的 Class 对象无法销毁,所以除非修改类名或者类路径,不然插件中已加载到 ClassLoader 的类是没办法动态修改的。所以本方案比较适合插件数据量不会太多、具有较好的开发规范、插件经过测试后才能上线或发布的场景。作者:zlt2000链接:https://www.jianshu.com/p/5f37f4986fa9
-
当我们有业务需求的时候,需要判断当前时间是否为节假日有3种方式Jsoup 爬虫爬取 (万年日历查询 或者 360日历 百度日历)自己维护一套日历数据表(成本较高)调用开发 api 获取别人维护好的数据本文采用的是3api接口地址: http://timor.tech/api/holiday/ 较稳定可靠@Component public class HolidayUtil { @Autowired private RestTemplateUtil restTemplateUtil; @Autowired private RedisUtil redisUtil; /** * 获取是否节假日 * @return */ public Sting getNowDayHoliday(){ LocalDate now = LocalDate.now(); //我们利用redis 将数据存起来 String key = RedisConstant.HOLIDAY + now; if(redisUtil.hasKey(key)){ return redisUtil.get(key); }else{ String url = "http://timor.tech/api/holiday/info/" + now; Map headers = new HashMap<>(); headers.put("Content-Type", "application/json;charset=UTF-8"); //模拟真实请求 headers.put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"); ResponseEntity responseEntity = restTemplateUtil.get(url,headers, String.class); //json转换 Map map = JsonUtil.stringToCollect(responseEntity.getBody()); if(map.get("code").equals(0)){ redisUtil.set(key,map.get("type"),86400); return map.get("type"); }else{ return null; } } } }RestTemplateUtil: /** * 带请求头的GET请求调用方式 * * @param url 请求URL * @param headers 请求头参数 * @param responseType 返回对象类型 * @param uriVariables URL中的变量,按顺序依次对应 * @return ResponseEntity 响应对象封装类 */ public ResponseEntity get(String url, Map headers, Class responseType, Object... uriVariables) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setAll(headers); return get(url, httpHeaders, responseType, uriVariables); } /** * 带请求头的GET请求调用方式 * * @param url 请求URL * @param headers 请求头参数 * @param responseType 返回对象类型 * @param uriVariables URL中的变量,按顺序依次对应 * @return ResponseEntity 响应对象封装类 */ public ResponseEntity get(String url, HttpHeaders headers, Class responseType, Object... uriVariables) { HttpEntity requestEntity = new HttpEntity<>(headers); return exchange(url, HttpMethod.GET, requestEntity, responseType, uriVariables); /** * 通用调用方式 * * @param url 请求URL * @param method 请求方法类型 * @param requestEntity 请求头和请求体封装对象 * @param responseType 返回对象类型 * @param uriVariables URL中的变量,按顺序依次对应 * @return ResponseEntity 响应对象封装类 */ public ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Object... uriVariables) { return restTemplate.exchange(url, method, requestEntity, responseType, uriVariables); } }输出{"code":0,"type":{"type":0,"name":"周二","week":2},"holiday":null}链接:https://www.jianshu.com/p/c0c1cdd8bd00
-
##### LinkedHashMap LinkedHashMap此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接 列表。存储的数据是有序的【添加顺序】 public class LinkedHashMap extends HashMap implements Map 内部类 static class Entry extends HashMap.Node { Entry before, after; Entry(int hash, K key, V value, Node next) { super(hash, key, value, next); } } HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了 Map map=new LinkedHashMap(); Random r=new Random(); for(int i=0;i<10;i++) map.put(r.nextInt(100), i+"-value"); System.out.println(map); 实际上是调用map.entrySet对数据进行遍历,生成String 特殊新增的构造器 new LinkedHashMap<>(16, 0.75f, true) LinkedHashMap存储数据是有序的(默认false,其中使用before/after记录了插入顺序) public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } accessOrder涉及到LinkedHashMap存储的顺序,具体的存储顺序分为两种:插入顺序(false)和 访问顺序(true调用get方法会导致位置的移动)。accessOrder设为false表示插入顺序存储的, 这也是默认值,表示LinkedHashMap中存储的顺序是按照调用put方法插入的顺序进行排序的。 设置采用访问顺序 如果一个key被访问过了的元素则自动添加到默认位置的末尾。如果不进行访问操作,则为插入顺序一致 Map map=new LinkedHashMap<>(10,0.75f,true); Random r=new Random(); map.put(111,"value"); for(int i=0;i<10;i++) map.put(r.nextInt(100), i+"value"); map.get(111); //约瑟夫环的最佳写法 如果true采用访问顺序,而111对应的数据被访问过后自动自动到末尾;如果false则位置永远不变 ###### 总结 - LinkedHashMap是继承于HashMap,是基于HashMap和双向链表来实现的。 - HashMap无序;LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序那put和get 操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入) - LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序 - LinkedHashMap是线程不安全的 / public class TestLinkedHashMap { public static void main(String[] args) { Map map = new LinkedHashMap<>(10, 0.75f, true); Random r = new Random(); map.put(111, "value"); int kk = 0; for (int i = 0; i < 10; i++) { kk=r.nextInt(100); map.put(kk, i + "value"); } map.get(kk); map.get(111); System.out.println(map); } } ———————————————— 版权声明:本文为CSDN博主「zjh0101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/zjh0101/article/details/126670694
-
使用打表法找规律 作者:Grey 原文地址: 博客园:使用打表法找规律 CSDN:使用打表法找规律 打表法的使用条件 打表法适合:输入简单,输出也简单(只有一个数),可以暴力把一部分结果打印出来找规律,看下能否找到一个公式来优化代码。 买苹果问题 题目描述见:牛客:买苹果 暴力解法思路 如果是奇数,直接返回 -1,因为 6 和 8 不可能组成奇数 假设有 n 个苹果,最多需要n/8个 8 号袋,假设n/8=m,如果m==0,则最少需要m个 8 号袋子即可,如果m!=0,则看剩下的苹果能否被 被 6 号袋子消化,如果不能消化,则则减少一个 8 号袋子(m--),继续看剩下能否被 6 号袋搞定。 暴力解法的代码如下 public static int minBags(int n) { if ((n & 1) == 1) { return -1; } if (n % 8 == 0) { // 全部可以被 8 号袋子分解 return n / 8; } int use8 = n / 8; int rest = n % 8; while (rest != 0) { if (rest % 6 == 0) { // 分配了 8 号袋子,剩下的分配 6 号袋子,正好分配完。 return use8 + (rest / 6); } else { // 分配了 8 号袋子,剩下的分配 6 号袋子,无法分配完,则减少一个 8 号袋子 if (use8 > 0) { use8--; rest += 8; } else { return -1; } } } return -1; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 打印出前几项的结果观察一下 当有0个苹果的时候,最少需要:0个袋子 当有1个苹果的时候,最少需要:-1个袋子 当有2个苹果的时候,最少需要:-1个袋子 当有3个苹果的时候,最少需要:-1个袋子 当有4个苹果的时候,最少需要:-1个袋子 当有5个苹果的时候,最少需要:-1个袋子 当有6个苹果的时候,最少需要:1个袋子 当有7个苹果的时候,最少需要:-1个袋子 当有8个苹果的时候,最少需要:1个袋子 当有9个苹果的时候,最少需要:-1个袋子 当有10个苹果的时候,最少需要:-1个袋子 当有11个苹果的时候,最少需要:-1个袋子 当有12个苹果的时候,最少需要:2个袋子 当有13个苹果的时候,最少需要:-1个袋子 当有14个苹果的时候,最少需要:2个袋子 当有15个苹果的时候,最少需要:-1个袋子 当有16个苹果的时候,最少需要:2个袋子 当有17个苹果的时候,最少需要:-1个袋子 当有18个苹果的时候,最少需要:3个袋子 当有19个苹果的时候,最少需要:-1个袋子 当有20个苹果的时候,最少需要:3个袋子 当有21个苹果的时候,最少需要:-1个袋子 当有22个苹果的时候,最少需要:3个袋子 当有23个苹果的时候,最少需要:-1个袋子 当有24个苹果的时候,最少需要:3个袋子 当有25个苹果的时候,最少需要:-1个袋子 当有26个苹果的时候,最少需要:4个袋子 当有27个苹果的时候,最少需要:-1个袋子 当有28个苹果的时候,最少需要:4个袋子 当有29个苹果的时候,最少需要:-1个袋子 当有30个苹果的时候,最少需要:4个袋子 当有31个苹果的时候,最少需要:-1个袋子 当有32个苹果的时候,最少需要:4个袋子 当有33个苹果的时候,最少需要:-1个袋子 当有34个苹果的时候,最少需要:5个袋子 当有35个苹果的时候,最少需要:-1个袋子 当有36个苹果的时候,最少需要:5个袋子 当有37个苹果的时候,最少需要:-1个袋子 当有38个苹果的时候,最少需要:5个袋子 当有39个苹果的时候,最少需要:-1个袋子 ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 通过观察,可以得到如下结论 奇数个苹果,返回 -1。 n <= 5或者n==10的时候,返回 -1。 n == 6或者n == 8的时候,返回 1。 n % 8 == 0时候,全部用 8 号袋子,即n / 8; 当n%8!=0时,需要n/8+1个袋子。 代码可以优化为 // 打表方式优化 public static int minBags2(int n) { if (n <= 5 || n == 10 || (n & 1) == 1) { return -1; } if (n == 6 || n == 8) { return 1; } return n % 8 == 0 ? n / 8 : n / 8 + 1; } 1 2 3 4 5 6 7 8 9 10 11 牛羊吃草问题 牛客:青草游戏 暴力解法思路 在 n <= 4的情况下,我们可以通过观察得到牛羊的胜败情况 // 0 羊胜 // 1 牛胜 // 2 羊胜 // 3 牛胜 // 4 牛胜 if (n < 5) { // base case return (n == 0 || n == 2) ? "yang" : "niu"; } 1 2 3 4 5 6 7 8 当n >= 5的情况下,我们可以暴力枚举所有情况, 当牛选择吃 1 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。 当牛选择吃 4 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。 … 当牛选择吃 4^x 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。 (注:4^x <= n)。 暴力解法的代码如下: public static String winner(int n) { // 0 羊 // 1 牛 // 2 羊 // 3 牛 // 4 牛 if (n < 5) { // base case return (n == 0 || n == 2) ? "yang" : "niu"; } // n >= 5 时 int base = 1; // 当前先手(牛)决定吃的草数 // 当前是先手(牛)在选 while (base <= n) { // 当前一共n份草,先手(牛)吃掉的是base份,n - base 是留给后手(羊)的草 if (winner(n - base).equals("yang")) { return "niu"; } if (base > (n >> 2)) { // 防止base*4之后溢出 break; } base <<= 2; } return "yang"; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 打印出前面的一些输出 0 堆草,获胜者是:yang 1 堆草,获胜者是:niu 2 堆草,获胜者是:yang 3 堆草,获胜者是:niu 4 堆草,获胜者是:niu 5 堆草,获胜者是:yang 6 堆草,获胜者是:niu 7 堆草,获胜者是:yang 8 堆草,获胜者是:niu 9 堆草,获胜者是:niu 10 堆草,获胜者是:yang 11 堆草,获胜者是:niu 12 堆草,获胜者是:yang 13 堆草,获胜者是:niu 14 堆草,获胜者是:niu 15 堆草,获胜者是:yang 16 堆草,获胜者是:niu 17 堆草,获胜者是:yang 18 堆草,获胜者是:niu 19 堆草,获胜者是:niu 20 堆草,获胜者是:yang 21 堆草,获胜者是:niu 22 堆草,获胜者是:yang 23 堆草,获胜者是:niu 24 堆草,获胜者是:niu 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 通过观察可知,可以得到结论 n % 5 == 0 || n % 5 == 2情况下,获胜者都是羊,否则获胜者是牛。 优化后的代码如下: public static String winner2(int n) { if (n % 5 == 0 || n % 5 == 2) { return "yang"; } else { return "niu"; } } 1 2 3 4 5 6 7 判断一个数是否可以表示成若干(数量>1)连续正数和的数 题目描述如下 定义一种数:可以表示成若干(数量 > 1)连续正数和的数 比如: 5 = 2 + 3,5 就是这样的数 12 = 3 + 4 + 5,12 就是这样的数 1不是这样的数,因为要求数量大于1个、连续正数和 2 = 1 + 1,2 也不是,因为等号右边不是连续正数 给定一个参数 N,返回是不是可以表示成若干连续正数和的数 按照题目的描述,我们可以把暴力解法写出来,就是枚举 num 是否 可以被 1 + 2 + ... + n 2 + 3 + ... + n 3 + 4 + ... + n … 上述任何一个式子分解。 暴力解法的代码如下 public static boolean isMSum1(int num) { if (num <= 2) { return false; } int sum = 1; for (; sum < num; sum++) { int o = sum; int i = sum + 1; sum += i; if (sum > num) { return false; } while (sum < num) { i++; sum += i; } if (sum == num) { return true; } sum = o; } return false; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 打印出前面若干项的结果 1 : false 2 : false 3 : true 4 : false 5 : true 6 : true 7 : true 8 : false 9 : true 10 : true 11 : true 12 : true 13 : true 14 : true 15 : true 16 : false 17 : true 18 : true 19 : true 20 : true 21 : true 22 : true 23 : true 24 : true 25 : true 26 : true 27 : true 28 : true 29 : true 30 : true 31 : true 32 : false 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 观察后发现 num == 1 || num == 2时,为 false。 如果一个数是 2 的某次方,则返回 false,否则则返回 true。 如何判断一个数是不是 2 的某次方呢? 有如下三种方法 num == (num & (-num)) num == (num & (~num + 1)) (n & (num - 1)) == 0 1 2 3 所以,优化后的代码如下 public static boolean isMSum2(int num) { if (num == 1 || num == 2) { return false; } return ((num - 1) & num) != 0; } ———————————————— 版权声明:本文为CSDN博主「GreyZeng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/hotonyhui/article/details/126671270
-
哈希表 Hash 一般翻译为散列,也有直接音为哈希的,这就是把任意长度的输入通过散列算法,变换 成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值 所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。 哈希冲突 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,就把它叫做碰撞(哈希碰撞)。哈希碰撞的解决方案为开放地址法和链地址法。 以key/value的方式存储数据,采用拉链法综合了数组和链表结构。如果key已知则存取效率较高,但是删除慢,如果不知道key存取则慢,对存储空间使用不充分。最典型的实现是HashMap JDK8+对桶内数据处理从链表转换为红黑树,则查询效率从O(N)提高到O(logN)。当链表中的个数超过8个时 会转换为红黑树 Map实现类 HashMap、TreeMap、LinkedHashMap、Hashtable等 HashMap 类定义 public class HashMap extends AbstractMap implements Map,Cloneable,Serializable; 注意:Map接口的定义 public interface Map没有父接口 具体的内部数据存储方式 transient Node[] table;//哈希表的本质是一个数组,数组中每一个元素称为一个桶,桶中存放的是键值对 transient Set> entrySet; 并不是用于实际存储数据,主要用于针对entrySet和keySet两个视图提供支持。 transient int size;当前集合中的元素个数。 final float loadFactor ;当前集合的负载因子,当前Map集合中扩容的阈值【负载因子*最大容积】 transient int modCount;修改次数,主要用于实现多线程操作时快死异常 重要的阈值 static final int DEFAULT_INITIAL_CAPACITY=1<<4;//aka16 默认的初始化容积 static final int MAXIMUM_CAPACITY=1<<30;最大容积值,实际上就是2的30次方 static final int TREEIFY_THRESHOLD = 8;//树化阈值:即链表转成红黑树的阈值,在存储数据时,当链表长 度 > 该值时,则将链表转换成红黑树 static final int MIN_TREEIFY_CAPACITY = 64;//最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才 允许树形化链表 (即将链表转换成红黑树)否则,若桶内元素太多时,则直接扩容,而不是树形化 static final int UNTREEIFY_THRESHOLD = 6;//桶的链表还原阈值:即红黑树转为链表的阈值,当在扩容resize )时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红 黑树转换成链表 内部存储的实现 静态内部类用于实现Entry,HahMap中存放的key/value对就被封为Node对象。其中Key就是存放的键值,决定具体存放位置; Value是具体存放数据,hash就是当前Node对象的hash值,next用于指向下一个Node 节点(单向链表) HashMap()采用所有默认配置值,其中的参数值有initial Capacity:int初始化容积,默认值16,第二参数为加载因子,默认值为0.75,表示存储16*0.75个元素后,如果大于这个值则需要进行扩容。 Map主要用于存储键key值value对,根据键得到值,因此键不允许重复,但允许值重复。 HashMap是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值, 具有很快的访问速度。 HashMap最多只允许一条记录的键为null;允许多条记录的值为null HashMap不支持线程的同步,即任一时刻可以有多个线程同时写Hash Map;可能会导致数据不一致,在JDK1.7中会出现环形链,虽然JDK1.8不会出现环形链,但是还会有rehash操作出现死循环、脏读问题、size值不准确等问题。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。 如何判断环形链? 创建一个Set,然后遍历整个集合,将每个元素的key值存入set,如果要存的key值已经存储在set中,则出现环型链. Java7中使用Entry来代表每个HashMap中的数据节点,Java8中使用Node,基本没有区别,都是key,value,hash和next这四个属性,不过,Node只能用于链表的情况,红黑树的情况需要使用TreeNode。 TREEIFY_THRESHOLD为 8如果新插入的值是链表中的第 9 个会触发下面的 treeifyBin(树化操作,就是将单向链转换为红黑树),也就是将链表转换为红黑树。 JDK8+插入数据到链表的最后面,Java7是插入到链表的最前面 HashMap的put方法的具体流程 public V put(K key, V value) { 以key存储value值,返回原始位置上的value值 先执行hash(key)根据key获取一个hash值, 参数2是要存储的key值,参数3是要存储的value,参数4表示如果当前位置已存在一个值 ,是否替换,false是替换,true是不替换。参数5是否在创建模式,如果为false,则表是在创建模式 return putVal(hash(key), key, value, false, true); } ———————————————— 版权声明:本文为CSDN博主「Lxcjl」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Lxcjl/article/details/126651922
-
1.HashMap总结 - 结构特点:Java8-中的HashMap是基于“数组+链表”的方式(链表法解决冲突),到了Java8,应该是“数组+链表/红黑树”的方式,默认阈值为树化8退化6 - 线程安全:HashMap是不安全,JDK1.8-并发由于插入采用的是头插法可能会导致环形链,JDK1.8+插入数据采用的是尾插法可能会导致数据丢失。集合框架中有两种线程安全的实现Collections.synchronizedMap(new java.util.HashMap<>())和juc包中的ConcurrentHashMap,前者是锁整个表,后者采用乐观锁的方式 - 性能特点:HashMap可以在常数时间内增加,删除,查找元素,但这也是一种平均情况,使用load factor装载因子计算阈值就是为了减少冲突带来的性能退化 - 扩容方法:HashMap的桶数组一次扩展为原数组的2倍,控制扩展和移动的次数,这里需要执行rehash计算。如果容量小于64只会进行简单扩容,如果容量大于64则会进行树化改造。树化处理可以避免哈希碰撞攻击 2.Hashtable 线程安全的,不允许null的键或值;是线程安全的但是Hashtable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差 HashMap允许键和值为null,只是key只能有一个null,值允许null多个由于线程安全,则在非多线程环境下不建议使用 Hashtable是一个散列表,它存储的内容是键值对(key-value)映射。通过"拉链法"实现的哈希表 Hashtable继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。 Hashtable的构造器 public Hashtable(int initialCapacity, float loadFactor) { //针对初始化容积和负载因子进行合法性验证 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry[initialCapacity]; //初始化数组,HashMap采用的是延迟初始化数组的策略 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //获取扩容的阈值 } MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { //无参构造器的默认值 容积为11,负载因子0.75 this(11, 0.75f); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; //在第一次添加put数据时,才进行数组的初始化操作,初始值为16,负载因子0.75 } 成员方法put public synchronized V put(K key, V value) { //线程安全,以当前hashtable对象充当锁 if (value == null) { //value值不允许为空 throw new NullPointerException(); } Entry tab[] = table; int hash = key.hashCode(); //直接获取key的hash值,但是HashMap中专门定义了hash方法,用于对key的hashCode值进行扰动处理 int index = (hash & 0x7FFFFFFF) % tab.length; //去除hash值中的符号位求余计算获取数组的对应下标索引 Entry entry = (Entry)tab[index]; //获取对应位置上的链头遍历整个链表,查找key值相同的Entry对象【首先进行hash值比较,如果相等则调用equals方法】,如果相等则后盖前 for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //如果链表上没有相等的key,则添加entry对象到链表上 addEntry(hash, key, value, index); //参数1是key的hash值,并没有进行扰动处理,参数2key,参数3value,参数4是数组索引序号 return null; } Hashtable的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的;在hashmap中允许key和value为null,只是key只能有一个null,如果出现冲突后盖前;如果使用null的key和value 则会出现一个运行时异常NullPonterException 3.HashMap 与 HashTable区别 1. 线程安全:HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过 synchronized修饰。(如果要保证线程安全的话就使用ConcurrentHashMap) 2. 效率:因为线程安全的问题,HashMap要比HashTable效率高一点。另外HashTable基本被淘汰,不 要在代码中使用它 3. 对Null key和Null value的支持:HashMap中null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put进的键值只要有一个null,直接抛NullPointerException 4. 初始容量大小和每次扩充容量大小的不同 : 创建时如果不指定容量初始值,Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。 创建时如果给定了容量初始值,那么Hashtable会直接使用给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小。 5. 底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认8)时,将链表转化为红黑树,以减少搜索时间。Hashtable没有这样的机制。 6. 推荐使用:在Hashtable的类注释可以看到,Hashtable是保留类不建议使用,推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用ConcurrentHashMap替代。 ———————————————— 版权声明:本文为CSDN博主「L to the B」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_56627229/article/details/126650598
-
List集合 List接口 继承于Collection接口 有序可重复原则,集合中每个元素具有对应的索引,可以通过索引直接访问该元素 List集合默认按照添加元素的顺序设置索引 List集合关心是否有序但并不关心是否重复 List接口的实现类由ArrayList动态数组、Vector动态数组、LinkedList双向链表、Stack栈 List集合针对索引的方法 1、添加元素 void add(int index, Object element):将元素element插入到List集合的index处。 boolean addAll(int index, Collection c):将集合c所包含的所有元素都插入到List集合的index处。 2、获取元素 Object get(int index): 返回集合index索引处的元素。 List subList(int fromIndex, int toIndex):返回从索引 fromlndex (包含)到索引toIndex(不包含)处所有集合元素组成的子集合。 3、获取元素索引 int indexOf(Object obj):返回对象。在List集合中第一次出现的位置索引。 int lastIndexOf(Object obj):返回对象。在List集合中最后一次出现的位置索引。 public static void main(String[] args) { List list = new ArrayList<>(); list.add("詹姆斯"); list.add("韦德"); list.add("欧文"); list.add("杜兰特"); list.add("西蒙斯"); System.out.println(list); list.add(3,"哈登");//添加到欧文之后 System.out.println(list); System.out.println(list.get(4));获取指定索引位置处的元素---->杜兰特 System.out.println(list.subList(1,3));//返回两个索引之间的元素作为子集合输出,不包含后一个索引位置处的元素 list.add("詹姆斯"); list.add("詹姆斯"); list.add("詹姆斯"); System.out.println(list); System.out.println(list.indexOf("詹姆斯"));//获取“詹姆斯”在集合中第一次出现的索引 System.out.println(list.lastIndexOf("詹姆斯"));//获取“詹姆斯”在集合中最后一次出现的索引 list.remove(1);//删除1处索引元素“韦德” System.out.println(list); list.set(4,"伦纳德");//将索引4处的西蒙斯换成伦纳德 System.out.println(list); for (String s : list) { System.out.print(s+" "); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 重写equals方法判断 @Override public boolean equals(Object o) { //首先判断传入该方法的对象是否是E类对象 if (o instanceof E){ E obj = (E) o;//强制转换 if(this.getCount() == obj.getCount()) return true;//相等返回true 否则返回false } return false; } 1 2 3 4 5 6 7 8 9 10 ListIterator import java.util.ListIterator;//导包 1 List集合提供了一个额外的方法listiterator()方法用于返回一个iterator对象,ListIterator接口继承了Iterator接口,提供了操作List集合的方法,与Iterator不同的是,它可以双向移动而Iterator只能单向移动 常用方法: void add():通过迭代器添加元素到对应集合 void set(Object obj):通过迭代器替换正迭代的元素 void remove():通过迭代器删除刚迭代的元素 boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。 Object previous():返回列表中的前一个元素。 int previousIndex():返回列表中的前一个元素的索引 boolean hasNext() Object next() int nextIndex() ArrayList list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); //首先进行正向遍历,可以把迭代器的遍历理解为具有一个指针,当正向遍历到最后一个元素时,在进行逆向遍历 ListIterator iterator = list.listIterator(); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println(); //逆向遍历 while(iterator.hasPrevious()){ System.out.print(iterator.previous()+" "); } //set方法使用 public static void main(String[] args) { List list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); ListIterator it = list.listIterator(); while (it.hasNext()){ Integer intt = it.next(); if (intt.equals(3)){ it.set(999); } } System.out.println(list); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 使用Listerator时注意处理并发异常现象 public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); //下面代码演示Listiterator并发异常现象 ListIterator it = list.listIterator(); while (it.hasNext()) { list.add(1, "牛"); System.out.println(it.next()); } //并发异常,使用迭代器遍历时不能使用集合对象修改集合数据 //java.util.ConcurrentModificationException } //逆向遍历 while(iterator.hasPrevious()){ System.out.print(iterator.previous()+" "); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 LinkedList实现类 LinkedList集合支持双链表,定位首尾元素速度较快,增删首尾元素速度快。 故而提供了多种API来操作LinkedList集合的首尾元素 LinkedList与ArrayList不同的是,ArrayList的底层是数组实现,但LinkedList的底层是利用链表实现 LinkedList做队列 public static void main(String[] args) { //LinkedList做队列使用 ---->先进先出,后进后出 LinkedList queue = new LinkedList<>(); //入队 queue.addLast("一号"); queue.addLast("二号"); queue.addLast("三号"); queue.addLast("四号"); queue.addLast("五号"); System.out.println(queue); //出队 System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 LinkedList做栈 public static void main(String[] args) { //LinkedList做栈使用---->先进后出,后进先出 //可以理解为装填子弹,第一颗装入之后装第二课,第二课将第一颗往下挤,第一颗位于首位,以此类推 LinkedList stack = new LinkedList<>(); //压栈 stack.push("第一颗子弹"); stack.push("第二颗子弹"); stack.push("第三颗子弹"); stack.push("第四颗子弹"); //出栈 System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 做栈使用时发现: 入栈方法push()与addFirst()是相同的效果,并且push()的底层实现也是调用了addFirst() //push方法的底层实现 public void push(E e) { addFirst(e); } 1 2 3 4 出栈方法pop()与removeFirst()也是相同效果 //pop()方法底层实现 public E pop() { return removeFirst(); } 1 2 3 4 Map集合 概述 Map集合是一种与Collection不同的集合体系,Collection是单列集合 与Collection集合相同的是,Map是所有双列集合的父接口 其实现类有HashMap、TreeMap、LinkedHashMap Map集合是双列集合,它每个元素包含两个数据,即“键key”和“值value” Map集合也成为键值对集合,每个元素的格式:key=value Map集合的完整格式:Map {key1=value1,key2=value2,key3=value3,...} 1 与Set集合相同:无序、不重复、无索引、值可以为空 不重复:后面添加的元素在前面若有重复则会覆盖掉之前的元素 Map集合的特点由键key决定 Set集合的底层实现就是利用了Map集合 Set sets = new HashSet<>(); /*HashSet底层就是HashMap集合 public HashSet() { map = new HashMap<>(); }*/ Set integerSet = new TreeSet<>(); /*TreeSet底层实现就是TreeMap集合 public TreeSet() { this(new TreeMap()); }*/ 1 2 3 4 5 6 7 8 9 10 常用API* public static void main(String[] args) { Map maps = new HashMap<>(); //1. 添加元素 maps.put("Benz",4); maps.put("Ferrari",5); maps.put("Lamborghini",13); maps.put("Maserati",7); System.out.println(maps); //2. 清空Map集合 //maps.clear(); //System.out.println(maps); //3. 判断集合是否为空,是返回true,反之false System.out.println(maps.isEmpty());//true //4. 根据键key获取对应值value Integer temp = maps.get("Maserati"); System.out.println(temp); //5. 根据键删除整个元素---->删除键key返回值 maps.remove("Benz"); System.out.println(maps); //6. 判断是否包含某个键key System.out.println(maps.containsKey("Benz")); //7. 判断是否包含某个值 System.out.println(maps.containsValue(7)); //8. 获取全部键key的集合-----> public set keySet() //map集合是无需不重复,返回的是set集合 //写法一: System.out.println(maps.keySet()); //写法二: Set keys = maps.keySet(); for (String key : keys) { System.out.print(key+" "); } System.out.println(); //9. 获取全部值value的集合----->public Collection values() //Map集合值不做要求,值value可以重复,所以要用Collection集合接收并返回 //写法一: System.out.println(maps.values()); //写法二: Collection values = maps.values(); for (Integer value : values) { System.out.print(value+" "); } System.out.println(); //10. 获取集合的大小 System.out.println(maps.size()); //11. 合并其他Map集合 Map mapss = new HashMap<>(); mapss.put("Mi",2); mapss.put("iPhone",2); mapss.put("华为",2); mapss.put("MEIZU",2); //将mapss集合全部注入maps集合中即大小size变成7 maps.putAll(mapss); System.out.println(maps.size());//3--->7 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 Map集合遍历 通过键key来获取每个值 public static void main(String[] args) { //通过键找值 Map maps = new HashMap<>(); maps.put("Benz",4); maps.put("Ferrari",5); maps.put("Lamborghini",13); maps.put("Maserati",7); //调用KeySet方法拿到键的Set集合 Set keyset = maps.keySet(); //遍历键集合,通过调用get方法获取每个键的value值 for (String key : keyset) { Integer value = maps.get(key); System.out.println(key+" = "+value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 通过Set>和entrySet方法将Map集合打成Set集合再进行遍历 public static void main(String[] args) { Map maps = new HashMap<>(); maps.put("Benz",4); maps.put("Ferrari",5); maps.put("Lamborghini",13); maps.put("Maserati",7); /* 正常情况下,可以利用foreach遍历set、list等单列集合,因为单列集合中的元素有自己特定的数据类型 但是map集合中的元素是一个键值对,没有自己的数据类型也就不能直接通过foreach遍历 故而把Map集合通过Set> entrySet转换城Set集合进行遍历 Map.Entry是Map集合的一个实体类型,换而言之 Map.Entry就是的数据类型 此时将key和value键值对看作一个整体 查看Entry底层源码发现,Entry具有能够直接获取Map集合Key与Value的方法 interface Entry { K getKey(); V getValue(); */ Set> entrySet = maps.entrySet(); for (Map.Entry stringIntegerEntry : entrySet) { String strEntry = stringIntegerEntry.getKey(); Integer intEntry = stringIntegerEntry.getValue(); System.out.println(strEntry+"=>"+intEntry); //System.out.print(stringIntegerEntry+" "); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Map集合存储自定义类型元素 class Student{ private String name; private int age; public Student() {} public Student(String name, int age) {this.name = name;this.age = age;} public String getName() {return name;} public void setName(String name) {this.name = name;} public int getAge() {return age;} public void setAge(int age) {this.age = age;} @Override public String toString() {return "Student {"+name+age+"岁"+"}";} } public class TestMain { //将学生与班级进行对应 学生作为键,班级作为值 public static void main(String[] args) { Map studentMessage = new HashMap<>(); studentMessage.put(new Student("张三",19),2250); studentMessage.put(new Student("李四",21),2266); studentMessage.put(new Student("王五",20),2266); studentMessage.put(new Student("赵六",17),2266); studentMessage.put(new Student("冯七",22),2250); Set> entrySet = studentMessage.entrySet(); for (Map.Entry studentIntegerEntry : entrySet) { System.out.println(studentIntegerEntry); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 TreeMap TreeMap作为TreeSet的底层实现,仍然是两种排序方式即自然排与定制排 自然排序 public static void main(String[] args) { Map studentMessage = new TreeMap<>(); studentMessage.put(47,"1"); studentMessage.put(22,"2"); studentMessage.put(39,"3"); studentMessage.put(10,"4"); studentMessage.put(1,"4"); //默认升序排序 /* key=1value4 key=10value4 key=22value2 key=39value3 key=47value1 */ studentMessage.forEach((k,v)-> System.out.println("key="+k+"value"+v)); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 定制排序一: 此类实现Comparable接口,并且重写CompareTo比较方法 class R implements Comparable{ private String name; private int age; public R(){} public R(String name,int age){this.name=name;this.age=age;} public void setName(String name){this.name=name;} public void setAge(int age){this.age=age;} public String getName(){return name;} public int getAge(){return age;} public String toString(){return "姓名:"+name+" 年龄"+age;} public int compareTo(R o){ //按照年龄排序--->降序 return Integer.compare(o.age,this.age); } } public class TestMain { public static void main(String[] args) { Map maps = new TreeMap<>(); maps.put(new R("张三",25),"GZ2266期"); maps.put(new R("李四",36),"GZ2250期"); maps.put(new R("王五",67),"GZ2266期"); maps.put(new R("汪苏泷",33),"GZ2250期"); maps.put(new R("刘德华",51),"GZ2250期"); maps.put(new R("吴京",42),"GZ2250期"); maps.forEach((k,v)-> System.out.println("key:"+k+"value:"+v)); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 定制排序二: 利用匿名内部类写法,向TreeMap集合中注入Comparator比较器,并且重写compare比较方法 class R{ private int count; public R(){} public R(int count){this.count=count;} public void setCount(int count){this.count=count;} public int getCount(){return count;} @Override public String toString() {return "R{" + "count=" + count + '}';} } public class TestMain { public static void main(String[] args) { Map maps = new TreeMap<>(new Comparator() { @Override public int compare(R o1, R o2) { //按照降序 return o2.getCount() - o1.getCount(); } }); maps.put(new R(1),"1"); maps.put(new R(2),"2"); maps.put(new R(3),"3"); maps.put(new R(4),"4"); maps.put(new R(5),"5"); System.out.println(maps); /*{R{count=5}=5, R{count=4}=4, R{count=3}=3, R{count=2}=2, R{count=1}=1}*/ } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 LinkedHashMap HashSet有一个子类是LinkedHashSet,HashMap也有一个LinkedHashMap子类; LinkedHashMap也使用双向链表维护Map集合的键值对次序 public static void main(String[] args) { Map subjectScore = new LinkedHashMap<>(); subjectScore.put("语文",127); subjectScore.put("数学",147); subjectScore.put("英语",148); subjectScore.put("理科综合",292); subjectScore.put("口语",93); //按照添加顺序输出 subjectScore.forEach((k,v)-> System.out.println("key="+k+"value="+v)); } ———————————————— 版权声明:本文为CSDN博主「绿仔牛奶_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/yuqu1028/article/details/126650400
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签