• NIO介绍
     Java NIO(New IO)是Java 1.4引入的一项新技术,它提供了一种高效、灵活的I/O处理方式。相比于传统的I/O模型,NIO采用了基于缓冲区、通道和选择器等概念的方式来实现高效的数据传输。  在传统的I/O模型中,每个I/O操作都需要创建一个新的连接、读取或写入数据等,这些操作不仅耗时长,而且容易出现资源浪费的情况。而NIO则通过使用缓冲区来存储输入输出的数据,并通过通道进行数据的传输,从而大大提高了I/O效率。  在Java NIO中,通道(Channel)是最基本的I/O组件之一。通道可以分为字节通道(Byte Channel)、字符通道(Char Channel)、文件通道(File Channel)等多种类型,每种类型的通道都有其特定的读写方式和适用场景。例如,字节通道适用于网络通信、文件读写等场景;字符通道适用于文本数据的读写等场景。  除了通道之外,Java NIO还提供了选择器(Selector)的概念。选择器用于监听多个通道的状态变化,当某个通道就绪时,选择器会通知应用程序进行相应的读写操作。这种方式可以同时监听多个通道的状态变化,从而避免了频繁地轮询和创建连接等操作,提高了系统的性能和吞吐量。  Java NIO还提供了Buffer、ChannelHandler等重要的概念。Buffer是用于存储输入输出数据的缓存区,可以提高数据传输的效率;ChannelHandler则是用于处理通道事件的接口,可以通过实现该接口来处理通道就绪、异常等事件。  总之,Java NIO是一种高效、灵活的I/O处理方式,它可以大大提高系统的性能和吞吐量。对于需要高效数据传输的应用程序来说,Java NIO是一个非常值得学习和使用的技术。  Selector、Poll和Epoll都是Java NIO中用于处理I/O事件的机制,它们都可以提高系统的性能和吞吐量。下面是它们的介绍和区别:    1. Selector Selector是Java NIO中最常用的I/O事件选择器,它可以同时监听多个通道的状态变化,并在通道状态发生变化时通知应用程序进行相应的读写操作。Selector的使用需要创建一个Selector对象,然后通过调用register()方法将需要监听的Channel注册到Selector上。当某个Channel就绪时,Selector会返回对应的事件,应用程序可以通过调用select()方法来等待事件的发生。Selector的优点是可以同时监听多个通道的状态变化,适用于高并发的场景;缺点是它的使用比较复杂,需要手动管理注册和注销等操作。    2. Poll Poll也是Java NIO中的一种I/O事件选择器,它与Selector类似,也可以同时监听多个通道的状态变化。不同的是,Poll使用的是轮询的方式来检查通道的状态变化,而Selector则是使用回调函数的方式来处理事件。Poll的优点是使用比较简单,适合于低并发的场景;缺点是它的性能比Selector差一些。    3. Epoll Epoll是Java NIO中的最新版本,它是一种高效的I/O事件选择器,可以实现快速的事件响应和高效的系统利用率。Epoll使用基于事件驱动的方式来处理I/O事件,当某个Channel就绪时,Epoll会立即返回对应的事件,而不需要像Selector和Poll那样轮询或回调函数。Epoll的优点是性能比Selector和Poll好很多,适用于高并发的场景;缺点是它的使用比较复杂,需要手动管理注册和注销等操作。  总之,Selector、Poll和Epoll都是Java NIO中用于处理I/O事件的机制,它们各有优缺点,适用于不同的场景。在选择使用哪种机制时,需要根据具体的应用场景来进行选择。 
  • [其他] java线程同步CoutDownLatch、CylicBarrier、Semsphore
     CountDownLatchCountDownLatch是Java并发包(java.util.concurrent)中的一种同步工具,它允许一个或多个线程等待其他线程完成任务后再执行。CountDownLatch通常用于在多线程环境下协调任务的执行顺序。  CountDownLatch的基本用法如下:    1. 创建一个CountDownLatch实例:  import java.util.concurrent.CountDownLatch;  public class CountDownLatchExample {      public static void main(String[] args) throws InterruptedException {         CountDownLatch latch = new CountDownLatch(3); // 初始化CountDownLatch,需要3个线程调用countDown()方法          Thread thread1 = new Thread(new Task(latch, "Thread 1"));         Thread thread2 = new Thread(new Task(latch, "Thread 2"));         Thread thread3 = new Thread(new Task(latch, "Thread 3"));          thread1.start();         thread2.start();         thread3.start();     } }  class Task implements Runnable {      private final CountDownLatch latch;     private final String name;      public Task(CountDownLatch latch, String name) {         this.latch = latch;         this.name = name;     }      @Override     public void run() {         try {             System.out.println("Task " + name + " is running");             Thread.sleep(1000); // 模拟任务执行时间             System.out.println("Task " + name + " completed");             latch.countDown(); // 当计数器减至0时,所有等待的线程将被唤醒         } catch (InterruptedException e) {             e.printStackTrace();         }     } } 在这个示例中,我们创建了一个名为CountDownLatchExample的类。main()方法中,我们创建了一个CountDownLatch实例,并为3个线程分别分配了任务。然后,我们启动这3个线程。每个线程在完成任务后调用CountDownLatch的countDown()方法,当计数器减至0时,主线程将被唤醒。 CyclicBarrier CyclicBarrier是Java并发包(java.util.concurrent)中的一种同步工具,它允许一组线程在达到某个共享状态时一起阻塞。CyclicBarrier主要用于协调多个线程之间的顺序执行或同步。  CyclicBarrier的基本用法如下:    1. 创建一个CyclicBarrier实例:  import java.util.concurrent.CyclicBarrier;  public class CyclicBarrierExample {      public static void main(String[] args) throws InterruptedException {         CyclicBarrier barrier = new CyclicBarrier(3); // 初始化CyclicBarrier,需要3个线程调用await()方法          Thread thread1 = new Thread(new Task(barrier, "Thread 1"));         Thread thread2 = new Thread(new Task(barrier, "Thread 2"));         Thread thread3 = new Thread(new Task(barrier, "Thread 3"));          thread1.start();         thread2.start();         thread3.start();     } }  class Task implements Runnable {      private final CyclicBarrier barrier;     private final String name;      public Task(CyclicBarrier barrier, String name) {         this.barrier = barrier;         this.name = name;     }      @Override     public void run() {         try {             System.out.println("Task " + name + " is running");             Thread.sleep(1000); // 模拟任务执行时间             System.out.println("Task " + name + " completed");             barrier.await(); // 当所有线程都到达屏障时,主线程将被唤醒         } catch (InterruptedException e) {             e.printStackTrace();         }     } }  在这个示例中,我们创建了一个名为CyclicBarrierExample的类。main()方法中,我们创建了一个CyclicBarrier实例,并为3个线程分别分配了任务。然后,我们启动这3个线程。每个线程在完成任务后调用CyclicBarrier的await()方法,当所有线程都到达屏障时,主线程将被唤醒。  SemaphoreSemaphore是Java并发包(java.util.concurrent)中的一种同步工具,它允许一组线程在达到某个共享状态时进行互斥访问。Semaphore有两个重要类型:    1. Semaphore:Semaphore是一个计数信号量,用于控制同时访问的线程数量。创建一个Semaphore实例时,可以指定初始值和最大值。当一个线程尝试获取信号量时,如果计数小于最大值,则计数加1;否则,线程将等待直到计数减少到可用值。    2. CountDownLatch:CountDownLatch也是一个计数信号量,但它允许一个或多个线程在到达某个共享状态后继续执行。创建一个CountDownLatch实例时,可以指定初始值。当一个或多个线程调用countDown()方法时,计数减1。当计数变为0时,所有等待的线程都将被唤醒。  下面是Semaphore的示例代码:  import java.util.concurrent.Semaphore;  public class SemaphoreExample {      public static void main(String[] args) throws InterruptedException {         int initialValue = 5; // 初始化Semaphore,允许5个线程同时访问         int maxValue = 10; // 最大允许访问的线程数为10         Semaphore semaphore = new Semaphore(initialValue, maxValue); // 创建一个Semaphore实例,初始值为5,最大值为10          Thread thread1 = new Thread(new Task(semaphore));         Thread thread2 = new Thread(new Task(semaphore));         Thread thread3 = new Thread(new Task(semaphore));         Thread thread4 = new Thread(new Task(semaphore));          thread1.start();         thread2.start();         thread3.start();         thread4.start();     } }  class Task implements Runnable {      private final Semaphore semaphore;      public Task(Semaphore semaphore) {         this.semaphore = semaphore;     }      @Override     public void run() {         try {             semaphore.acquire(); // 尝试获取信号量,如果可用则计数加1,否则等待直到可用             System.out.println("Thread " + Thread.currentThread().getName() + " is running");             Thread.sleep(1000); // 模拟任务执行时间             semaphore.release(); // 释放信号量,计数减1             System.out.println("Thread " + Thread.currentThread().getName() + " completed");         } catch (InterruptedException e) {             e.printStackTrace();         }     } } 
  • [技术干货] linux下打包压缩和解压缩文件夹命令
    在linux下经常会遇到需要对某些文件或文件夹进行打包压缩、解压缩的需求,那么在linux下如何将文件和文件夹打包,怎样在linux下压缩某个文件夹命令呢?我们知道,在linux中是先打包再压缩的方式,其中常用压缩格式有:.zip ,.gz ,.bz2,.tar.gz ,.tar.bz2。而本文主要讲解tar命令。tar命令格式tar [参数] [打包文件名] [原文件]打包命令tar -cvf 打包文件名 原文件选项:-c:打包-v:显示过程-f:指定打包后的文件名例如:tar -cvf cai2022.tar cai2022 ,即为将cai2022文件夹打包成才022.tar解打包命令tar -xvf 打包文件名选项:-x:解打包例如:tar -xvf cai2022.tar,即为将cai2022.tar解压到当前目录.tar.gz格式压缩命令格式:tar -zcvf 压缩包名.tar.gz 原文件选项:-z:压缩为.tar.gz格式例如:tar -zcvf cai2022.tar.gz cai2022 //tar.gz格式是先打包为.tar格式,再压缩为.gz格式如果是分开打包和压缩:tar -cvf cai2022.tar cai2022 //先打包成.tarbzip2 cai2022.tar //再对.tar文件进行压缩成cai2022.tar.bz2解压缩命令格式:tar -zxvf 压缩包名.tar.gz选项:-x:解压缩.tar.gz格式例如:tar -zxvf cai2022.tar.gz如果是分开解压:bzip -d cai2022.tar.bz2 //先解压成tartar -xvf cai2022.tar //再解压tar.tar.bz2格式压缩压缩命令格式:tar -jcvf 压缩包名.tar.bz2 原文件选项:-z:压缩为.tar.bz2格式例如:tar -jcvf cai2022.tar.bz2 cai2022解压命令格式:tar -jxvf 压缩包名.tar.bz2选项:-x:解压缩.tar.bz2格式例如:tar -jxvf cai2022.tar.bz2补充说明如果需要指定解压缩路径,使用-C (大写C)例如:tar -jxvf cai2022.tar.bz2 -C /tmp/压缩多个文件,文件名用空格隔开例如:tar -zcvf test.tar.gz test test.php如果需要压缩到指定路径,在压缩包前加绝对路径例如:tar -zcvf /tmp/test.tar.gz test如果只查看压缩包内容而不解压,那使用选项-t例如:tar -ztvf test.tar.gz部分关键字说明z:gz格式j:bz2格式c: 压缩x: 解压缩t: 查看原文链接:https://baijiahao.baidu.com/s?id=1740784939692447868&wfr=spider&for=pc
  • [技术干货] No transaction aspect-managed TransactionStatus in scope
    出现No transaction aspect-managed TransactionStatus in scope报错排查几点: 1.service方法是否添加注解 @Transactional(rollbackFor = RuntimeException.class) 2.方法内部是否捕获异常了,并未向外抛出 a.捕获了异常可以再catch中抛出,上层处理 b.catch中增加如下,可以不向外抛出并且触发事务  TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 3.前两项确认无问题后,排查是否发生事务失效问题 样例:controller如下 service方法如下 添加事务的方法如下 上面代码中,controller调用了service的parse方法后,内部this.getServerInfo自调用了内部方法,导致事务失效从而报错。 常见几种事务失效场景: @Transactional 加于private方法, 无效 @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效 @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效 **@Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效** @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效 @Transactional 加于接口方法后, 被它类的接口方法调用, 有效 @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效 ———————————————— 原文链接:https://blog.csdn.net/w1028556865/article/details/125085130 
  • [技术干货] 判断String数字字符串是否为(正负)小数、整数,并转换
    在判断一个字符串数字之前,首先要分析这个字符串数字可能出现的情况  1、首位可能有符号,或者没有,^[-\\+]? 2、第二位必须是0-9的数字,[0-9] 3、第三位或者后几位可能有小数点“.”,或者没有,\\.? 4、有小数点的话,后面需要有1-n位0-9之间的数字,[0-9]+$  那么根据以上几点,最好的方式就是用正则表达式 代码如下:          String value = "-3.12";                  Boolean strResult = value.matches("^[-\\+]?([0-9]+\\.?)?[0-9]+$");         if (strResult == true ) {             System.out.println("Is Number!");             Double num = Double.parseDouble(value);             System.out.println(num);         } else {             System.out.println("Is not Number!");         } ———————————————— 原文链接:https://blog.csdn.net/su1573/article/details/86582220 
  • [技术干货] 详解BigDecimal及其加减乘除运算
     一、BigDecimal概述        浮点数值不适用于无法接受舍入误差的计算当中,比如金融计算。         例如,System.out.println(2.0-1.1)打印的结果为0.8999999999999999,而不是人们想象当中的0.9,这是为什么呢?因为System.out.println()中的数字默认是double类型的,而浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示小数0.1,因此产生了这种舍入误差。         就像在十进制当中,无法精确地表示分数1/3一样。         既然浮点数都不能精确的表示出来,那么通过浮点数进行的加减乘除运算自然都不是精确的值。         如果在数值计算中需要精确的结果,不允许有这种舍入误差,那么就需要使用BigDecima类。         BigDecima是java.math包中的一个类,这个类可以处理任意长度数字序列的数值,实现了任意精度的浮点数运算。类似BigDecima,还有一个Biglnteger,实现了任意精度的整数运算。           BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。  二、构造函数详解 1、BigDecimal(int val)        将 int转换成 BigDecimal对象。例如:         BigDecimal a = new BigDecimal(8);         这是通过构造函数的方式,将一个整型值转换成BigDecimal 对象,还可以通过静态的valueOf方法将普通的数值转换成BigDecimal。例如:         BigDecimal b = BigDecimal.valueOf(100);将long型数值转换成(小数位数为零的)BigDecimal。         BigDecimal c = BigDecimal.valueOf(3.14);将double型数值转换成BigDecimal。  2、BigDecimal(int val, MathContext mc)        将 int转换成 BigDecimal ,根据上下文设置进行舍入。         这里需要先讲述一下MathContext。       MathContext        MathContext是一个不可变对象,用于封装上下文设置,这些设置描述数字运算符的某些规则。它有三个构造函数:  (1)MathContext(int setPrecision)         构造具有指定精度的MathContext对象,默认的是HALF_UP舍入模式(关于舍入模式,本文后面会进行详细讲解)。  (2)MathContext(int setPrecision, RoundingMode setRoundingMode)         使用指定的精度和舍入模式构造新的MathContext对象。  (3)MathContext(String val)         使用字符串构造一个MathContext对象。         使用示例如下,  MathContext mathContext = new MathContext(4); BigDecimal d = new BigDecimal(12345, mathContext); System.out.println(d);        打印的结果为:1.235E+4         分析上述代码。第一行代码创建了一个MathContext 对象,指定的精度为4,也就是4位有效数字。第二行代码利用整型的“12345”和mathContext创建了一个BigDecimal对象,将12345保留4位有效数字转换成BigDecimal。12345保留四位有效数字的结果也就是1.235E+4(1.235乘于10的四次方)。  3、BigDecimal(long val)        将 long转换成 BigDecimal 。  4、BigDecimal(long val, MathContext mc)        将 long转换为 BigDecimal ,根据上下文设置进行舍入。  5、BigDecimal(BigInteger val)        将 BigInteger转换成 BigDecimal 。  6、BigDecimal(BigInteger unscaledVal, int scale)        将BigInteger的 BigInteger值和 int等级转换为 BigDecimal 。  7、BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)        将 BigInteger未缩放值,根据scale和mc ,转换为 BigDecimal 。  8、BigDecimal(BigInteger val, MathContext mc)        根据上下文设置将 BigInteger转换为 BigDecimal,根据上下文设置进行舍入。  9、BigDecimal(double val)        将 double转换为 BigDecimal ,这是 double的二进制浮点值的精确十进制表示。  10、BigDecimal(double val, MathContext mc)        将 double转换为 BigDecimal ,根据上下文设置进行舍入。  11、BigDecimal(char[] in)        将字符数组转换成 BigDecimal 。  12、BigDecimal(char[] in, int offset, int len)        将字符数组转换成 BigDecimal,同时允许一个子阵列被指定。  13、BigDecimal(char[] in, int offset, int len, MathContext mc)        将字符数组转换成 BigDecimal,同时允许指定一个子阵列和用根据上下文设置进行舍入。  14、BigDecimal(char[] in, MathContext mc)       将字符数组转换成 BigDecimal ,根据上下文设置进行舍入。  15、BigDecimal(String val)        常用!!!将字符串表示的数字转换为 BigDecimal 。  16、BigDecimal(String val, MathContext mc)        常用!!!将字符串表示的数字转换为 BigDecimal ,根据上下文设置进行舍入。         可以看到,BigDecimal的构造函数很多,根据自己的需要,大家选择合适的构造函数创建对应的BigDecimal对象就行了。但是有一点需要注意,就是以数值型参数创建BigDecimal对象的时候,会产生精度问题。        例如,  System.out.println(new BigDecimal(0.1)); System.out.println(new BigDecimal("0.1"));        打印结果为:     0.1000000000000000055511151231257827021181583404541015625 0.1         可以看到,通过double类型的0.1创建BigDecimal对象的时候,实际打印的值并不是精确的0.1,通过String类型创建的BigDecimal对象,实际打印的值是0.1。这是为什么呢?         因为参数类型为double的构造方法的结果有一定的不可预知性。不熟悉BigDecimal的人可能会认为在Java中通过newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1,但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。因为0.1无法用二进制精确地表示出来,本来开头亦对此进行了解释。这样,传入到构造方法的值并不是我们看到的0.1,实际上传的是0.1000000000000000055511151231257827021181583404541015625,因此产生了上述的打印结果。         String 构造方法是完全可预知的:通过 newBigDecimal(“0.1”) 将创建一个 BigDecimal对象,它正好等于预期的 0.1。因此, 通常建议优先使用String构造方法。  三、BigDecimal的加、减、乘、除、绝对值 1、加法        加法对应的方法有两个:  (1)public BigDecimal add(BigDecimal augend);         方法返回的是一个BigDecimal对象,值为当前对象的值加上被加数对象augend的值(this+augend)。保留的小数位数是max(this.scale(), augend.scale()).  (2) public BigDecimal add(BigDecimal augend,MathContext mc);         方法返回的是一个BigDecimal对象,值为当前对象的值加上被加数对象augend的值(this+augend),并根据上下文设置进行舍入。(关于上下文MathContext,前文已解释过)         加法示例如下,      @Test     public void testAdd() {         //用double类型初始化BigDecimal对象         BigDecimal numA = new BigDecimal(0.05);         BigDecimal numB = new BigDecimal(0.06);         System.out.println("numA + numB = " + numA.add(numB));         //用double类型和int类型初始化BigDecimal对象。(作加法运算时得到的只是一个近似值)         BigDecimal numC = new BigDecimal(3.05);         BigDecimal numD = new BigDecimal(100);         System.out.println("numC + numD = " + numC.add(numD));         //用字符串类型初始化BigDecimal对象。(作加法运算时得到的是精确值)         BigDecimal strA = new BigDecimal("3.05");         BigDecimal strB = new BigDecimal("100");         System.out.println("strA + strB = " + strA.add(strB));     }       打印的结果如下:  numA + numB = 0.11000000000000000055511151231257827021181583404541015625 numC + numD = 103.04999999999999982236431605997495353221893310546875 strA + strB = 103.05         可以看到,使用double类型初始化BigDecimal 对象,进行加法运算时就出现了精度问题。正如前文所说,并不是所有的浮点数都能够在二进制系统中被精确的表示,自然而然的在进行加减乘除运算时就会出错。  2、减法        对应的方法有两个: (1)public BigDecimal subtract(BigDecimal subtrahend);         方法返回的是一个BigDecimal对象,值为当前对象的值减去被减数对象subtrahend的值(this-subtrahend)。保留的小数位数是max(this.scale(), subtrahend.scale()).。     (2)public BigDecimal subtract(BigDecimal subtrahend,MathContext mc);         方法返回的是一个BigDecimal对象,值为当前对象的值减去被减数对象subtrahend的值(this-subtrahend),并根据上下文设置进行舍入。         减法示例如下,      @Test     public void testSubtract() {         //用double类型初始化BigDecimal对象         BigDecimal numA = new BigDecimal(0.05);         BigDecimal numB = new BigDecimal(0.06);         System.out.println("numA + numB = " + numA.subtract(numB));         //用double类型和int类型初始化BigDecimal对象。(作减法运算时得到的只是一个近似值)         BigDecimal numC = new BigDecimal(100);         BigDecimal numD = new BigDecimal(0.05);         System.out.println("numC + numD = " + numC.subtract(numD));         //用字符串类型初始化BigDecimal对象。(作减法运算时得到的是精确值)         BigDecimal strA = new BigDecimal("100");         BigDecimal strB = new BigDecimal("0.05");         System.out.println("strA + strB = " + strA.subtract(strB));     }        打印结果如下:  numA + numB = -0.00999999999999999500399638918679556809365749359130859375 numC + numD = 99.94999999999999999722444243843710864894092082977294921875 strA + strB = 99.95  3、乘法        对应的方法有:  (1)public BigDecimal multiply(BigDecimal multiplicand);         方法返回的是一个BigDecimal对象,值为当前对象的值乘于被乘数对象multiplicand的值(this*multiplicand)。保留的小数位数是(this.scale() + multiplicand.scale()).。     (2)public BigDecimal multiply(BigDecimal multiplicand,MathContext mc);        方法返回的是一个BigDecimal对象,值为当前对象的值乘于被乘数对象subtrahend的值(this*multiplicand),并根据上下文设置进行舍入。        乘法示例如下,      @Test     public void testMultiply() {         //用double类型初始化BigDecimal对象         BigDecimal numA = new BigDecimal(0.05);         BigDecimal numB = new BigDecimal(0.06);         System.out.println("numA + numB = " + numA.multiply(numB));         //用double类型和int类型初始化BigDecimal对象。(作乘法运算时得到的只是一个近似值)         BigDecimal numC = new BigDecimal(100);         BigDecimal numD = new BigDecimal(0.05);         System.out.println("numC + numD = " + numC.multiply(numD));         //用字符串类型初始化BigDecimal对象。(作乘法运算时得到的是精确值)         BigDecimal strA = new BigDecimal("100");         BigDecimal strB = new BigDecimal("0.05");         System.out.println("strA + strB = " + strA.multiply(strB)); }       打印结果如下, numA + numB = 0.00300000000000000005551115123125782085820576136538628584587058372823258067807472571075777523219585418701171875 numC + numD = 5.00000000000000027755575615628913510590791702270507812500 strA + strB = 5.00 4、除法        对应的方法主要有: (1) public BigDecimal divide(BigDecimal divisor);        返回一个BigDecimal,其值为(this/divisor),首选小数位数为(this.scale()-divisor.scale());如果无法表示精确的商(因为它具有非终止的十进制扩展),则会引发算术异常。 (2) public BigDecimal divide(BigDecimal divisor,MathContext mc);        返回值为(this/divisor)的BigDecimal,并根据上下文设置进行舍入。  (3) public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode);        返回一个BigDecimal,其值为(this/divisor),其小数位数与指定的相同。如果必须执行舍入才能生成具有指定比例的结果,则应该用指定的舍入模式。 (4) public BigDecimal divide(BigDecimal divisor,int scale,RoundingMode roundingMode);        返回一个BigDecimal,其值为(this/divisor),其小数位数与指定的相同(由第二个参数scale指定)。如果必须执行舍入才能生成具有指定比例的结果,则应该用指定的舍入模式(由第三个参数roundingMode指定)。        (3)(4)两个方法其实是一样的,区别只在于第三个参数的指定方式。第一个方法是int roundingMode,即传入的是一个整型数字,而第二个方法是RoundingMode roundingMode,即传入的是一个RoundingMode对象。         这里需要对RoundingMode作一下介绍。      RoundingMode        RoundingMode是一个枚举类,内部有8个枚举常量,分别是:        UP:远离0的舍入模式,在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。放到一维坐标轴上就很容易理解,就是以0为分隔点,0右侧部分一直向右舍入,0左侧部分一直向左侧舍入。例如0.333保留两位小数,采用UP舍入模式的结果为0.34,-0.333保留两位小数,采用UP舍入模式的结果为-0.34。        DOWN:和UP相反,是接近零的舍入模式。理解了UP舍入模式,就很容易理解DOWN舍入模式。例如0.333保留两位小数,采用DOWN舍入模式的结果为0.33,-0.333保留两位小数,采用DOWN舍入模式的结果为-0.33。可以发现,在采用DOWN模式进行舍入的时候,直接把需要舍弃的位数丢掉就行了。        CEILING:CEILING英文是天花板的意思,可以理解为向”大“舍入。如果 BigDecimal 为正,则舍入行为与 UP 相同,如果为负,则舍入行为与 DOWN 相同。例如0.333保留两位小数,采用CEILING舍入模式的结果为0.34(0.34 >0.333),-0.333保留两位小数,采用CEILING舍入模式的结果为-0.33(-0.33>0.333 )。        FLOOR:与CEILING相反,FLOOR有地板的意思,可以理解为向”小“舍入。如果 BigDecimal 为正,则舍入行为与 DOWN 相同,如果为负,则舍入行为与 UP 相同。例如0.333保留两位小数,采用FLOOR舍入模式的结果为0.33,-0.333保留两位小数,采用FLOOR舍入模式的结果为-0.34。        HALF_UP:向”最接近的“数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 UP 相同,否则舍入行为与 DOWN 相同。其实就是四舍五入。        HALF_DOWN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与UP 相同,否则舍入行为与 DOWN 相同。与四舍五入的思路是一样的,只不过这里是”五舍六入“。如果扣字面意思的话,也很好理解,HALF_UP,half是一半的意思,也就是5,UP是向上的意思,就可以理解为5向上入,即四舍五入。同理,HALF_DOWN,5向下舍,即五舍六入。        HALF_EVEN:向“最接近的”数字舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 UP 相同,如果为偶数,则舍入行为与 DOWN 相同。如0.135,保留两位小数,舍弃5,因为3是奇数,所以与UP相同,结果为0.14;0.125,保留两位小数,舍弃5,因为2是偶数,所以与DOWN相同,结果为0.12.        UNNECESSARY:以断言请求的操作具有精确的结果,因此不需要舍入。如果有舍入,会报java.lang.ArithmeticException异常。        关于这几个舍入模式的结果,亦可参照下面的表进行快速学习。  保留整数,不同舍入模式下的计算结果 原数    UP    DOWN    CEILING    FLOOR    HALF_UP    HALF_DOWN    HALF_EVEN    UNNECESSARY 5.5    6    5    6    5    6    5    6    throw ArithmeticException 2.5    3    2    3    2    3    2    2    throw ArithmeticException 1.6    2    1    2    1    2    2    2    throw ArithmeticException 1.1    2    1    2    1    1    1    1    throw ArithmeticException 1.0    1    1    1    1    1    1    1    1 -1.0    -1    -1    -1    -1    -1    -1    -1    -1 -1.1    -2    -1    -1    -2    -1    -1    -1    throw ArithmeticException -1.6    -2    -1    -1    -2    -2    -2    -2    throw ArithmeticException -2.5    -3    -2    -2    -3    -3    -2    -2    throw ArithmeticException -5.5    -6    -5    -5    -6    -6    -5    -6    throw ArithmeticException                                                  代码示例如下,  @Test public void testDivide() {         BigDecimal numA = new BigDecimal("1");         BigDecimal numB = new BigDecimal("-1");         BigDecimal numC = new BigDecimal("3");         // 保留两位小数,舍入模式为UP         System.out.println("1/3保留两位小数(UP) = " + numA.divide(numC, 2, RoundingMode.UP));         System.out.println("-1/3保留两位小数(UP) = " + numB.divide(numC, 2, RoundingMode.UP));         // 保留两位小数,舍入模式为DOWN         System.out.println("1/3保留两位小数(DOWN) = " + numA.divide(numC, 2, RoundingMode.DOWN));         System.out.println("-1/3保留两位小数(DOWN) = " + numB.divide(numC, 2, RoundingMode.DOWN));         // 保留两位小数,舍入模式为CEILING         System.out.println("1/3保留两位小数(CEILING) = " + numA.divide(numC, 2, RoundingMode.CEILING));         System.out.println("-1/3保留两位小数(CEILING) = " + numB.divide(numC, 2, RoundingMode.CEILING));         // 保留两位小数,舍入模式为FLOOR         System.out.println("1/3保留两位小数(FLOOR) = " + numA.divide(numC, 2, RoundingMode.FLOOR));         System.out.println("-1/3保留两位小数(FLOOR) = " + numB.divide(numC, 2, RoundingMode.FLOOR));           BigDecimal numD = new BigDecimal("1");         BigDecimal numE = new BigDecimal("-1");         BigDecimal numF = new BigDecimal("8");         // 保留两位小数,舍入模式为HALF_UP         System.out.println("1/8(=0.125)保留两位小数(HALF_UP) = " + numD.divide(numF, 2, RoundingMode.HALF_UP));         System.out.println("-1/8(=0.125)保留两位小数(HALF_UP) = " + numE.divide(numF, 2, RoundingMode.HALF_UP));         // 保留两位小数,舍入模式为HALF_DOWN         System.out.println("1/8(=0.125)保留两位小数(HALF_DOWN) = " + numD.divide(numF, 2, RoundingMode.HALF_DOWN));         System.out.println("-1/8(=0.125)保留两位小数(HALF_DOWN) = " + numE.divide(numF, 2, RoundingMode.HALF_DOWN));           // 保留两位小数,舍入模式为HALF_EVEN         System.out.println("0.54/4(=0.135)保留两位小数(HALF_EVEN) = " + new BigDecimal("0.54").divide(new BigDecimal("4"), 2, RoundingMode.HALF_EVEN));         System.out.println("1/8(=0.125)保留两位小数(HALF_EVEN) = " + numE.divide(numF, 2, RoundingMode.HALF_EVEN));           //UNNECESSARY,会报异常         System.out.println("1/8(=0.125) = " + numE.divide(numF,  RoundingMode.UNNECESSARY)); } 打印结果:  1/3保留两位小数(UP) = 0.34 -1/3保留两位小数(UP) = -0.34 1/3保留两位小数(DOWN) = 0.33 -1/3保留两位小数(DOWN) = -0.33 1/3保留两位小数(CEILING) = 0.34 -1/3保留两位小数(CEILING) = -0.33 1/3保留两位小数(FLOOR) = 0.33 -1/3保留两位小数(FLOOR) = -0.34 1/8(=0.125)保留两位小数(HALF_UP) = 0.13 -1/8(=0.125)保留两位小数(HALF_UP) = -0.13 1/8(=0.125)保留两位小数(HALF_DOWN) = 0.12 -1/8(=0.125)保留两位小数(HALF_DOWN) = -0.12 0.54/4(=0.135)保留两位小数(HALF_EVEN) = 0.14 1/8(=0.125)保留两位小数(HALF_EVEN) = -0.12  java.lang.ArithmeticException: Rounding necessary  5、绝对值    public BigDecimal abs();        返回一个BigDecimal,其值为该BigDecimal的绝对值,其小数位数为this.scale()。     public BigDecimal abs(MathContext mc);       返回一个BigDecimal,其值是此BigDecimal的绝对值,并根据上下文设置进行舍入。        代码示例,      @Test     public void testAbs() {         BigDecimal a = new BigDecimal("1.234");         BigDecimal b = new BigDecimal("-1.234");         System.out.println("1.234的绝对值:" + a.abs());         System.out.println("-1.234的绝对值:" + b.abs());         System.out.println("-1.234的绝对值,保留两位有效数字:" + b.abs(new MathContext(2)));     }        打印结果为:  1.234的绝对值:1.234 -1.234的绝对值:1.234 -1.234的绝对值,保留两位有效数字:1.2 ———————————————— 原文链接:https://blog.csdn.net/AttleeTao/article/details/103950423 
  • [技术干货] BigDecimal 除法
    BigDecimal 除法 除法 常用方法 divide(BigDecimal divisor, int scale, int roundingMode)  参数    释义 divisor    被除数 scale    保留小数位 roundingMode    舍入模式 示例 BigDecimal b1 = new BigDecimal("3"); BigDecimal b2 = new BigDecimal("7"); //计算b1除以b2的值并四舍五入保留两位小数 b1 = b1.divide(b2, 2 ,BigDecimal.ROUND_HALF_UP); System.out.println("结果:"+b1); 结果:  结果:0.43 舍入模式 模式    释义 ROUND_UP    只要舍去的值不为0,则进1。如1.010保留一位小数为1.1,保留两位小数为1.01。 ROUND_DOWN    无论舍去的值是多少,直接舍去(截断)。 ROUND_CEILING    向正无穷方向舍入,若是正数则表现为’ROUND_UP’,若为负数则表现为’ROUND_DOWN’。 ROUND_FLOOR    向负无穷方向舍入,若是正数则表现为’ROUND_DOWN’,若为负数则表现为ROUND_UP’’。 ROUND_HALF_UP    四舍五入。 ROUND_HALF_DOWN    五舍六入。 ROUND_HALF_EVEN    若舍去位数左边值为偶数,则五舍六入。若舍去位数左边值为奇数,则四舍五入。 ROUND_UNNECESSARY    断言得数为精确的结果,若不是则抛出ArithmeticException。 ROUND_UP 示例:  BigDecimal b1 = new BigDecimal("1.11"); BigDecimal b2 = new BigDecimal("1.10"); BigDecimal one = new BigDecimal("1");  b1 = b1.divide(one, 1 ,BigDecimal.ROUND_UP); b2 = b2.divide(one, 1, BigDecimal.ROUND_UP);  System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果:  b1结果:1.2 b2结果:1.1 ROUND_DOWN 示例:  BigDecimal b1 = new BigDecimal("1.11"); BigDecimal b2 = new BigDecimal("1.10"); BigDecimal one = new BigDecimal("1");  b1 = b1.divide(one, 1 ,BigDecimal.ROUND_DOWN); b2 = b2.divide(one, 1, BigDecimal.ROUND_DOWN);  System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果:  b1结果:1.1 b2结果:1.1 ROUND_CEILING 示例:  BigDecimal b1 = new BigDecimal("-1.11"); BigDecimal b2 = new BigDecimal("1.11"); BigDecimal one = new BigDecimal("1");  b1 = b1.divide(one, 1 ,BigDecimal.ROUND_CEILING); b2 = b2.divide(one, 1, BigDecimal.ROUND_CEILING);  System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果:  b1结果:-1.1 b2结果:1.2 ROUND_FLOOR 示例:  BigDecimal b1 = new BigDecimal("-1.11"); BigDecimal b2 = new BigDecimal("1.11"); BigDecimal one = new BigDecimal("1");  b1 = b1.divide(one, 1 ,BigDecimal.ROUND_FLOOR); b2 = b2.divide(one, 1, BigDecimal.ROUND_FLOOR);  System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果:  b1结果:-1.2 b2结果:1.1 ROUND_HALF_UP 示例:  BigDecimal b1 = new BigDecimal("1.14"); BigDecimal b2 = new BigDecimal("1.15"); BigDecimal one = new BigDecimal("1");  b1 = b1.divide(one, 1 ,BigDecimal.ROUND_HALF_UP); b2 = b2.divide(one, 1, BigDecimal.ROUND_HALF_UP);  System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果:  b1结果:1.1 b2结果:1.2 ROUND_HALF_DOWN 示例: BigDecimal b1 = new BigDecimal("1.15"); BigDecimal b2 = new BigDecimal("1.16"); BigDecimal one = new BigDecimal("1"); b1 = b1.divide(one, 1 ,BigDecimal.ROUND_HALF_DOWN); b2 = b2.divide(one, 1, BigDecimal.ROUND_HALF_DOWN); System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果: b1结果:1.1 b2结果:1.2 ROUND_HALF_EVEN 示例: BigDecimal b1 = new BigDecimal("1.15"); BigDecimal b2 = new BigDecimal("1.25"); BigDecimal one = new BigDecimal("1"); b1 = b1.divide(one, 1 ,BigDecimal.ROUND_HALF_EVEN); b2 = b2.divide(one, 1, BigDecimal.ROUND_HALF_EVEN); System.out.println("b1结果:"+b1); System.out.println("b2结果:"+b2); 结果: b1结果:1.2 b2结果:1.2 ROUND_UNNECESSARY 结果精度确定: BigDecimal b1 = new BigDecimal("10"); BigDecimal one = new BigDecimal("2"); b1 = b1.divide(one, 1 ,BigDecimal.ROUND_UNNECESSARY); System.out.println("b1结果:"+b1); 结果: b1结果:5.0 结果精度不确定: BigDecimal b1 = new BigDecimal("3"); BigDecimal one = new BigDecimal("7"); b1 = b1.divide(one, 1 ,BigDecimal.ROUND_UNNECESSARY); System.out.println("b1结果:"+b1); 结果: Exception in thread "main" java.lang.ArithmeticException: Rounding necessary at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4148) at java.math.BigDecimal.needIncrement(BigDecimal.java:4204) at java.math.BigDecimal.divideAndRound(BigDecimal.java:4112) at java.math.BigDecimal.divide(BigDecimal.java:5183) at java.math.BigDecimal.divide(BigDecimal.java:1561) at test.test.main(test.java:10) ———————————————— 原文链接:https://blog.csdn.net/weixin_42889176/article/details/108188226 
  • [技术干货] LocalDateTime获取 年月日时分秒和判断日期大小
    环境:java version "13.0.1"。创建一个DateUtils类,提供三个常用方法:String 转换 LocalDateTime的方法。获取LocalDateTime获取 年月日时分秒。两个时间的间隔毫秒数,常用于判断日期大小。import org.springframework.util.StringUtils; import java.text.DateFormat; import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalDateTime; import java.time.Month; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; public class DateUtils { private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"); public static void getTimeByDate() { Date date = new Date(); DateFormat df1 = DateFormat.getDateInstance();//日期格式,精确到日 System.out.println(df1.format(date)); DateFormat df2 = DateFormat.getDateTimeInstance();//可以精确到时分秒 System.out.println(df2.format(date)); DateFormat df3 = DateFormat.getTimeInstance();//只显示出时分秒 System.out.println(df3.format(date)); DateFormat df4 = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL); //显示日期,周,上下午,时间(精确到秒) System.out.println(df4.format(date)); DateFormat df5 = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); //显示日期,上下午,时间(精确到秒) System.out.println(df5.format(date)); DateFormat df6 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); //显示日期,上下午,时间(精确到分) System.out.println(df6.format(date)); DateFormat df7 = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); //显示日期,时间(精确到分) System.out.println(df7.format(date)); } public static void getTimeByCalendar() { Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR);//获取年份 int month = cal.get(Calendar.MONTH);//获取月份 int day = cal.get(Calendar.DATE);//获取日 int hour = cal.get(Calendar.HOUR);//小时 int minute = cal.get(Calendar.MINUTE);//分 int second = cal.get(Calendar.SECOND);//秒 int WeekOfYear = cal.get(Calendar.DAY_OF_WEEK);//一周的第几天 System.out.println("现在的时间是:公元" + year + "年" + month + "月" + day + "日," + hour + "时" + minute + "分" + second + "秒, 一周的第几天:" + WeekOfYear); } /** * 获取指定时间的年月日等 * @param localDateTime 指定时间 */ public static void getTimeByGivenLocalDateTime(LocalDateTime localDateTime) { if (null == localDateTime) { localDateTime = LocalDateTime.now(); } int dayOfYear = localDateTime.getDayOfYear(); int dayOfMonth = localDateTime.getDayOfMonth(); DayOfWeek dayOfWeek = localDateTime.getDayOfWeek(); System.out.println("今天是" + localDateTime + "\n" + "本年当中第" + dayOfYear + "天" + "\n" + "本月当中第" + dayOfMonth + "天" + "\n" + "本周中星期" + dayOfWeek.getValue() + "-即" + dayOfWeek + "\n"); //获取指定时间的年月日时分秒 int year = localDateTime.getYear(); Month month = localDateTime.getMonth(); int day = localDateTime.getDayOfMonth(); int hour = localDateTime.getHour(); int minute = localDateTime.getMinute(); int second = localDateTime.getSecond(); System.out.println("今天是" + localDateTime + "\n" + "年 : " + year + "\n" + "月 : " + month.getValue() + "-即 " + month + "\n" + "日 : " + day + "\n" + "时 : " + hour + "\n" + "分 : " + minute + "\n" + "秒 : " + second + "\n" ); } /** * String 转换 LocalDateTime * @param dateStr * @return */ public static LocalDateTime string2Time(String dateStr) { if (StringUtils.isEmpty(dateStr)) { return null; } LocalDateTime time = LocalDateTime.parse(dateStr, fmt); System.out.println("time:" + time); return time; } /** * 求时间间隔,返回值大于零说明结束时间晚于开始时间 * @param beginTime 开始时间 * @param endTime 结束时间 * @return 毫秒,等于endTime 减去 beginTime后的毫秒数 */ public static Long between(LocalDateTime beginTime, LocalDateTime endTime) { Duration duration = Duration.between(beginTime, endTime); return duration.toMillis(); } public static void main(String[] args) { getTimeByDate(); System.out.println("********getTimeByCalendar********************"); getTimeByCalendar(); System.out.println("********getTimeByGivenLocalDateTime****************"); String str = "2021-10-09 15:32:06:777"; LocalDateTime ldt = string2Time(str); getTimeByGivenLocalDateTime(ldt); System.out.println("********between****************"); str = "2021-10-09 17:32:06:777"; LocalDateTime endTime = string2Time(str); System.out.println(between(ldt, endTime)); } } 测试结果:2021年10月10日 2021年10月10日 下午12:06:02 下午12:06:02 2021年10月10日星期日 中国标准时间 下午12:06:02 2021年10月10日 CST 下午12:06:02 2021/10/10 下午12:06 2021年10月10日 下午12:06:02 ********getTimeByCalendar******************** 现在的时间是:公元2021年9月10日,0时6分2秒, 一周的第几天:1 ********getTimeByGivenLocalDateTime**************** time:2021-10-09T15:32:06.777 今天是2021-10-09T15:32:06.777 本年当中第282天 本月当中第9天 本周中星期6-即SATURDAY 今天是2021-10-09T15:32:06.777 年 : 2021 月 : 10-即 OCTOBER 日 : 9 时 : 15 分 : 32 秒 : 6 ********between**************** time:2021-10-09T17:32:06.777 7200000 Process finished with exit code 0原文链接:https://www.cnblogs.com/east7/p/15389080.html
  • [技术干货] @PathVariable注解的用法和作用
    @PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值 语法:  @PathVariable("xxx") 通过 @PathVariable 可以将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“)  @RequestMapping(value=”user/{id}/{name}”) 请求路径:http://localhost:8080/hello/show5/1/james 测试环境: 环境:jdk1.8 Tomcat8.5  idea2018  manven父工程子模块 步骤: 1、创建web工程、引入依赖 2、配置SpringMvc入口文件 --DispatcherServlet--为总调度、web.xml里配置 3、创建Springmvc.xml文件--理解为:适配器(这里不需要自已指定适配、springmvc会自动指定)--视图解析器 4、创建 业务处理器 Controller类 步骤1、2、3、参考:SpringMvc入门案例:https://blog.csdn.net/sswqzx/article/details/84171999 业务处理器HelloController.java  package com.day01springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView;   /**  * @ Author     :ShaoWei Sun.  * @ Date       :Created in 20:58 2018/11/16  */ @Controller @RequestMapping("hello") public class HelloController2 {     /**      *3、占位符映射      * 语法:@RequestMapping(value=”user/{userId}/{userName}”)      * 请求路径:http://localhost:8080/hello/show5/1/james      * @param ids      * @param names      * @return      */     @RequestMapping("show5/{id}/{name}")     public ModelAndView test5(@PathVariable("id") Long ids ,@PathVariable("name") String names){         ModelAndView mv = new ModelAndView();         mv.addObject("msg","占位符映射:id:"+ids+";name:"+names);         mv.setViewName("hello2");         return mv;     } }                                              ———————————————— 原文链接:https://blog.csdn.net/sswqzx/article/details/84194979 
  • [技术干货] Ambiguous handler methods mapped for 'xxx'的解决办法
    Ambiguous handler methods mapped for '/book/es' 一. 问题描述 我们在创建Restful风格的url的时候,可以直接将参数变量值放入到url中,然后传递到后台,后台自动识别对应的方法,非常的方便.  但是如果我们的接口方法中出现了方法重载的情况,则有可能会出现一些问题,如下:  package com.syc.boot.web;   import com.syc.boot.domain.Book; import com.syc.boot.service.BookService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Optional;   @Slf4j @RestController public class ElasticController {       @Autowired     private BookService bookService;       @RequestMapping("/book/{id}")     public Book getBookById(@PathVariable String id) {         Optional<Book> opt = bookService.findById(id);         Book book = opt.get();         log.warn(book.toString());         return book;     }       @RequestMapping("/book/{title}")     public Page<Book> getBookByTitleHighLight(@PathVariable("title") String title) {         Page<Book> books = bookService.findByTitle(title, 1, 10);         log.warn(books.toString());         return books;     }   } 此时在浏览器地址栏访问 http://localhost:8080/book/1 或 http://localhost:8080/book/es教程 时均会出现如下错误.  java.lang.IllegalStateException: Ambiguous handler methods mapped for '/book/es': {public com.syc.boot.domain.Book com.syc.boot.web.ElasticController.getBookById(java.lang.String), public org.springframework.data.domain.Page com.syc.boot.web.ElasticController.getBookByTitleHighLight(java.lang.String)}   意思是说我们的Controller中,有两个模棱两可的处理方法,这两个方法有歧义,无法分清谁是谁.因为Spring无法根据传参的类型自动匹配到可以处理的方法.  此时如果我们依然想使用Restful编程风格,就必须改变请求得url格式,保证url对应的方法不会产生歧义.  二. 解决办法 针对上面的例子,只要保证一个类中多个方法请求的接口方法不一样就可以了,可以是请求方法,请求参数,url等不同,这样就可以匹配到不同的方法.  package com.syc.boot.web;   import com.syc.boot.domain.Book; import com.syc.boot.service.BookService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Optional;   @Slf4j @RestController public class ElasticController {       @Autowired     private BookService bookService;       @RequestMapping("/book/id/{id}")     public Book getBookById(@PathVariable String id) {         Optional<Book> opt = bookService.findById(id);         Book book = opt.get();         log.warn(book.toString());         return book;     }       @RequestMapping("/book/title/{title}")     public Page<Book> getBookByTitleHighLight(@PathVariable("title") String title) {         Page<Book> books = bookService.findByTitle(title, 1, 10);         log.warn(books.toString());         return books;     }  } 此时再到浏览器地址栏通过 http://localhost:8080/user/id/1 或 http://localhost:8080/user/title/es 来请求. 就可以可以正常访问到后台的对象内容了. ———————————————— 原文链接:https://blog.csdn.net/syc000666/article/details/94978731 
  • [技术干货] Linux下Graphics2D.drawString(message, px, py)乱码问题
     今天遇到了一个乱码问题,合成的小票图片上的中文全部变成了口口口,后来在网上查了资料,发现是Graphics2D用了宋体字,而linux服务器上没有对应的字体库。 把本地的字体库上传上去就解决了。 本地字体库位置:(winXP 宋体字)C:\WINDOWS\Fonts\simsun.ttc simsun.ttc 改名为 simsun.ttf 复制到linux:/usr/java/jdk1.7.0_79/jre/lib/fonts/ 参考文档:http://ember319.iteye.com/blog/254015 以下为原文内容 问题描述: 用户使用手机登录我们系统,在订购产品时,需要从服务器端下载一张图片,显示到手机屏幕上,该图片上可能包含中英文字符,视情况而定。 结果测试下来,英文没有问题,中文打印为方块。 环境描述: 开发平台为WinXP,测试环境为Solaris10。   数据来源: 用以打印到图片的信息通过Web serice从另一系统获取。 下面说一下解决的思路, 1、是否字符编码问题 为了检查获取的字符串的编码是否正确,使用下面的方式尝试多种字符集编码的转换,但是都没有效果; Java代码   String message=“...”;//ws获取到的信息   new String(message.getBytes("utf8"),"gbk");   回过来仔细考虑了一下我们的系统,系统的jsp页面及数据库编码都是采用的utf-8编码,无论是录入、保存还是显示都没有出现中文乱码问题;尤其是系统之间多处使用ws方式同步数据,也没有出现乱码问题,于是认为ws获取到的信息同样是utf-8编码。为了验证这个想法,做了一个jsp页面,使用utf-8的方式显示获取到的信息,在不经过任何转换的前提下,正确显示中文。最终确定,不是字符集编码的问题。 2、是否打印过程发生了问题 查看了JDK API的说明,发现在使用字体的时候,我没有特别设定,使用了默认字体。写了一个简单的输出平台默认字体的类,得到的字体却是一样的信息: Java代码   Default font:java.awt.Font[family=Dialog,name=Dialog,style=plain,size=12]   1) 指定打印到图片的字体为中文 Java代码   g2d.setFont(new Font("宋体", Font.PLAIN, 12));   2) 上传WinXp 中的宋体字体文件到服务器(Solaris) 我的本地字体文件地址是“C:\WINDOWS\Fonts\simsun.ttc”,上传到服务器地址是:“/usr/jdk/instances/jdk1.5.0/jre/lib/fonts/simsun.ttf” 打印图片中文乱码问题到这里就解决了。 考虑起来,因为打印字符成为图片,需要用到像素排列,如果字体不对,的确是没有办法打印啊。 ———————————————— 原文链接:https://blog.csdn.net/itjauser/article/details/88723604 
  • [技术干货] 玩转MybatisPlus分页插件二:自定义分页函数[简单案例+复杂案例]+Demo实践常规处理操作
    前言 这种场景非常常见,就是当前的查询语句是我们自定义的,想要在自己写的sql语句中通过分页插件来实现分页功能(比如说我需要在教师表中查询name不为空的⽤户,并且职称为讲师,年龄小于40的教师对象并将查询结果分页等等…) 还没有看过上一篇的朋友,这里是传送门:玩转MyBatis-Plus分页插件一:基本使用 如果对MybatisPlus分页插件基本使用已经非常熟练的朋友就接着往下看吧  开整 玩自定义分页必须掌握的神器就是:条件构造器wrapper、Lambda条件构造器如果还没有学会的朋友可以去官网先学习一波 前面说到,分页它的本质就是内部封装了一个拦截器,对于满足满足条件的数据,起到一个过滤的作用这会和我们后续的自定义条件有关,毕竟也是同样的道理,你自定义一个条件来“筛选数据”,只展示满足条件的数据 上一篇博文我们在mapper的selectPage函数当中将wrapper设为null,则是查询全部数据。 自定义分页的核心就是:参照selectPage函数 自定义分页的重点就是:自定义函数的返回值必须为Page对象,你所定义的函数的第一个参数类型必须为Page  简单案例[体验一波] 目标数据表t_product  SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;  -- ---------------------------- -- Table structure for t_product -- ---------------------------- DROP TABLE IF EXISTS `t_product`; CREATE TABLE `t_product`  (   `id` int(0) NULL DEFAULT NULL,   `name` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `price` decimal(10, 0) NULL DEFAULT NULL,   `stock` int(0) NULL DEFAULT NULL,   `proPic` varchar(765) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `isHot` bit(1) NULL DEFAULT NULL,   `isSwiper` bit(1) NULL DEFAULT NULL,   `swiperPic` varchar(765) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '轮转图片的地址',   `swiperSort` int(0) NULL DEFAULT NULL COMMENT '排序',   `typeId` int(0) NULL DEFAULT NULL COMMENT '类别id',   `hotDateTime` datetime(0) NULL DEFAULT NULL COMMENT '设置热卖的时间',   `productIntroImgs` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `productParaImgs` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `description` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品描述' ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;  -- ---------------------------- -- Records of t_product -- ---------------------------- INSERT INTO `t_product` VALUES (1, '小米电视大师 82英寸', 10999, 3451, '21.png', b'0', b'1', '2.jpg', 3, 14, '2022-04-26 21:36:34', '\"\"', '\"\"', '\"\"'); INSERT INTO `t_product` VALUES (4, 'Xiaomi 11', 3799, 3232, '6.png', b'1', b'0', 'default.jpg', 0, 2, '2022-05-31 21:36:34', '<img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/11.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/11.jpg\'></img>', '「全版本领券立减200元,券后价3299元起;享至高24期免息;赠手机保护壳*1;【全款支付套装】赠果冻包」'); INSERT INTO `t_product` VALUES (5, 'Redmi K40 游戏增强版', 2299, 2005, '11.png', b'0', b'1', '1.jpg', 1, 39, NULL, '\"\"', '\"\"', '\"\"'); INSERT INTO `t_product` VALUES (6, 'Xiaomi 11 Pro', 4499, 2343, '1.png', b'1', b'0', 'default.jpg', 0, 2, '2021-09-12 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (7, 'Xiaomi MIX FOLD折叠屏手机', 7999, 2222, '2.png', b'1', b'0', 'default.jpg', 0, 3, '2021-10-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (8, 'Note 9 5G', 1199, 1111, '3.png', b'1', b'0', 'default.jpg', 0, 40, '2022-02-23 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (9, 'Xiaomi 10S', 2999, 1111, '4.png', b'1', b'0', 'default.jpg', 0, 2, '2021-12-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (10, 'Note 9 Pro 5G', 1399, 2222, '5.png', b'1', b'0', 'default.jpg', 0, 40, '2021-11-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (11, '黑鲨4', 2499, 3322, '7.png', b'1', b'0', 'default.jpg', 0, 41, '2021-11-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (12, 'Redmi  K40 Pro 系列', 2499, 3244, '8.png', b'1', b'0', 'default.jpg', 0, 39, '2021-11-28 21:36:34', NULL, NULL, NULL); INSERT INTO `t_product` VALUES (14, 'Xiaomi Civi', 2599, 4444, '9.png', b'0', b'0', 'default.jpg', 0, 1, NULL, '<img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/1.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/2.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/3.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/1.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/2.jpg\'></img>', '「购机至高享24期免息;赠Redmi AirDots 2真无线蓝牙耳机;赠Keep会员7天体验卡;+110元得Air2 SE蓝牙耳机」'); INSERT INTO `t_product` VALUES (15, 'Xiaomi 11 Ultra', 5499, 4444, '10.png', b'0', b'0', 'default.jpg', 0, 2, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (16, '小米平板5', 1999, 444, '13.png', b'0', b'0', 'default.jpg', 0, 5, NULL, '<img src=\'http://localhost:8001/image/productIntroImgs/111.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/222.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/333.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/111.jpg\'></img>', '11英寸大屏 2.5K超清显示 120Hz高刷新率'); INSERT INTO `t_product` VALUES (17, '小米平板5 Pro', 2499, 444, '14.png', b'0', b'0', 'default.jpg', 0, 5, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (19, 'RedmiBook Pro 15 增强版', 5299, 444, '15.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (20, 'Redmi G 2021', 6499, 1999, '16.png', b'0', b'1', '3.jpg', 2, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (21, 'Redmi G 2021 锐龙版', 7499, 2000, '17.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (22, 'RedmiBook Pro 14 增强版', 4999, 777, '18.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (23, '小米笔记本 Pro 14 锐龙版', 5499, 666, '19.png', b'0', b'0', 'default.jpg', 0, 7, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (24, '小米笔记本 Pro 14 增强版', 5499, 666, '20.png', b'0', b'0', 'default.jpg', 0, 7, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (26, '1', 1, 1, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '1', '1', NULL); INSERT INTO `t_product` VALUES (27, '1', 2, 3, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '5', '6', '4'); INSERT INTO `t_product` VALUES (28, '2', 3, 4, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '6', '7', '5'); INSERT INTO `t_product` VALUES (29, '1', 1, 1, 'default.jpg', b'0', b'0', 'default.jpg', 0, 11, NULL, '1', '1', '1'); INSERT INTO `t_product` VALUES (30, '223335552', 322, 22355, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '22333522', '22333544442', '22333511');  SET FOREIGN_KEY_CHECKS = 1; 例如:想要查询出价格大于某个值的产品并进行分页(这里为了测试sql定义了该值为1400) 1.写出sql语句 共查询到18条数据 2.自定义分页函数 @Select("SELECT * FROM `t_product` where price>#{price}") Page<Product> selectPageByMyself(@Param("page") Page<Product> page, @Param("price") Integer price); 3.测试 @SpringBootTest public class MybatisPageTest {     @Autowired     public ProductMapper mapper;          @Test     public void test(){         Page<Product> page = new Page<>(1,10);         mapper.selectPageByMyself(page,1400);         System.out.println(page);     } } 我这里设置了每页10条数据 可以看到将第一页的数据全部查询出来了,修改参数即可查看第二页的数据 复杂 可以看到我这里只是简单的对price进行了筛选,如果说我现在的查询条件变得很复杂了,该怎么处理呢?这里要使用到的就是我们一直提到的条件构造器来解决这个问题 比如,我想要查询商品名称模糊查询,价格小于[前端传过来的值],isHot参数为[前端传过来的值]的商品有哪些,并进行分页 常规操作 一般遇到这种指定条件的分页情况,都是通过前端传过来一个vo对象,然后根据这个vo对象进行操作解析,简单说明就是:我们不需要对product类全部字段进行操作,只需要几个重要字段就行了 以下是使用了thymeleaf来做测试  1.创建vo对象[需要什么就添加什么,和实体类名称类型保持一致import lombok.Data;  /**  * @description: product vo对象类  * @author: xmonster_大魔王  * @create: 2022-07-28 20:00  **/  @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ProductParam {      private String name;     private int price;     private Boolean ishot;  }  2.编写mapper层 /**  * <p>  *  Mapper 接口  * </p>  *  * @author xmonster_大魔王  * @since 2022-06-16  */ @Mapper public interface ProductMapper extends BaseMapper<Product> {      @Select("SELECT * FROM `t_product` where price>#{price}")     Page<Product> selectPageByMyself(@Param("page") Page<Product> page, @Param("price") Integer price);      @Select("SELECT * FROM `t_product` ${ew.customSqlSegment}")     Page<Product> selectPageByMyself2(@Param("page") Page<Product> page,@Param(Constants.WRAPPER) Wrapper<Product> wrapper); } 这里主要看selectPageByMyself2方法啦,直接传wrapper即可,这里的sql语句当中使用了 ${ew.customSqlSegment},那么它具体的用法是什么呢?  ${ew.customSqlSegment} 拼接where后的语句(包括where,需注意在动态SQL中不处于标签内) 我们来一起看看我们的SQL语句就明白啦  3.编写controller[重点] 这里可以看到,我们编写了一个ProductParam的对象,并对这个对象填入我们需要查询的信息,这里我要查询商品名称中包含“x”,价格小于8000,并且ishot属性为true  @GetMapping("") public String index(Model model){     ProductParam param = new ProductParam().builder().name("x").price(8000).ishot(true).build();     Page<Product> page = new Page<>(1,10);     LambdaQueryWrapper<Product> wrapper = Wrappers.lambdaQuery(Product.class)             .like(Product::getName, param.getName())             .lt(Product::getPrice, param.getPrice())             .eq(Product::getIshot, param.getIshot());     mapper.selectPageByMyself2(page,wrapper);     model.addAttribute("productList",page.getRecords());     return "index"; } 4.前端展示 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head>     <meta charset="UTF-8">     <title>product分页测试</title>     <link rel="stylesheet" th:href="@{/asserts/bootstrap5/css/bootstrap.min.css}" />  <!--    <link rel="stylesheet" th:href="@{/message/message.css}"/>--> </head> <body> <!--页面展示--> <table class="table table-dark table-hover" style="margin:10px auto;width: 800px">     <thead>         <th>name</th>         <th>price</th>         <th>stock</th>         <th>isHot</th>         <th>typeId</th>     </thead>     <tbody>        <tr th:each="product: ${productList}">             <td ><th:block th:text="${product.name}"></th:block></td>             <td ><th:block th:text="${product.price}"></th:block></td>             <td ><th:block th:text="${product.stock}"></th:block></td>             <td ><th:block th:text="${product.ishot}"></th:block></td>             <td ><th:block th:text="${product.typeid}"></th:block></td>         </tr>     </tbody>  </table> <script th:src="@{/asserts/bootstrap5/js/bootstrap.bundle.min.js}"></script> <script th:src="@{/asserts/jquery/jquery.min.js}"></script> </body> </html>  5.效果 看到sql语句后面自动添加了where条件进行筛选,具体和${ew.customSqlSegment}有关的其他信息可以去查查资料深入一下它可是非常常用的 好啦,这就是自定义分页函数基础的内容,体验了一波之后我相信更多的朋友想要的效果当然是和前端进行交互的那种真正感受分页的快乐,这里的操作都是在后台进行输出,前台只是简单的展示,与前端进行交互的内容我会放在下一期~也会介绍很多好看的分页效果 ———————————————— 原文链接:https://blog.csdn.net/Xmumu_/article/details/126032264 
  • [技术干货] 《Java并发编程实战》读书笔记三:使用Executor框架
    一、Executor框架选择串行的方式执行任务,串行处理机制通常无法提高高吞吐率和快速响应性,于是我们可以显式地为任务创建线程,为每一个请求创建一个线程来执行任务,这样可以实现更高的响应性。但是这样会带来很多问题:1. 线程的创建和销毁开销非常高2. 活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程就会闲置。大量空闲的线程会占用许多内存,给GC带来很大的压力,而且大量线程在竞争CPU资源时还会产生其他性能开销。3. 可创建线程的数量存在一个限制。这个限制值受多个平台制约,包括JVM的启动参数,Thread构造函数中请求栈的大小以及操作系统的限制。总得来说,增加线程可以提高系统的吞吐率但是如果超出了这个范围,再创建更多的线程只会降低程序的速度,更严重会导致奔溃。1.使用Executor框架对于线程和任务,任务是一组逻辑工作单元,而线程是使任务异步执行的机制,Executor框架可以将线程和任务协调起来。Executor基于生产者—消费者模型,它提供了一个标准的方法,将任务的提交和执行过程解耦,用Runnable来表示任务。提交任务的操作相当于生产者,执行任务的线程相当于消费者。Executor有两种实现方式:每线程每任务public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable command) { new Thread(command).start(); } } 一个线程所有任务:public class WithinThreadExecutor implements Executor { public void execute(Runnable command) { command.run(); } } 每当你看到new Thread(runnable).start();且你希望获得一种更灵活的执行策略时,考虑使用Executor代替Thread2.线程池线程池的优势:1. 通过重用现有的线程而不是创建新线程,可以减少创建和销毁线程的开销2. 当请求到来时,由于线程已经存在,可以减少等待时间,从而提高了响应性可以通过调用Executors中的静态工厂方法之一来创建一个线程池:newFixedThreadPool创建一个定长的线程池, 每当提交一个任务就创建一个线程, 直到达到池的最大长度, 这时线程池会保持长度不再变化. (一任务一线程)20170118125021499.pngnewCachedThreadPool 创建一个可缓存的线程池, 如果当前线程的长度超过了处理的需要时, 它可以灵活的回收空闲的线程, 当需求增加时, 它可以灵活的增加新的线程, 而并不会对池的长度做任何限制. (缓存线程池)20170118125038933.pngnewSingleThreadExecutor 创建一个单线程化的executor, 它只创建唯一的工作者线程来执行任务, 如果这个线程异常结束, 会有另一个取代它, 但是任务会保存在一个queue中等待执行. (多任务一线程)20170118125049125.pngnewScheduleThreadPool 创建一个定长的线程池, 而且支持定时的以及周期性执行任务, 类似timer.(定时线程池)20170118125057656.png可以看见上面的静态方法都几乎是通过ThreadPoolExecutor来实现的参数含义:corePoolSize:池中所保存的线程数,包括空闲线程maximumPoolSize:线程池允许的最大线程数量keepAliveTime:存活时间,当线程数大于核心线程数时,多出来的线程为空余线程,当空余线程在一定的时间内没有新任务到达执行,则终止该线程unit :keepAliveTime 参数的时间单位workQueue :执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务,使用不同的Queue,能够得到不同的功能,例如newFixThreadPool使用的是linkedBolckQueue,而newCachedThreadPool使用的是SynchronizedQueue举例:对于newFixedThreadPool:它实现了一个初始线程数为任务数(nThreads),最大线程数为任务数,等待时间为0(因为单线程单任务,不存在线程等待任务),并使用一个linkedBolckQueue来保存任务的线程池,这就是单任务单线程的实现对于newCachedThreadPool:它实现了一个初始线程数为0,最大线程数为无限大,等待时间为1分钟,并使用一个SynchronizedQueue来保存任务的线程池,所以它是一个无限线程的缓存池3.Executor的生命周期:ExecutorService从上面所述,我们可以看出Executor通常是创建线程来执行任务,为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了生命周期的管理方法public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; ....ExecutorService有三种状态: running(运行), shuting down(关闭), terminated(已终止)。shuting down(关闭)状态:shutdown:将停止接受新的任务, 同时等待已经提交的任务完成, 包括尚未完成的任务showdownNow:会启动一个强制的关闭过程, 尝试取消所有运行中的任务和排在队列中尚未开始的任务,并把排队中尚未开始的任务返回。对于关闭后提交到ExecutorService中的任务, 会被(拒接执行处理器)rejected execution handler处理,它会抛弃任务,或者使得execute方法抛出一个未检查的RejectedExecutionException。terminated(已终止)状态:等所有任务都完成之后,进入terminated状态, 可以调用awaitTermination等待ExecutorService到达终止状态, 也可以轮询检查isTerminated判断是否终止. 通常shutdown会紧随awaitTermination之后, 这样可以产生同步地关闭ExecutorService的效果.4.延迟任务和周期任务延迟任务:在100ms后执行任务周期任务:没100ms执行一次任务一把来说,Timer类用于执行延迟任务和周期任务,但是Timer有以下两个问题:1. 只会创建一个线程来执行所有task, 如果一个task非常耗时, 会导致其他的task的实效准确性出问题2. Timer线程并不捕获异常,对于一些未检查异常(RuntimeException)抛出,Timer线程会被终止public class Test { public static void main(String[] args) throws InterruptedException{ Timer timer = new Timer(); timer.schedule(new myTask(), 1);//这里抛出RuntimeException之后,整个Timer线程会被终止 Thread.sleep(1000); timer.schedule(new myTask(), 1); Thread.sleep(5000); } static class myTask extends TimerTask{ @Override public void run() { throw new RuntimeException(); } }}因此我们一般不使用Timer,可以使用DelayQueue来实现自己的调度服务, 它使BlockingQueue的一种实现,并为ScheduledThreadPoolExecutor提供了调度功能。其内部包含了一个Delayed对象的容器. Delayed是一种延迟时间对象, 只有元素过期后,它才会让你执行take获取元素(这个元素实现了Delayed接口)。5.Callable对比Runnablepublic class ThreadPerTaskExecutor implements Executor { public void execute(Runnable command) { new Thread(command).start(); } }Exceutor框架使用Runnable作为最基本的任务形式,但是Runnable有一种很大的局限性,它不能返回一个值或抛出一个受检查的异常。Callable是一种更好的任务形式,它能返回一个值或者抛出一个异常public interface Callable<V> { V call() throws Exception;}Executor执行任务时有4个生命周期阶段:创建,提交,开始,完成。对于已提交但尚未开始的任务,调用shutdownNow()可以取消并返回这些任务;对于已经开始的任务,只有当他们能响应中断时,才能取消。6.Callable和Future结合使用返回结果public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;get()方法的行为将取决于任务的状态,如果任务已经完成,那么get会立即返回结果或者抛出一个Exception,如果任务没有完成,那么get将阻塞并直到任务完成。将一个Runnable或者Callable任务传递给ExecutorService的submit方法,将返回一个Future用于获得任务的执行结果或者取消任务。private final ExecutorService executor = Executors.newCachedThreadPool(); void renderPage(String source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); //下载图片 Callable<List<ImageData>> task = new Callable<List<ImageData>>() { @Override public List<ImageData> call() throws Exception { List<ImageData> list = new ArrayList<ImageData>(); for (ImageInfo imageInfo : imageInfos) { list.add(imageInfo.download()); } } }; //处理图片 Future<List<ImageData>> future = executor.submit(task);//将一个Runnable或者Callable任务传递给ExecutorService的submit方法,将返回一个Future用于获得任务的执行结果或者取消任务。 renderText(source); try { List<ImageData> list = future.get();//get()方法会一直阻塞,直到callable的任务全部完成。 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 由于不需要结果,因此取消任务 future.cancel(true); } catch (ExecutionException e) { e.printStackTrace(); }上面程序还存在一个问题不是很好,就是下载图片和处理图片的速率的协调问题,如果处理图片的速度远远高于下载图片的速度,那跟串行执行的效率可能相差不是很大。7.CompletionService和BlockingQueueCompletionService用来将Executor与BlockingQueue进行结合, 将Callable任务提交给它执行, 然后使用类似队列中的take和poll在结果完整时获得这个结果。public class Test { private final ExecutorService executor = Executors.newCachedThreadPool(); void renderPage(String source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>( executor); for (final ImageInfo imageInfo : imageInfos) { service.submit(new Callable<ImageData>() { public ImageData call() throws Exception { return imageInfo.downloadImage(); } }); } renderText(source); for (int i = 0; i < imageInfos.size(); i++) { Future<ImageData> f; try { f = service.take(); ImageData imageData = f.get(); renderImage(imageData); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }}上面这个改进相对于前一个,在两个方面提高了性能:1. 为每一个图片的下载都创建一个独立任务,把串行下载改为了并行下载,减少了下载图像的总时间2. 通过CompletionService来即时获取每一个下载任务的Future,并通过该Future来获取结果进行处理,有更好的并发性一般来说,我们推荐下面这种用法。二、取消和关闭Java没有提供任何机制来安全地终止线程,但是它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。1.通过取消标志public class PrimeGenerator implements Runnable { private final List<BigInteger> primes = new ArrayList<BigInteger>(); private volatile boolean cancelled; @Override public void run() { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { primes.add(p); } } } public void cancel() { this.cancelled = true; } public synchronized List<BigInteger> get() { return new ArrayList<BigInteger>(primes); } }客户端执行取消 PrimeGenerator g = new PrimeGenerator(); new Thread(g).start(); try { Thread.sleep(10); } finally { g.cancel(); } 但是这里会有一个问题,这种标志,在生产者消费者模型中,当队列已被填满,生产者put时会被阻塞,如果此时消费者希望取消生产者任务,于是它调用cancel()来改变取消标志,但是生产者无法识别这个标志,因为它已经被阻塞了,它无法从put的阻塞状态恢复过来。2.通过中断机制Thread中的中断方法:public void interrupt();public boolean isInterrupted();public static boolean interrupted();interrupted():会清除当前线程的中断状态,除非你想屏蔽掉这个中断,否则你要对它处理:抛出InterruptedException或者再次调用interrupt恢复中断对中断的理解应该是:它并不会真正中断一个正在运行的线程; 它仅仅只是发送中断请求(这一点很重要)对于上面取消标志的生产者消费者模型,如果使用中断标志就能够解决为。在每一次迭代循环时,有两个位置可以检测出中断:阻塞的put中,循环开始处查询中断状态时,当检测到中断时,我们对它进行处理就可以实现任务的取消了。public class PrimeProducer extends Thread { private final BlockingQueue<BigInteger> queue; public PrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; } @Override public void run() { try { BigInteger p = BigInteger.ONE; while (!Thread.currentThread().isInterrupted()) { queue.put(p.nextProbablePrime()); } } catch (InterruptedException e) { } } public void cancel() { interrupt(); } }3.通过Future来实现取消ExecutorService.submit将返回一个Future来管理任务,Future拥有一个cancle方法boolean cancel(boolean mayInterruptIfRunning);如果mayInterruptIfRunning为true,并且任务当前正在某一个线程运行,那么这个线程能够被中断;如果为false,那么意味着“如果任务还没有启动,则不要运行它”当Future.get抛出InterruptedException或者TimeoutException时,如果你知道不再需要结果,那么就可以调用Future.cancle来取消任务。4.关闭服务如果一个方法需要处理一批任务, 并在所有任务结束前不返回, 那么它可以通过使用私有的Executor来简化服务的生命周期, 其中Executor的寿命限定在该方法中:boolean checkMail(Set<String> hosts, long timeout, TimeUnit unit) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); final AtomicBoolean hasNewMail = new AtomicBoolean(false); // 使用Atmic去掉volatile是因为内部Runnable访问hasNewMail标识, 那么必须是final的, 这样才能避免被修改 try { for (final String host : hosts) { exec.execute(new Runnable() { @Override public void run() { if (checkMail(host)) { hasNewMail.set(true); } } }); } } finally { exec.shutdown(); //关闭 exec.awaitTermination(timeout, unit); //等待结束 } return hasNewMail.get(); }Executor的shutdownNow方法强行关闭一个ExecutorService时, 它试图取消正在进行的任务, 并返回那些已经提交但是尚未开始的任务。然而,对于那些已经开始但尚未结束的任务,我们用常规的方法无法找出,但是可以通过一个容器来保存这些已开始但尚未结束的任务。三、线程池1.线程饥饿死锁在线程池中,如果任务依赖于其他任务,那么可能导致死锁。在单线程的Executor中,第二个任务停留在工作队列中,等待第一个任务的完成,之后到第二个任务开始执行,而第一个任务又需要第二个任务完成的结果,因此这样就导致死锁。只要线程池中的任务需要无限期地等待一些必须由池中其它任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,除非线程池足够大,否则将发生线程饥饿死锁。如果阻塞时间过长,即使不死锁,响应性也会很差。这种情况,我们要限定等待的时间,不要无限的等。大多数平台类库中的阻塞方法, 都同时有限时的和非限时的两个版本, 比如Thread.join, BlockingQueue.put, CountDownLatch.await, Selector.select. 如果出现超等待, 你可以把任务标识为失败, 中止它或者把它重新放回队列, 准备之后执行。2.ThreadPoolExecutor核心池大小, 最大池大小, 存活时间共同管理着线程的创建与销毁. 即使没有任务执行, 池的大小也等于核心池的大小, 并且直到工作队列充满前, 池都不会创建更多的线程. 最大池大小是可以同时活动的线程的上限, 如果一个线程已经闲置的时间超过了存活时间, 它将成为一个被收回的候选者, 如果当前的池的大小超过了核心池的大小, 线程会终止该候选者。workQueue管理这Executor的实现方式总结来说:各种不同类型的线程池,即使通过核心池大小、最大池大小、 存活时间和、实现队列来实现各自类型的功能newFixedThreadPool:工厂为请求的池设置了核心池的大小和最大池的大小, 而且它永远不会超时;newCachedThreadPool:工厂将最大池的大小设置为Integer.MAX_VALUE, 核心池的大小设置为0, 超时时间设置为1分钟, 这样创建出来的无限扩大的线程池会在需求量减少的情况下减少线程数量.ThreadPoolExecutor提供一个BlockingQueue来保存等待执行的任务。基本的排队方法有3种,无界队列,有界队列和同步移交(Synchronous)。newFixedThreadPool和newSingleThreadExecutor:默认使用的是一个无界的LinkedBlockingQueue, 如果所有的工作者线程都处于忙碌状态, 任务将会在队列中等候, 如果任务持续地到达, 超过了它被执行的速度, 队列会无限地增加.SynchronousQueue:当你需要一个庞大,且线程无限的线程池,可以使用SynchronousQueue,它完全绕开队列, 将任务直接从生产者移交到工作者线程, 因为SynchronousQueue并不是一个真正的队列, 而是一种管理直接在线程间移交信息的机制。只有当池的大小是无限的, 或者可以接受任务被拒绝, SynchronousQueue才是一个有实际价值的选择, newCachedThreadPool工厂就是用了SynchronousQueue.3.饱和策略当有界队列被填满后,饱和策略就游泳了。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。JDK提供了几种不同的实现,每一种实现有不同的饱和策略:AbortPolicy,CallerRunsPolicy,DiscardPolicy和DiscardOldestPolicy。1. 中止策略(AbortPolicy):默认的饱和策略,会抛出RejectedExecutionException: 调用者可以捕获这个隐藏然后编写满足自己需求的处理代码2. 抛弃策略(DiscardPolicy):当最新提交的任务不能进入队列等待执行时, 遗弃(discard)策略会默认放弃这个任务3. 遗弃最旧策略(DiscardOldestPolicy):选择丢弃的任务是本应该接下来就应该执行的任务, 该策略还会尝试去重新提交新任务。(该策略最好不要和优先级队列一起使用)4. 调用者运行策略(CallerRunsPolicy):既不会丢弃哪个任务, 也不会抛出任何异常. 它会把一些任务退回到调用者那里, 从此缓解新任务流. 他不会在池线程中执行最新提交的任务, 但是他会在一个调用了execute的线程中执行。当工作队列充满后, 并没有预置的饱和策略来阻塞execute当工作队列充满后, 并没有预置的饱和策略来阻塞execute. 但是, 使用Semaphore信号量可以实现这个效果public class BoundedExecutor { private final Executor exec; private final Semaphore semahpore; public BoundedExecutor(Executor exec, int bound) { super(); this.exec = exec; this.semahpore = new Semaphore(bound); } public void submitTask(final Runnable command) throws InterruptedException { semahpore.acquire(); //请求资源 try { exec.execute(new Runnable() { @Override public void run() { try { command.run(); } finally { semahpore.release(); } } }); } catch (RejectedExecutionException e) { semahpore.release(); } } } 4.定制ThreadPoolExecutor大多数通过构造函数传给ThreadPoolExecutor的参数(核心池大小, 最大池大小, 活动时间, 拒绝处理器) 都可以在创建之后通过setter进行设置。如果Executors是通过Executors中的某个工厂方法创建(newSingleThreadExecutor除外),那么可以将结果的类型转换为ThreadPoolExecutor以访问设置器(setter):ExecutorService executor = Executors.newCachedThreadPool();if(executor instanceof ThreadPoolExecutor){ ((ThreadPoolExecutor) executor).setCorePoolSize(10);}5.拓展ThreadPoolExecutorThreadPoolExecutor提供了几个可以在子类化中改写的方法(afterExecute, beforeExecute, terminated),这些方法可以用于拓展ThreadPoolExecutor的行为。在执行任务的线程将调用beforeExecute和afterExecute等方法无论任务是正常地从run返回, 还是抛出一个异常, afterExecute都会被调用, 如果抛出一个Error则不会如果任务抛出一个RuntimeException, 任务不会被执行, afterExecute也不会被调用terminated会在线程池完成关闭时调用, 也就是当所有任务已完成并且所有工作者线程也已经关闭后。terminated可以用来释放Executor在生命周期中分配的一些资源, 还可以发出通知, 记录日志或者完成统计信息public class TimingThreadPool extends ThreadPoolExecutor { private final ThreadLocal<Long> startTime = new ThreadLocal<Long>(); private final Logger log = Logger.getLogger("TimingThreadPool"); private final AtomicLong numTask = new AtomicLong(); private final AtomicLong totalTime = new AtomicLong(); public TimingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); log.fine(String.format("Thread %s: start %s", t, r)); startTime.set(System.nanoTime()); } @Override protected void afterExecute(Runnable r, Throwable t) { try { long endTime = System.nanoTime(); long taskTime = endTime - startTime.get(); totalTime.addAndGet(taskTime); numTask.incrementAndGet(); } finally { super.afterExecute(r, t); } } @Override protected void terminated() { try { log.info(String.format("Terminated: avg time=%dns", totalTime.get() / numTask.get())); }finally { super.terminated(); } } } 6.并行实现递归算法//树节点的串行递归public <T> void sequentialRecursive(List<Node<T>> nodes, Collection<T> results) { for (Node<T> node : nodes) { results.add(node.compute()); sequentialRecursive(node.getChildren(), results); } } //树节点的并行递归public <T> void parallelRecursive(final Executor exec, List<Node<T>> nodes, final Collection<T> results) { for (final Node<T> node : nodes) { exec.execute(new Runnable() { @Override public void run() { results.add(node.compute()); } }); parallelRecursive(exec, nodes, results); } } public <T> Collection<T> getParallelResult(List<Node<T>> nodes) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); Queue<T> resultQueue = new ConcurrentLinkedQueue<T>(); parallelRecursive(exec, nodes, resultQueue); exec.shutdown(); exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); return resultQueue; }原文链接:https://www.jianshu.com/p/3ee3f645d347
  • [技术干货] 【Lombok】val 和 var的使用 | 像 JavaScript 一样的去声明变量
    一、简介 1. val val是在lombok 0.10中引入的。 您可以使用val作为局部变量声明的类型,而不是实际编写该类型。执行此操作时,将从初始值设定项表达式推断类型。局部变量也将成为最终变量。此功能仅适用于局部变量和foreach循环,而不适用于字段。初始值设定项表达式是必需的。 val实际上是一种排序的“类型”,在lombok包中作为一个真正的类存在。必须导入它才能使val工作(或使用lombok.val作为类型)。局部变量声明中存在此类型会触发添加final关键字以及复制初始化表达式的类型,从而覆盖“伪”val类型。 警告:此功能目前在NetBeans中不起作用。 2. var var是在lombok 1.16.12中作为实验性功能引入的。 var的工作原理与val完全相同,只是局部变量没有标记为final。 类型仍然完全是从强制初始值设定项表达式派生的,任何进一步的赋值,尽管现在是合法的(因为变量不再是最终的),都不会被用来确定合适的类型。 例如,var x = "Hello"; x = Color.RED; 不起作用;x的类型将被推断为java.lang.String,因此,x = Color.RED的赋值将失败。如果x的类型被推断为java.lang.Object,这段代码就会被编译,但这不是var的工作方式。 3. 区别 var和val的区别在于:val修饰的局部变量没有被标记为final。 var在lombok 1.16.20中被提升为主包;考虑到JEP 286建立的期望,而lombok对var的处理遵循了这些期望,我们决定提升var,尽管这个特性仍然有争议。 Lombok 1.18.22中的新功能:val被替换为final var。 二、示例比较 1. Lombok 写法 import java.util.ArrayList; import java.util.HashMap; import lombok.val;  public class ValExample {   public String example() {     val example = new ArrayList<String>();     example.add("Hello, World!");     val foo = example.get(0);     return foo.toLowerCase();   }      public void example2() {     val map = new HashMap<Integer, String>();     map.put(0, "zero");     map.put(5, "five");     for (val entry : map.entrySet()) {       System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());     }   } } 2. Java 标准写法 import java.util.ArrayList; import java.util.HashMap; import java.util.Map;  public class ValExample {   public String example() {     final ArrayList<String> example = new ArrayList<String>();     example.add("Hello, World!");     final String foo = example.get(0);     return foo.toLowerCase();   }      public void example2() {     final HashMap<Integer, String> map = new HashMap<Integer, String>();     map.put(0, "zero");     map.put(5, "five");     for (final Map.Entry<Integer, String> entry : map.entrySet()) {       System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());     }   } }  三、支持的配置项 lombok.val.flagUsage = [warning | error] (默认: 未设置) 如果配置,Lombok会将val的任何使用标记为warning或error。  lombok.var.flagUsage = [warning | error] (默认: 未设置) 如果配置,Lombok会将var的任何使用标记为warning或error。  四、附属说明 对于复合类型,将推断最常见的超类,而不是任何共享接口。例如bool ? new HashSet() : new ArrayList()是一个具有复合类型的表达式。其结果既是AbstractCollection,也是Serializable。推断的类型将是AbstractCollection,因为它是一个类,而Serializable是一个接口。  在不明确的情况下,例如初始化表达式为 null时,会推断出java.lang.Object。 ———————————————— 原文链接:https://blog.csdn.net/qq_31635851/article/details/121786449 
  • [技术干货] 使用Java Cron表达式实现定时器
    使用Java Cron表达式实现定时器在Java应用程序中,经常需要使用定时器来执行周期性任务。Java提供了许多定时器库,但是Cron表达式是一种非常灵活和强大的方式来实现定时器。在本文中,我们将学习如何使用Java Cron表达式实现定时器。步骤1:添加依赖项首先,我们需要添加quartz依赖项。我们可以在pom.xml文件中添加以下依赖项:<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency> 步骤2:创建Job类接下来,我们将编写一个Job类来定义要执行的任务。以下是一个示例代码:import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 执行任务 }}在这里,我们实现了Job接口,并覆盖了execute()方法。在execute()方法中,我们可以编写要执行的任务的代码。步骤3:创建Scheduler现在,我们将创建一个Scheduler来调度Job。以下是一个示例代码:import org.quartz.*;import org.quartz.impl.StdSchedulerFactory;public class MyScheduler { public static void main(String[] args) throws SchedulerException { JobDetail job = JobBuilder.newJob(MyJob.class) .withIdentity("myJob", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")) .build(); SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); scheduler.scheduleJob(job, trigger); }}在这里,我们首先创建了一个JobDetail对象,并将其与MyJob类相关联。我们还为Job指定了一个标识符和一个组。然后,我们创建了一个Trigger对象,并使用Cron表达式来定义触发器的计划。最后,我们创建了一个Scheduler对象,并将Job和Trigger传递给它以安排任务。在这个例子中,我们使用Cron表达式“0 0/5 * * * ?”,它表示每5分钟执行一次任务。您可以使用不同的Cron表达式来定义不同的计划。结论到此为止,我们已经学会了如何使用Java Cron表达式实现定时器。使用Quartz库,我们可以创建高度灵活的定时器,以满足我们的应用程序的需求。
总条数:2294 到第
上滑加载中