Youzi的知识库 - Java后端
type: channel
link: https://knowledge.autisn.com/index.php/category/后端
items: JVM初认识
items: ElasticSearch在SpringBoot中的简单运用
items: 线程创建常用方式及区别
items: 复习（一）
items: EasyExcel的导入功能的使用总结
items: 在AOP配置事务控制或注解 @Transactional控制事务中，try…catch…使事务失效的解决办法
items: 字节流和字符流
items: RabbitMQ知识点
items: Docker安装RabbitMQ并简单简单使用
items: 计算时间间隔

JVM初认识
type: item
date: 2023-10-24T15:35:05+08:00
description: 1、jvm 内存模型程序计数器 Program Counter Register：记录的是正在执行的虚拟机字节码指令的地址。此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域虚拟机：VM Stack描述的是 JAVA 方法执行的内存模型，每个方法在执行的时候都会创建一个栈帧， 用于存储局部变量表，操作数栈，动态链接，方法接口等信息局部变量表存储了编译期可知的各种基本数据类型、对象引用线程请求的栈深度不够会报 StackOverflowError 异常栈动态扩展的容量不够会报 OutOfMemoryError 异常虚拟机栈是线程隔离的，即每个线程都有自己独立的虚拟机栈本地方法：Native Stack本地方法栈类似于虚拟机栈，只不过本地方法栈使用的是本地方法堆：Heap几乎所有的对象实例都在堆上分配内存2、堆所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域，也被称为“GC 堆”；也是我们优化最多考虑的地方。 堆可以细分为：1.新生代Eden 空间From Survivor 空间To Survivor 空间2.老年代3.永久代/元空间Java8 以前永久代，受 jvm 管理，java8 以后元空间，直接使用物理内存。因此， 默认情况下，元空间的大小仅受本地内存限制。 垃圾回收从 Java8 开始，HotSpot 已经完全将永久代（Permanent Generation）移除，取而代之的是一 个新的区域—元空间（MetaSpace）3、运行图由于FGC的速度比YGC的速度慢得多，所以JVM的优化也是尽量避免FGC

