• Java程序猿搬砖笔记(九)


    ZipOutputStream打包文件成一个zip,自定义写入文件夹和文件

    Java实现将文件(包括压缩包)、文件夹压缩成zip,并且压缩包里面保留文件夹结构
    参考链接参考链接参考链接

    Get请求且用?号形式传参,那么list参数必须加@RequestParam主键否则会报错

    参考链接

    Java7文件复制方法

    Java源码中有Files.copy(source, target, options)方法,options可以不传值。
    例如:D:/a.txt文件要复制到E:/xx/b.txt中,先要得到得到两文件的path。
    Ps:这里要求目标文件b.txt文件不存在(存在会报错),而且目标文件的路径必须要有,源代码方法中没有创建dir的方法。
    参考代码如下:

    //要确认拷贝的路径存在
    File destDir = new File("E:/xx");
    if(!(destDir.exists()&& destDir.isDirectory())) {
    	destDir.mkdirs();
    }
    File source = new File("D:/a.txt");
    File dest = new File("E:/xx/b.txt");
    try{
    	Files.copy(source.toPath(), dest.toPath());
    } catch (IOException e){
    	// TODO Auto-generated catch block
    	 e.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    linux查看进程占用端口的方法

    方法一:
    1、netstat -lntp
    找到端口对应的应用的ID
    2、ps -ef |grep 进程ID
    方法二:
    lsof -i:端口号
    输出的列表的pid就是进程号

    在Java的switch中使用枚举判断

    在Java的switch中使用枚举的code或者name会报错,必须使用枚举类。
    需要在枚举类中加入一个根据编码获取枚举类的方法。
    参考代码如下:

    /**
     * 审核状态枚举(预审页面)
     *
     * @author Chuenhung
     * @date 2021/10/12
     */
    public enum PreApprovalStatusEnum {
    
    	TO_APPROVAL("2", "待审核"),
    	REJECT("3", "已退回"),
    	AGREE("4", "审核通过");
    
    	private final String code;
    	private final String name;
    
    	PreApprovalStatusEnum(String code, String name) {
    		this.code = code;
    		this.name = name;
    	}
    
    	public String getCode() {
    		return code;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	/**
    	 * 根据编码获取枚举
    	 *
    	 * @param: code
    	 * @return PreApprovalStatusEnum
    	 * @author Chuenhung
    	 * @date 2022/6/13
    	 */
    	public static PreApprovalStatusEnum getEnumByCode(String code) {
    		if (StringUtil.isNotEmpty(code)) {
    			for (PreApprovalStatusEnum enu : PreApprovalStatusEnum.values()) {
    				if (code.equals(enu.getCode())) {
    					return enu;
    				}
    			}
    		}
    		return null;
    	}
    }
    
    // switch case示例代码
    switch (PreApprovalStatusEnum.getEnumByCode(status)){
    		case TO_APPROVAL:
    			break;
    		case REJECT:
    			break;
    		case AGREE:
    			break;
    		default:
    	}
    
    • 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
    • 56
    • 57
    • 58

    Ps: case后面的枚举不能带类名,否则会"An enum switch case label must be the unqualified name of an enumeration constant"错误
    参考链接

    MySQL创建的临时表会全表扫描,很影响性能

    Nginx配置学习

    location /portal {
    	proxy_pass http://10.12.7.182:7001;
    	proxy_set_header Host $http_host;
    	proxy_set_header X-Real-IP $remote_addr;
    	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    	proxy_set_header Cookie $http_cookie;
    	client_max_body_size 1000m;
    	fastcgi_buffers 8 256k;
    	fastcgi_buffer_size 64k;
    	gzip on;
    	gzip_min_length 50k;
    	gzip_comp_level 5;
    	gzip_vary on;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    该配置(portal、proxy_pass后面无/)举例:
    原始请求链接:http://10.12.7.182:5001/portal/carousel/getList
    实际请求链接:http://10.12.7.182:7001/portal/carousel/getList

    location /portal/ {
    	proxy_pass http://10.12.7.182:7001/;
    	proxy_set_header Host $http_host;
    	proxy_set_header X-Real-IP $remote_addr;
    	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    	proxy_set_header Cookie $http_cookie;
    	client_max_body_size 1000m;
    	fastcgi_buffers 8 256k;
    	fastcgi_buffer_size 64k;
    	gzip on;
    	gzip_min_length 50k;
    	gzip_comp_level 5;
    	gzip_vary on;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    该配置(portal、proxy_pass后面有/)举例:
    原始请求链接:http://10.12.7.182:5001/portal/carousel/getList
    实际请求链接:http://10.12.7.182:7001/carousel/getList

    Nginx配置返回文本或者JSON

    参考代码如下:

    location /proxy_reward/web/login/findemployeebyphone {
        default_type application/json;
    	add_header Content-Type 'text/html; charset=utf-8';
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Origin *;
        add_header Cache-Control public;
        add_header Cache-Control max-age=300;
        return 200 '{"status":"200","data":{"companyId":"1781","name":"张三","mobile":"12345678901","email":"123@163.com"}}';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    参考链接

    Nginx负载均衡配置

    参考代码如下:

    upstream test{
    	server 192.168.0.1;
    	server 192.168.0.2;
    }
    
    server{
    	listen 80;
    	server_name test.com;
    	charset utf-8;
    
    location /nacos {
    	proxy_pass http://test/;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    参考链接

    validation相关注解速查

    参考链接

    Git修改.gitignore文件修改后刷新

    • 刷新命令

      git rm -r --cached .
      git add .
      
      • 1
      • 2
    • 提交代码命令

      git commit -m "update .gitignore"
      git push origin
      
      • 1
      • 2

    ApplicationContext发布事件和处理事件示例代码

    要发布的事件:

    @Getter
    public class TransformDocEvent extends ApplicationEvent {
    
        public TransformDocEvent(Object source) {
            super(source);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    发布事件:

    applicationContext.publishEvent(new TransformDocEvent(doc));
    
    • 1

    处理事件:

    @Component
    @Slf4j
    public class TransformDocListener {
    
        @Async("MyThreadPool")
        @EventListener
        public void transform(TransformDocEvent event) {
           
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    FastStone Capture有两种滚动截屏方式

    1、按住CTRL键选取截取范围,再点击滚动条下面的向下箭头
    2、点击鼠标左键即可自动捕捉窗口全部页面(适用于页面禁用滚动条的时候)

    RestTemplate学习

    1、RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
    2、调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
    3、ClientHttpRequestFactory接口主要提供了两种实现方式

    • 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。
    • 另一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。

    RestTemplate的getForObject完成get请求、postForObject完成post请求、put对应的完成put请求、delete完成delete请求;还有execute可以执行任何请求的方法,需要你设置RequestMethod来指定当前请求类型。

    参考链接

    使用RestTemplate发送post请求

    示例代码:

    // RestTemplate也可以通过@Autowired注入到Spring容器。 
    RestTemplate restTemplate = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
    headers.setContentType(type);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());
    
    // 请求参数
    Map<String, Object> map = new HashMap<>();
    map.put("typeId",9);
    map.put("dontQueryEmpty",true);
    JSONObject jsonObj = new JSONObject(map);
    HttpEntity<String> formEntity = new HttpEntity<String>(jsonObj.toString(), headers);
    
    String url = "http://10.12.7.124:18081/manage/awards/querysecondclasslist";
    String result = restTemplate.postForObject(url, formEntity, String.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果直接使用在postForObject中把对象传入很容易出现no suitable HttpMessageConverter found for request type的错误,建议直接先转成字符串,见jsonObj.otString()。
    使用RestTemplate调用接口确实非常优雅,几行代码就解决了。

    Swagger常用注解

    参考链接

    使用IDEA合并远程其他分支到本地

    • 鼠标放到项目目录上右击
    • 点击Git
    • 点击Repository
    • 点击pull
    • 在Branchs to merge中选择远程分支

    建议合并前把本地分支的代码先提交到远程仓库,方便解决冲突

    @Transactional与@Async同时使用事务会失效

    @Transactional与@Async同时使用事务会失效,在Transactional注解中的多线程也会导致事物失效
    参考链接

    事务失效问题解决:
    方法一:@Async与@Transactional不使用在同一个方法上
    方法二:手动提交事务及回滚事务
    参考链接

    方法三:将需要事务的方法放到另一个类中。

    @Component
    public class A {
    
        @Resource
        private B b;
    
        @Async
        public void test(){
            b.dd();
        }
    }
    @Component
    public class B {
    
        @Transactional
        public void dd(){
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    参考链接

    经过测试,直接在service实现类的方法同时加两个注解,事务和多线程都不会失效。

    achievementRepetitionService.checkAchievementRepetition();
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async("RepetitionThreadPool")
    public void checkAchievementRepetition() {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    经过测试,service实现类的非异步方法调用异步方法,多线程失效,事务不会失效。

    @Override
    public void checkAchievementRepetition() {
    	insert();
    }
    @Async("RepetitionThreadPool")
    public void insert(){
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    几种常见限流算法(方式)

    限流算法

    • 固定窗口算法
      固定窗口算法又叫计数器算法,是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。
    • 滑动窗口算法
      固定窗口算法假如 QPS为2,但是当遇到时间窗口的临界突变时,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 4 次。
      滑动窗口算法是对固定窗口算法的改进,可以每 500ms 滑动一次窗口,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。
    • 滑动日志算法
      滑动日志算法是实现限流的另一种方法,这种方法比较简单。基本逻辑就是记录下所有的请求时间点,新请求到来时先判断最近指定时间范围内的请求数量是否超过指定阈值,由此来确定是否达到限流,这种方式没有了时间窗口突变的问题,限流比较准确,但是因为要记录下每次请求的时间点,所以占用的内存较多。
    • 漏桶算法
      漏桶算法中的漏桶是一个形象的比喻,这里可以用生产者消费者模式进行说明,请求是一个生产者,每一个请求都如一滴水,请求到来后放到一个队列(漏桶)中,而桶底有一个孔,不断的漏出水滴,就如消费者不断的在消费队列中的内容,消费的速率(漏出的速度)等于限流阈值。即假如 QPS 为 2,则每 1s / 2= 500ms 消费一次。漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。
    • 令牌桶算法
      令牌桶算法同样是实现限流是一种常见的思路,最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系。
      系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加。
      请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。
    • Redis分布式限流
      Redis 是一个开源的内存数据库,可以用来作为数据库、缓存、消息中间件等。Redis 是单线程的,IO多路复用,又在内存中操作,所以速度极快,得益于 Redis 的各种特性,所以使用 Redis 实现一个限流工具是十分方便的。

    总结

    • 窗口算法实现简单,逻辑清晰,可以很直观的得到当前的 QPS 情况,但是会有时间窗口的临界突变问题,而且不像桶一样有队列可以缓冲。
    • 桶算法虽然稍微复杂,不好统计 QPS 情况,但是桶算法也有优势所在。
      漏桶模式消费速率恒定,可以很好的保护自身系统,可以对流量进行整形,但是面对突发流量不能快速响应。
      令牌桶模式可以面对突发流量,但是启动时会有缓慢加速的过程,不过常见的开源工具中已经对此优化。

    参考链接

    SpringBoot使用Redis教程(共五步)

    参考链接

    用RestTemplate调用对方接口一直报400 Bad Request,但是用PostMan没问题

    MediaType type = MediaType.parseMediaType(“application/json; charset=UTF-8”);
    把charset=utf-8去掉就可以了,对方接口解析请求头有问题

    EasyExcel大数据导入导出

    思路:分批次查询(分页查询)+循环写入Excel,及时清理查询的list,防止OOM
    1、查询出此次要导出的数据量count,根据count和自定义的每次查询数量size(我是10000)来计算出一共要查询n次。(防止一次查询数据量过大导致OOM)
    2、循环n次,每次查询size大小的数据。然后导出到同一个sheet的excel中。注意,每次循环完clear掉list数据,防止占用内存
    3、循环中会有一个问题,就是你在循环过程中别人新插入了数据怎么办,我的做法是最开始查询count的时候根据创建时间排序,获取最新的创建时间,然后后面的循环都使用这个创建时间作为搜索条件。这样就只会查询到和count相符合的数据了。
    注意:导出数据超过6w条的时候excel格式要用xlsx。
    参考链接参考链接(含代码)参考链接

    EasyExcel大数据导入

    思路:分批次写入数据库,及时清理读取的list,防止OOM
    参考链接(含代码)

    Windows查看CPU核数和线程数

    方法一:
    第一步:开始菜单->运行->cmd->输入 wmic
    第二步(方法一):
    分别输入下面的命令:

    wmic:root\cli>cpu get NumberOfCores
    NumberOfCores
    6
    wmic:root\cli>cpu get NumberOfLogicalProcessors
    NumberOfLogicalProcessors
    12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第二步(方法二)

    • 输入 cpu get *
    • 底部滑动条往右拖
    • NumberOfCores为核数 NumberOfLogicalProcessors为逻辑cpu数(线程数)

    方法二:
    在任务管理器中的性能页面中查看,如下图所示:
    在这里插入图片描述
    方法三:
    运行dxdiag命令

    Linux查看物理CPU个数、CPU核心数、逻辑CPU的个数(线程数)、型号、内核信息

    1、物理CPU数
    实际机器中插槽上的CPU个数,查询命令如下:

    [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l
    16
    
    • 1
    • 2

    2、CPU核心数
    一块物理CPU上能处理数据的芯片组数量。也就是说一个物理CPU上可能会有多个核心,日常中说的双核、四核就是指的CPU核心。查询命令如下:

    [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "cores"|uniq
    cpu cores: 1
    
    • 1
    • 2

    3、逻辑CPU的个数(线程数)
    逻辑CPU数量=物理cpu数量 x CPU核心数 x 2(如果支持并开启ht)。
    查询命令如下所示:

    [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "processor"|wc -l
    16
    
    • 1
    • 2

    从结果可以看出,我的机器不支持(或不开启)ht。
    一般情况,我们认为一颗cpu可以有多核,加上intel的超线程技术(HT),可以在逻辑上再分一倍数量的cpu core出来;所以逻辑CPU的值理论上是可以超过100%的。


    4、CPU数量及型号

    [award_dev@localhost ~]$ cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
         16  Intel(R) Xeon(R) Gold 5118 CPU @ 2.30GHz
    
    • 1
    • 2


    5、查看当前操作系统内核信息

    [award_dev@localhost ~]$ uname -a
    Linux localhost.localdomain 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
    
    • 1
    • 2

    Ps:Linux下top查看的CPU也是逻辑CPU个数

    参考链接参考链接

    超线程

    一个CPU核可以执行一个线程,由英特尔开发超线程技术可以把一个线程模拟出两个线程来使用,使得单个核心用起来像两个核心一样,以充分发挥CPU的性能。
    通常:逻辑CPU数量=物理cpu数量 x CPU核心数,若不相等则表示CPU支持超线程技术

  • 相关阅读:
    umi项目本地开发环境远程打开的问题
    Linux服务器格式化磁盘,分区,挂载
    nodejs篇 内置模块events 常用api
    ctfshow-nodejs
    Electron之集成vue+vite开发桌面程序
    数据安全服务是什么意思?
    第25集丨人生中最高的精神价值
    角色妆容的实现
    Go 反射
    Fastdfs图片上传 配置Fastdfs在linux------基于SpringBoot
  • 原文地址:https://blog.csdn.net/a1275302036/article/details/126676663