ElasticSearch在SpringBoot中的简单运用
type: item
date: 2023-10-23T22:33:00+08:00
description: 1.给es中建立索引。product,建立好映射关系PUT product { "mappings": { "properties": { "skuId": { "type": "long" }, "spuId": { "type": "long" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "skuPrice": { "type": "keyword" }, "skuImg": { "type": "keyword", "index": false, "doc_values": false }, "saleCount": { "type": "long" }, "hosStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catelogId": { "type": "long" }, "brandName": { "type": "keyword", "index": false, "doc_values": false }, "brandImg": { "type": "keyword", "index": false, "doc_values": false }, "catelogName": { "type": "keyword", "index": false, "doc_values": false }, "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword", "index": false, "doc_values": false }, "attrValue": { "type": "keyword" } } } } } }2.Service类package com.atguigu.gulimall.lsearch.service; import com.atguigu.common.to.es.SkuEsModel; import java.io.IOException; import java.util.List; public interface ProductSaveService { boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException; }3.ServiceImpl类package com.atguigu.gulimall.lsearch.service.iml; import com.alibaba.fastjson.JSON; import com.atguigu.common.to.es.SkuEsModel; import com.atguigu.gulimall.lsearch.config.GulimallElasticSearchConfig; import com.atguigu.gulimall.lsearch.constant.EsConstant; import com.atguigu.gulimall.lsearch.service.ProductSaveService; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service("pro…

线程创建常用方式及区别
type: item
date: 2023-04-19T15:59:00+08:00
description: 继承Thread类创建线程//总结：重写的是run()方法，而不是start()方法，但是占用了继承的名额，Java中的类是单继承的 public class MyThread extends Thread{ public static void main(String[] args){ MyThread myThread = new MyThread(); //创建并启动线程 myThread.start(); } @Override public void run(){ //重写run方法 System.out.println("Hello"); } }实现Runnable接口创建线程//总结：实现Runnable接口，实现run()方法，使用依然要用到Thread类，这种方式更常用 //第一种写法 public class MyThread implements Runnable { public static void main(String[] args){ Thread thread = new Thread(new MyThread()); //创建并启动线程 thread.start(); } @Override public void run(){ //重写run方法 System.out.println("Hello"); } } //第二种写法 public class MyThread{ public static void main(String[] args){ Thread thread = new Thread(new Runnable(){ public void run(){ //重写run方法 System.out.println("Hello"); } }); //创建并启动线程 thread.start(); } } //第三种写法 public class MyThread implements Runnable { public static void main(String[] args){ Thread thread = new Thread(()->System.out.println("Hello")); thread.start(); } }使用Callable和Future创建线程//总结：与Runnable的区别在于，Callable可以获取返回结果 //实现Callable接口，实现call()方法，要使用Thread和FutureTask配合，这种方式支持拿到异步执行结果 public class MyThread implements Callable<String> { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> futureTask = new FutureTask<>(new MyThread()); Thread thread = new Thread(futureTask); thread.start(); //此时是获取到的结果 String result = futureTask.get(); System.out.println(result); } @Override public String call() throws Exception { return "Hello"; } }使用线程池例如用Executor框架//总结：实现Callable接口或者实现Runnable接口都可以，由ExecutorService创建线程 public class MyThread implements Runnable { public static void main(String[] args){ // 创建10个数据级的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); threadPool.execute(new MyThread()); } @Override public void run(){ System.out.println("Hello"); } }

复习（一）
type: item
date: 2023-03-31T22:51:57+08:00
description: 解决POST、GET请求中文乱码问题解决post请求乱码问题： 在web.xml中配置一个CharacterEncodingFilter过滤器，设置成 utf-8；<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>@RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")get 请求中文参数出现乱码解决方法有两个修改 tomcat 配置文件添加编码与工程编码一致，如下：<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/另外一种方法对参数进行重新编码//ISO8859-1 是 tomcat 默认编码，需要将 tomcat 编码后的内容按 utf-8 编码 String userName = new String(request.getParamter(“userName”).getBytes(“ISO8859-1”),“utf-8”)RedisRedis作用内存存储、持久化，放在内存中，断电即失（持久化重要（RDB、AOF））效率高，可以用于告诉缓存发布订阅系统地图信息分析计数器、计数器（浏览量）特性多样的数据类型持久化集群事务基础知识redis默认16个数据库，默认使用第0个，可以使用select进行切换数据库常用命令#查看数据库大小 DBSIZE #查看所有的key KEY * #清空当前数据库 flushdb #清空所有数据库 flushallRedis是单线程的Redis是基于内存操作，所以他的瓶颈在于机器的内存和网络带宽SpringBean的配置详解Bean的实例化配置静态工厂方法实例化Bean实例工厂方法实例化Bean实现FactoryBean规范延迟实例化BeanBean的依赖注入配置依赖注入的两种方式通过Bean的set方法注入<property name="" ref="" /><property name="" value="" />通过构造Bean的方法注入<constructor-arg name="" ref="" /><constructor-arg name="" value="" />ref用于引用其他的Bean的id；value用于注入普通属性值.依赖注入的数据类型有如下三种：普通数据类型，例如：String，int，boolean等，通过value属性指定引用数据类型，例如：UserDaoImpl，DataSource等，通过ref属性指定集合数据类型，例如：List，Map，Properties等Spring 声明式事务无效可能的原因有哪些？答：可能的原因如下：MySQL 使用的是 MyISAM 引擎，而 MyISAM 是不支持事务的；@Transactional 使用在非 public 方法上，@Transactional 注解只能支持 public 级别，其他类型声明的事务不会生效；@Transactional 在同一个类中无事务方法 A() 内部调用有事务方法 B()，那么此时 B() 事物不会生效。Spring 中的 AOP 的底层实现原理是什么？答：Spring AOP 的底层实现原理就是动态代理。Spring AOP 的动态代理有两种实现方式，对于接口使用的是 JDK 自带的动态代理来实现的，而对比非接口使用的是 CGLib 来实现的。Spring 中 Bean 的生命周期答：Spring 中 Bean 的生命周期如下：① 实例化 Bean：对于 BeanFactory 容器，当客户向容器请求一个尚未初始化的 Bean 时，或初始化 Bean 的时候需要注入另一个尚未初始化的依赖时，容器就会调用 createBean 进行实例化。对于 ApplicationContext …

EasyExcel的导入功能的使用总结
type: item
date: 2022-09-29T10:55:00+08:00
description: 监听器（用MyBatis的IService接口做了封装）package io.github.talelin.latticy.listener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.util.ListUtils; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.extension.service.IService; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Map; @Slf4j public class ExcelReaderListener<T> extends AnalysisEventListener<T> { /** * 每隔5条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收 */ private static final int BATCH_COUNT = 100; /** * 缓存的数据 */ private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); /** * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 */ private IService<T> service; /** * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 * * @param service */ public ExcelReaderListener(IService<T> service) { this.service = service; } /** * 这个每一条数据解析都会来调用 * * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(T data, AnalysisContext context) { log.info("解析到一条数据:{}", JSON.toJSONString(data)); cachedDataList.add(data); // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM if (cachedDataList.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list //注意ListUtils.newArrayListWithExpectedSize是在EasyExcel3.1.1版本之后才有的 cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { log.info("表头数据 excelHead= {}", headMap); } /** * 所有数据解析完成了 都会来调用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据，确保最后遗留的数据也存储到数据库 saveData(); log.info("所有数据解析完成！"); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据，开始存储数据库！", cachedDataList.size()); service.saveBatch(cachedDataList); log.info("存储数据库成功！"); } }控制层代码@PostMapping("/import") public AjaxResult importExcel(MultipartFile file) throws IOException { A…

在AOP配置事务控制或注解 @Transactional控制事务中，try…catch…使事务失效的解决办法
type: item
date: 2022-09-29T10:10:23+08:00
description: 可在catch中抛出运行时异常throw new RuntimeException(e)（注：默认回滚的是RuntimeException，如果你想触发其他异常的回滚，需要在注解上配置一下）手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),使得事务生效，出现异常回滚。@Transactional(rollbackFor = Exception.class) public AjaxResult update(@RequestBody RestaurantVo entity) { AjaxResult ajaxResult = new AjaxResult(); try { service.save(entity); return ajaxResult.success("success"); } catch (Exception e) { //throw new RuntimeException(e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return ajaxResult.fail("500", e.getMessage()); } }

字节流和字符流
type: item
date: 2022-07-12T16:04:00+08:00
description: 1、什么是字节流，什么是字符流字节流： 它处理单元为1个字节（byte），操作字节和字节数组，存储的是二进制文件，如果是音频文件、图片、歌曲，就用字节流好点（1byte = 8位）；字符流： 它处理的单元为2个字节的Unicode字符，分别操作字符、字符数组或字符串，字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的，如果是关系到中文（文本）的，用字符流好点（1Unicode = 2字节 = 16位）；所有文件的储存是都是字节（byte）的储存，在磁盘上保留的并不是文件的字符而是先把字符编码成字节，再储存这些字节到磁盘。在读取文件（特别是文本文件）时，也是一个字节一个字节地读取以形成字节序列。字节流可用于任何类型的对象，包括二进制对象，而字符流只能处理字符或者字符串；字节流提供了处理任何类型的IO操作的功能，但它不能直接处理Unicode字符，而字符流就可以。字节流是最基本的，所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据，它是按字节来处理的 但实际中很多的数据是文本，又提出了字符流的概念，它是按虚拟机的encode来处理，也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联，实际上是通过byte[]和String来关联。在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。最简单的区分字节流和字符流：万物皆文件，那就将文件在记事本里面打开，如果打开后能看的懂的就是字符流，如果看不懂那就是字节流。问题:word.doc 数据字节流还是字符流？答：.doc数据字节流。Excel 数据字节流还是字符流？答：要根据保存的格式进行判断，如果是保存为.csv那么他就是字符流，如果是其他的则数据字节流。字符和字节操作如何简单记住字节流和字符流字符流是以Reader，Writer结尾的字节流是以InputStream或OutputStream结尾的2、字节流的使用字节流主要是操作byte类型数据，以byte数组为准，主要操作类就是OutputStream、InputStream。字节输出流：OutputStream，OutputStream是整个IO包中字节输出流的最大父类Closeable表示可以关闭的操作2.1普通字节流的操作import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 复制D:\io_test下的加菲猫文件到D:\io_test这个目录下 */ public class IoDemo4 { public static void main(String[] args) throws IOException { //输入文件目录 String srcFilePath = "D:\\io_test\\加菲猫.jpg"; //输出文件目录（拷贝的路径） String destFilePath = "D:\\io_test\\加菲猫2.jpg"; FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; try { //输入流,传入路径 fileInputStream = new FileInputStream(srcFilePath); //输出流 fileOutputStream = new FileOutputStream(destFilePath); byte[] bytes = new byte[1024]; try { int count = 0; //如果不等于-1说明还没有读取完成，要继续读取 while((count = fileInputStream.read(bytes)) != -1){ //还有内容要读取,然后从0开始读取，上面count有读了多少个，那么就写多少个 fileOutputStream.write(bytes,0,count); } } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); }finally { //用完之后一定要关闭流,但是得判断不是空的时候才需要去关闭流 if(fileInputStream != null){ fileInputStream.cl…

RabbitMQ知识点
type: item
date: 2022-07-05T15:06:00+08:00
description: 1、消息应答的方法：Channel.basicAck(用于肯定确认)，RabbitMQ已知道该消息并且成功处理，可以将其丢弃Channel.basicNack(用于否定确认)Channel.basicReject(用于否定确认)，与Channel.basicNack相比少了一个参数，不处理该消息了，直接拒绝，可以将其丢弃了。2、Multiple的解释true表示批量应答channel上未应答的消息，比如channel上有传送tag的消息5,6,7,8,，当前tag是8，那么此时5-8的这些还未应答的消息就会被确认收到消息应答false同上面相比只会应答tag=8的消息，5,6,7这三个消息依然不会被确认收到消息应答手动应答的好处是可以批量应答并且减少网络拥堵 。3、消息手动应答代码//getEnvelope()属性，getDeliveryTag()标记 /* * 参数1：消息的标记 tag * 参数2：是否批量应答，false：不批量应答；true：批量 * */ channel.basicAck(message.getEnvelope().getDeliveryTag(), false); // 采用手动应答 boolean autoAck = false; channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> { System.out.println(consumerTag+"消费者取消消费接口回调逻辑"); });4、持久化// 声明队列 // 持久化 需要让Queue持久化 boolean durable = true; channel.queueDeclare(TASK_QUEUE_NAME,durable,false,false,null);注意：如果之前声明的队列不是持久化的，需要把原先队列先删除，或者重新创建一个持久化的队列，不然就会出现错误。将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘，但是这里依然存在当消息刚准备存储在磁盘的时候但是还没有存储完，消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强，但是对于我们的简单任务队列而言，这已经绰绰有余了。5、不公平分发//不公平分发channel //basicQos(0) --> 轮询分发（公平分发），默认值为0 //channel.basicQos(1) --> 不公平分发，能者多劳 //channel.basicQos(>2) --> 预取值 int prefetchCount = 1; channel.basicQos(prefetchCount); //采用手动应答 boolean autoAck = false; channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, cancelCallback);6、发布确认发布确认的策略:开启发布确认的方法：发布确认默认是没有开启的，如果要开启，需要调用方法 confirmSelect，每当你要想使用发布确认，都需要在 channel 上调用该方法。//开启发布确认 channel.confirmSelect();6.1单个确认发布这是一种简单的确认方式，它是一种同步确认发布的方式，也就是发布一个消息之后只有它被确认发布，后续的消息才能继续发布，waitForConfirmsOrDie(long) 这个方法只有在消息被确认的时候才返回，如果在指定时间范围内这个消息没有被确认那么它将抛出异常。这种确认方式有一个最大的缺点就是：发布速度特别的慢，因为如果没有确认发布的消息就会阻塞所有后续消息的发布，这种方式最多提供每秒不超过数百条发布消息的吞吐量。/** * 单个发送 */ public static void publishMessageIndividually() throws Exception { Channel channel = RabbitMqUtils.getChannel(); //队列声明 String queueName = UUID.randomUUID().toString(); channel.queueDeclare(queueName, true, false, false, null); //开启发布确认 channel.confirmSelect(); long begin = System.currentTimeMillis(); for (int i = 0; i < MESSAGE_COUNT; i++) { String message = i + "…

Docker安装RabbitMQ并简单简单使用
type: item
date: 2022-06-30T17:33:00+08:00
description: 1.Docker安装RabbitMQ1.1下载RabbitMQ镜像docker pull rabbitmq1.2创建并启动RabbitMQ容器docker run -id --hostname myrabbit --name rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq1.3进入容器交互页面docker exec -it rabbitmq /bin/bash1.4下载插件rabbitmq-plugins enable rabbitmq_management1.5登录ip+端口号访问账号密码均为：guest1.6拓展# 创建账号和密码 rabbitmqctl add_user 用户名 密码 # 设置用户角色 rabbitmqctl set_user_tags admin administrator # 为用户添加资源权限 # set_permissions [-p <vhostpath>] <user> <conf> <write> <read> # 添加配置、写、读权限 rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"用户级别：administrator：可以登录控制台、查看所有信息、可以对 rabbitmq 进行管理。monitoring：监控者 登录控制台，查看所有信息。policymaker：策略制定者 登录控制台，指定策略。managment：普通管理员 登录控制台。注：有可能会出现下面这个问题访问Docker的rabbitMq容器的Web-UI界面无法查看消息的状态及出现Stats in management UI are disabled on this node解决办法：进入RabbitMQ容器docker exec -it rabbitmq /bin/bash进入容器后，cd到路径/etc/rabbitmq/conf.d/cd /etc/rabbitmq/conf.d/再修改management_agent.disable_metrics_collectorecho management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf退出容器exit输入指令docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management重启RabbitMQdocker restart rabbitmq2.简单使用2.1pom.xml文件添加依赖(本环境是jdk1.8的)<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.8.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies>2.2新建Producer类为生产者package com.youzi.rabbitmq.one; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @Author youzi * @ClassName Producer * @Date 2022/6/30 15:41 * @Description //TODO 生产者 */ public class Producer { //设置队列名称 public static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws IOException, TimeoutException { //1.创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2.设置IP fa…

计算时间间隔
type: item
date: 2022-06-22T14:18:00+08:00
description: 首先<dependencies> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.1</version> </dependency> </dependencies>其次import org.joda.time.Interval; import org.joda.time.Period; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Author youzi * @ClassName TestDateUtil * startDate 开始时间 * endDate 结束时间 * dateFormat 时间格式（形如 yyyy-MM-dd） * @return 相差的月份数和天数 months 、 days * @throws ParseException * @Date 2022/6/22 14:02 * @Description 计算两个时间差 */ public class TestDateUtil { public static void main(String[] args) throws ParseException { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date dateStart = df.parse("2022-03-04"); Date dateEnd = df.parse("2022-06-03"); if (dateStart.getTime() > dateEnd.getTime()) { Date temp = dateStart; dateStart = dateEnd; dateEnd = temp; } Calendar c = Calendar.getInstance(); c.setTime(dateEnd); c.add(Calendar.DAY_OF_MONTH, 1); //这是明天 dateEnd = c.getTime(); Interval interval = new Interval(dateStart.getTime(), dateEnd.getTime()); Period p = interval.toPeriod(); Integer years = p.getYears(); Integer months = p.getMonths(); Integer weeks = p.getWeeks(); Integer days = p.getDays(); Integer resultMonths = years * 12 + months; Integer resultDays = weeks * 7 + days; Map<String, Integer> result = new HashMap<>(); result.put("months", resultMonths); result.put("days", resultDays); System.out.println(result); } }