【项目实践02】【优先级阻塞队列】

文章目录

  • 一、前言
  • 二、项目背景
  • 三、实现方案
  • 四、思路延伸
    • 1. 优先级队列
      • 1.1 concurrent 包下的 PriorityBlockingQueue
      • 1.2 Redisson 的优先级阻塞队列
    • 2. jvisualvm 远程连接
    • 3. Jstack 高 CPU 排查
  • 五、参考内容


一、前言

本系列用来记录一些在实际项目中的小东西,并记录在过程中想到一些小东西,因为是随笔记录,所以内容不会过于详细。


二、项目背景

项目存在一个功能:对PDF文件进行压缩,且要求PDF 每页大小小于400KB。由于无法判断PDF 每页的大小,所以项目实现方案是将PDF 每页读取后转成图片再进行压缩到合适大小,最后将压缩后的图片再重新生成为 PDF。

在一通实现(东抄西抄 )后,上述功能实现后便直接上线,但是上线后暴露出如下问题:

  • 对于多页数 PDF 的压缩效率太低:由于无法判定PDF每页是否满足大小,所以只能将PDF每页都进行 转图片、压缩、转PDF的操作。对于客户动辄50+页数的PDF,处理效率太低。并且由于存在压缩超时的判定限制,大页数PDF极有可能被判定为压缩超时。

为了解决上述问题,准备开启多线程以页为维度进行压缩,提高多页数PDF的解析效率。但经过测试,上述的PDF处理过程极其耗费资源,本地在测试时直接OOM,因此也要控制并发量。


三、实现方案

基础实现依托于下面的工具类,调用 ImgToPdfUtils#compressPdf 方法可完成压缩功能。

package com.kingfish.springcommondemo.docs;import com.google.common.collect.Lists;
import com.itextpdf.text.Document;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.*;
import com.kingfish.common.api.CommonBizException;
import com.kingfish.common.utils.ThreadUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;/*** @Author : kingfish* @Email : kingfishx@163.com* @Date : 2024/1/28 15:16* @Desc :*/
@Slf4j
public class PdfCompressUtils {/*** 每页最大*/private static final int SINGLE_PDF_MAX_SIZE = 350 * 1000;/*** PDF 压缩** @param pdfBytes* @return*/public static byte[] syncCompressPdf(byte[] pdfBytes) throws Exception {final List<byte[]> imageBytesList = pdf2Images(pdfBytes);ByteArrayOutputStream bos = new ByteArrayOutputStream();Document doc = new Document();PdfCopy pdfCopy = new PdfCopy(doc, bos);pdfCopy.setFullCompression();pdfCopy.setCompressionLevel(PdfStream.BEST_COMPRESSION);doc.open();imageBytesList.forEach(imageBytes ->copyFileToPdf(pdfCopy, compressImage2Pdf(imageBytes, SINGLE_PDF_MAX_SIZE)));pdfCopy.close();doc.close();return bos.toByteArray();}/*** PDF 压缩** @param pdfBytes* @return*/public static byte[] asyncCompressPdf(byte[] pdfBytes) throws Exception {final List<byte[]> imageBytesList = pdf2Images(pdfBytes);ByteArrayOutputStream bos = new ByteArrayOutputStream();Document doc = new Document();PdfCopy pdfCopy = new PdfCopy(doc, bos);pdfCopy.setFullCompression();pdfCopy.setCompressionLevel(PdfStream.BEST_COMPRESSION);doc.open();final List<CompletableFuture<byte[]>> completableFutures =imageBytesList.stream().map(imageBytes ->CompletableFuture.supplyAsync(() ->compressImage2Pdf(imageBytes, SINGLE_PDF_MAX_SIZE),ThreadUtil.getIoPool())).collect(Collectors.toList());CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();completableFutures.stream().filter(Objects::nonNull).map(CompletableFuture::join).forEach(pageBytes -> copyFileToPdf(pdfCopy, pdfBytes));pdfCopy.close();doc.close();return bos.toByteArray();}/*** pdf 转图片** @param pdfBytes* @return*/private static List<byte[]> pdf2Images(byte[] pdfBytes) {List<byte[]> results = Lists.newArrayList();try (PDDocument document = PDDocument.load(pdfBytes)) {PDFRenderer renderer = new PDFRenderer(document);for (int i = 0; i < document.getNumberOfPages(); ++i) {// DPI 越大,清晰度越高BufferedImage bufferedImage = renderer.renderImageWithDPI(i, 300);ByteArrayOutputStream out = new ByteArrayOutputStream();ImageIO.write(bufferedImage, "jpeg", out);results.add(out.toByteArray());}} catch (IOException e) {log.error("pdf转图片出错", e);}return results;}/*** 生成PDF** @param pdfCopy* @param pdfBytes* @throws IOException* @throws BadPdfFormatException*/@SneakyThrowspublic static void copyFileToPdf(PdfCopy pdfCopy, byte[] pdfBytes) {PdfReader reader = new PdfReader(pdfBytes);int totalPages = reader.getNumberOfPages();for (int j = 1; j <= totalPages; j++) {pdfCopy.addPage(pdfCopy.getImportedPage(reader, j));}reader.close();}/*** 图片压缩** @param imageBytes* @param maxSize* @return*/public static byte[] compressImage2Pdf(byte[] imageBytes, int maxSize) {try (ByteArrayOutputStream resultBos = new ByteArrayOutputStream()) {Document document;if (imageBytes.length > maxSize) {// 递归压缩imageBytes = compressImageCycle(imageBytes, maxSize, 0);}// 绘制图片转为 PDFImage image = Image.getInstance(imageBytes);image.setCompressionLevel(PdfStream.BEST_COMPRESSION);float scaledWidth = image.getScaledWidth();float scaledHeight = image.getScaledHeight();if (scaledWidth > scaledHeight) {image.scaleToFit(842.0F, 575.0F);document = new Document(PageSize.A4.rotate(), 0, 0, 0, 0);} else {image.scaleToFit(575.0F, 842.0F);document = new Document(PageSize.A4, 0, 0, 0, 0);}PdfWriter writer = PdfWriter.getInstance(document, resultBos);writer.setCompressionLevel(PdfStream.BEST_COMPRESSION);writer.setFullCompression();document.open();document.add(image);document.close();return resultBos.toByteArray();} catch (Exception exception) {throw new CommonBizException(exception);}}/*** @param bytes 原图片字节数组* @return*/private static byte[] compressImageCycle(byte[] bytes, int maxSize, int cycle) throws IOException {double accuracy = getAccuracy(bytes.length / 1000);//计算宽高BufferedImage bim = ImageIO.read(new ByteArrayInputStream(bytes));int imgWidth = bim.getWidth();int imgHeight = bim.getHeight();int desWidth = new BigDecimal(imgWidth).multiply(new BigDecimal(accuracy)).intValue();int desHeight = new BigDecimal(imgHeight).multiply(new BigDecimal(accuracy)).intValue();// 构造一个类型为预定义图像类型之一的 BufferedImageBufferedImage tag = new BufferedImage(desWidth, desHeight, BufferedImage.TYPE_INT_RGB);// 这边是压缩的模式设置tag.getGraphics().drawImage(bim.getScaledInstance(desWidth, desHeight, java.awt.Image.SCALE_SMOOTH), 0, 0,null);//将图片按JPEG压缩,保存到out中ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(tag, "jpeg", baos);cycle++;int srcSize = baos.size();if (srcSize > maxSize && cycle < 6) {
//            log.info(srcSize / 1000 + "KB文件大于" + maxSize / 1000 + "KB,第" + (cycle + 1) + "次进行压缩");return compressImageCycle(baos.toByteArray(), maxSize, cycle);}return baos.toByteArray();}/*** 自动调节精度** @param size 源图片大小* @return 图片压缩质量比*/private static double getAccuracy(long size) {double accuracy;if (size < 900) {accuracy = 0.85;} else if (size < 2047) {accuracy = 0.6;} else if (size < 3275) {accuracy = 0.44;} else {accuracy = 0.4;}return accuracy;}
}

使用20个PDF 文件 模拟测试调用,方法如下:

public class PdfDemoMain {static {LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();List<Logger> loggerList = loggerContext.getLoggerList();loggerList.forEach(logger -> {logger.setLevel(Level.INFO);});}public static void main(String[] args) throws Exception {sync();async();}/*** 同步调用*/private static void sync() {final File[] files = new File("C:\\Users\\Administrator\\Desktop\\compress\\压缩前").listFiles();FileUtil.del("C:\\Users\\Administrator\\Desktop\\compress\\压缩后\\");StopWatch stopWatch = new StopWatch();stopWatch.start();// 模拟并发调用final CompletableFuture[] cfs = Arrays.stream(files).map(file ->CompletableFuture.runAsync(() -> {try {// 同步压缩byte[] compressBytes = PdfCompressUtils.syncCompressPdf(FileUtil.readBytes(file));FileUtil.writeBytes(compressBytes, "C:\\Users\\Administrator\\Desktop\\compress\\压缩后\\" + file.getName() + ".pdf");} catch (Exception e) {throw new RuntimeException(e);}})).toArray(CompletableFuture[]::new);CompletableFuture.allOf(cfs).join();stopWatch.stop();System.out.println("同步花费时长: " + stopWatch.getTotalTimeSeconds());}/*** 异步调用*/private static void async() {final File[] files = new File("C:\\Users\\Administrator\\Desktop\\compress\\压缩前").listFiles();FileUtil.del("C:\\Users\\Administrator\\Desktop\\compress\\压缩后\\");StopWatch stopWatch = new StopWatch();stopWatch.start();// 模拟并发调用final CompletableFuture[] cfs = Arrays.stream(files).map(file ->CompletableFuture.runAsync(() -> {try {// 异步压缩byte[] compressBytes = PdfCompressUtils.asyncCompressPdf(FileUtil.readBytes(file));FileUtil.writeBytes(compressBytes, "C:\\Users\\Administrator\\Desktop\\compress\\压缩后\\" + file.getName() + ".pdf");} catch (Exception e) {throw new RuntimeException(e);}})).toArray(CompletableFuture[]::new);CompletableFuture.allOf(cfs).join();stopWatch.stop();System.out.println("异步花费时长: " + stopWatch.getTotalTimeSeconds());}
}

测试结果如下:
在这里插入图片描述

可以看到效率具有非常明显的提升,但是需要注意的是:

  1. PdfCompressUtils 的异步方法并非是所有情况都适用。当观察机器的CPU,如果CPU本身已经接近满载,再使用异步方法可能并不会提升效率。
  2. PdfCompressUtils 压缩方法的目的是将PDF 每页大小压缩尽量接近400Kb,因此压缩后的PDF大小可能会变更大,因为原先一页可能只有20KB,压缩后可能变成了380KB。
  3. PdfCompressUtils 压缩过程中对于PDF 可能会存在多次压缩,因为无法把握合适的压缩系数。比如500KBPDF 压缩一次后可能是420KB,则需要再次压缩。

四、思路延伸

PdfCompressUtils 中提供的压缩功能个人感觉并非最优解,效率低且占用资源非常高,在本地测试的时候因为过高的并发度导致 OOM 的发生,因此还需要控制 PdfCompressUtils 方法请求的并发度,否则可能会导致OOM的发送。而并发度的控制有两个方面:

  1. 异步压缩开启的线程数量:这个可以直接通过 ThreadUtil.getIoPool() 的线程池来控制。
  2. 服务接口被请求的并发控制:这个首先想到的就是队列,对于有优先级要求的情况下,单节点的情况下可以使用java.util.concurrent.PriorityBlockingQueue,而多节点则可以使用 Redisson 提供的 PriorityBlockingQueue。

1. 优先级队列

1.1 concurrent 包下的 PriorityBlockingQueue

下面以 java.util.concurrent.PriorityBlockingQueue 为例,在 PdfCompressUtils 中增加如下内容:


@Slf4j
public class PdfCompressUtils {.../*** 文件队列*/private static final PriorityBlockingQueue<PriorityFile> FILE_QUEUES = Queues.newPriorityBlockingQueue();static {// 可以通过线程池的线程数量来控制队列消费的并发度,这里使用单线程是为了方便测试final ExecutorService executorService =Executors.newSingleThreadExecutor();executorService.submit(PdfCompressUtils::runCompressTask);}private static void runCompressTask() {while (true) {try {// 睡眠10s 也是为了方便测试,让所有PDF都入队后再出队Thread.sleep(10000);// 从队列中取出final PriorityFile priorityFile = FILE_QUEUES.take();log.info("文件 {} 优先级为 {} 从队列中取出", priorityFile.getFile().getName(), priorityFile.getPriority());// 压缩final byte[] results = asyncCompressPdf(FileUtil.readBytes(priorityFile.getFile()));// 压缩完成回调priorityFile.getCallback().accept(results);} catch (Exception e) {// TODO : 文件压缩失败之后的处理}}}/*** PDF 压缩* @param file 要压缩的PDF* @param priority 优先级,越大优先级越高* @param callback 压缩回调,因为附件的优先级可能比较低导致一直没有压缩,因此使用回调的方式,当附件压缩完成时调用 callback 方法* @throws Exception*/public static void asyncCompressPdf(File file, int priority, Consumer<byte[]> callback) throws Exception {log.info("文件 {} 优先级为 {} 投递到队列", file.getName(), priority);FILE_QUEUES.offer(new PriorityFile(file, priority, callback));}/*** 优先级文件*/@Getterstatic class PriorityFile implements Comparable<PriorityFile> {/*** 文件*/private File file;/*** 优先级*/private int priority;/*** 结果回调*/private Consumer<byte[]> callback;public PriorityFile(File file, int priority, Consumer<byte[]> callback) {this.file = file;this.priority = priority;this.callback = callback;}@Overridepublic int compareTo(PriorityFile o) {return o.getPriority() - this.priority;}}...}

可以看到压缩结果是按照优先级的顺序压缩的
在这里插入图片描述


总结:

  1. 通过静态代码块中的线程池来控制压缩的并发度,防止OOM
  2. 通过FILE_QUEUES 来控制压缩的优先级。
  3. 这里其实存在一个问题,当PDF入队后,服务宕机重启,队列中就没有该PDF记录了,解决方式可以是在当PDF入队后将PDF记录到Redis 或数据库中,当压缩成功后再移除,每次服务启动时加载Redis或数据库中的PDF压缩记录即可。

1.2 Redisson 的优先级阻塞队列

如果要使用 Redisson 的 优先级阻塞队列,则进行如下改造:


@Slf4j
public class PdfCompressUtils {...private static RPriorityBlockingQueue<PriorityFile> FILE_QUEUES = null;private static RedissonClient REDISSON_CLIENT = null;static {Config config = new Config();config.useSingleServer().setTimeout(1000000).setDatabase(0).setAddress("redis://127.0.0.1:6379");REDISSON_CLIENT = Redisson.create(config);FILE_QUEUES = REDISSON_CLIENT.getPriorityBlockingQueue("FILE_QUEUE");// 可以通过线程池的线程数量来控制队列消费的并发度,这里使用单线程是为了方便测试final ExecutorService executorService =Executors.newSingleThreadExecutor();executorService.submit(PdfCompressUtils::runCompressTask);}private static void runCompressTask() {while (true) {try {// 睡眠10s 也是为了方便测试,让所有PDF都入队后再出队Thread.sleep(10000);final PriorityFile priorityFile = FILE_QUEUES.take();final File file = priorityFile.getFile();log.info("文件 {} 优先级为 {} 从队列中取出", file.getName(), priorityFile.getPriority());final byte[] results = asyncCompressPdf(FileUtil.readBytes(file));// 因为队列内容需要序列化到Redis中,所以无法使用回调函数,因此结果处理直接在这里处理FileUtil.writeBytes(results, "C:\\Users\\Administrator\\Desktop\\compress\\压缩后\\" + file.getName() + ".pdf");log.info("文件 {} 压缩完成", file.getName());} catch (Exception e) {// TODO : 文件压缩失败之后的处理log.info("文件压缩失败", e);}}}/*** PDF 压缩** @param file* @param priority* @throws Exception*/public static void asyncCompressPdf(File file, int priority) throws Exception {log.info("文件 {} 优先级为 {} 投递到队列", file.getName(), priority);FILE_QUEUES.offer(new PriorityFile(file, priority));}/*** 优先级文件*/@Getterstatic class PriorityFile implements Comparable<PriorityFile>, Serializable {/*** 文件*/private File file;/*** 优先级*/private int priority;public PriorityFile() {}public PriorityFile(File file, int priority) {this.file = file;this.priority = priority;}@Overridepublic int compareTo(PriorityFile o) {return o.getPriority() - this.priority;}}...}

总结:

  1. 如果真的要使用,Redis 中建议保存的是文件上传到 OSS的文件地址或者数据库中Id,而不是直接保存 File 或 byte[]

  2. Redisson 的优先级阻塞队列使用的并不是 sorted set 做数据结构, 而是使用 list 结构。这一点可以在元素入队时看到,如下:

    @Overridepublic boolean offer(V e) {return add(e);}@Overridepublic boolean add(V value) {lock.lock();try {checkComparator();// 二分查找,根据优先级确定当前元素入队的位置BinarySearchResult<V> res = binarySearch(value, codec);int index = 0;if (res.getIndex() < 0) {index = -(res.getIndex() + 1);} else {index = res.getIndex() + 1;}// lua 语句保证并发性在队列指定位置插入元素commandExecutor.evalWrite(getName(), RedisCommands.EVAL_VOID, "local len = redis.call('llen', KEYS[1]);"+ "if tonumber(ARGV[1]) < len then "+ "local pivot = redis.call('lindex', KEYS[1], ARGV[1]);"+ "redis.call('linsert', KEYS[1], 'before', pivot, ARGV[2]);"+ "return;"+ "end;"+ "redis.call('rpush', KEYS[1], ARGV[2]);", Arrays.<Object>asList(getName()), index, encode(value));return true;} finally {lock.unlock();}}

2. jvisualvm 远程连接

jvisualvm 是 JDK 提供的监控 Java 程序的工具,当使用如下命令进行启动服务时,可以使用 jvisualvm 远程连接服务。(这块内容如有需要可详参
https://blog.csdn.net/zhou920786312/article/details/123572662)

java - jar -Djava.rmi.server.hostname=[serverIp] -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=[serverPort]  -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false demo.jar

这里注意两个变量:

  • serverIp 指的是当前服务部署的机器的ip地址
  • serverPort 指的是当前服务部署的机器暴露给外部的端口,jvisualvm 将通过此端口来远程连接。所以服务器需要开放serverPort 端口,否则也是无法连接。

以下为举例:

  1. 使用该命令启动服务, 其中 192.168.72.128 为服务器本地地址, 10081 为服务器暴露的端口

    [root@localhost app]# java -jar -Djava.rmi.server.hostname=192.168.72.128 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=10081  -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false spring-simple-demo-1.0-SNAPSHOT.jar 
    
  2. jvisualvm 建立连接 : 如下,直接建立连接,名称可以随便写

    在这里插入图片描述

    建立后再新增 JMX 连接,如下,连接内容填写服务器地址和暴露的端口。
    在这里插入图片描述

    连接后的效果如下:

    在这里插入图片描述

3. Jstack 高 CPU 排查

在使用上述 PDF 压缩时,会出现 CPU 使用率过高的情况,借此再总结下通过 jstack 命令排查 CPU使用率过高的情况,如下(这块内容如有需要详参 https://blog.csdn.net/weixin_44588186/article/details/124680586):

  1. top :通过 top 命令确定 服务器上 CPU 占用较高的进程是哪个, top 命令默认按照CPU 排序,可以铜鼓 top c 可以更清晰的看到进程信息,如下:
    在这里插入图片描述

  2. top -Hp pid :确定好哪个进程 CPU 占用高后,可以通过 top -Hp pid 命令查看指定进程的每个线程的 CPU 占用情况。需要注意的是 top -Hp pid 中的 pid 指的是第一步中确定的进程的pid,而 命令输出中的 pid 则是指的进程中的 线程id。

    如下图中 top -Hp 2150 , 这里的 2150 指的是进程id,而输出中CPU 占用最高的 PID 为 2183,这个PID 为 线程id。

    在这里插入图片描述

  3. jstack pid : 通过 jstack pid 可以查看线程的具体信息( 可以通过 jstack pid >/tmp/log.txt 命令将内容输出到文件)。但我在本地测试的时候输出如下信息,无法输出正常数据,但所幸服务本身日志输出了对应内容 (因此次问题这里不做深究)。
    在这里插入图片描述

    jstack 命令正常输出线程信息如下:
    在这里插入图片描述

  4. 分析堆栈信息 :将 top -Hp pid 记录下来的pid 转为十六进制,去 jstack日志文件中找,可以找到对应线程的代码,从而修改代码。
    如 上面通过 top -Hp 2150 命令确定 pid 为 2183 的线程CPU占用较高,所以将 2183 转为十六进制为887,在 jstack 的日志中搜索 887 ,便可以根据搜索结果可以确定问题代码
    在这里插入图片描述

五、参考内容

https://blog.csdn.net/zhou920786312/article/details/123572662
https://blog.csdn.net/weixin_44588186/article/details/124680586

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/661006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qemu 抓取linux kernel vmcore

一、背景 在qemu调试linux kernel时 有时我们会遇到dump 情况&#xff0c;这时可以通过gdb 方式连接分析dump&#xff0c; 但实际中我们用得更多的是离线dump 分析&#xff0c;分析的文件通常是vmcore&#xff08;linux kernel panic 生成的coredump文件&#xff09;或者ramdu…

【多个SpringBoot模块项目如何变成聚合项目】

【前言】 项目虽然是Eureka、OpenFeign 进行服务注册和服务调用&#xff0c;但是每个模块都是一个单独的SpringBoot&#xff0c;启动每个模块都需要单独启动一个idea,觉得这个过于繁琐&#xff0c;现在想把项目变成一个聚合项目&#xff0c;只需要启动一个idea即可。 【过程】…

【数据结构 08】红黑树

一、概述 红黑树&#xff0c;是一种二叉搜索树&#xff0c;每一个节点上有一个存储位表示节点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长上两倍&#xff0c;因而是接进…

UGUI中Text和TextMeshPro实现图文混排方式

一些项目中实现图文混排是自定义一个脚本去继承Text类&#xff0c;然后文本中用富文本的方式进行图片和超链接的定义&#xff0c;在代码中用正则表达式匹配的方式把文本中图片和超链接给替换&#xff0c;如下&#xff1a; TextMeshPro实现是生成SpriteAsset进行图文混排的&…

YOLOv8-Segment C++

YOLOv8-Segment C https://github.com/triple-Mu/YOLOv8-TensorRT 这张图像是运行yolov8-seg程序得到的结果图&#xff0c;首先是检测到了person、bus及skateboard(这个是检测错误&#xff0c;将鞋及其影子检测成了滑板&#xff0c;偶尔存在错误也属正常)&#xff0c;然后用方…

go并发编程-runtime、Channel与Goroutine

1. runtime包 1.1.1. runtime.Gosched() 让出CPU时间片&#xff0c;重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤&#xff0c;但是你妈让你去相亲,两种情况第一就是你相亲速度非常快&#xff0c;见面就黄不耽误你继续烧烤&#xff0c;第二种情况就是你相亲速度…

电脑用的视频编辑软件有哪些 视频剪辑软件排行榜 视频剪辑软件推荐 视频剪辑培训学习 视频剪辑制作自学 电脑视频剪辑需要什么配置

电脑视频剪辑软件这么多&#xff0c;到底哪些比较好用&#xff1f;下面就让我们以十大电脑视频剪辑软件排行榜来细数好用的软件。另外&#xff0c;电脑视频剪辑需要什么配置&#xff1f;本文也会给大家从内存、CPU等参数上介绍&#xff0c;并推荐好用的电脑设备。 一、十大电脑…

Javaweb之SpringBootWeb案例之配置文件的详细解析

4. 配置文件 员工管理的增删改查功能我们已开发完成&#xff0c;但在我们所开发的程序中还一些小问题&#xff0c;下面我们就来分析一下当前案例中存在的问题以及如何优化解决。 4.1 参数配置化 在我们之前编写的程序中进行文件上传时&#xff0c;需要调用AliOSSUtils工具类&…

基于springboot+vue的校园赛事资讯网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

【Python笔记-设计模式】建造者模式

一、说明 又称生成器&#xff0c;是一种创建型设计模式&#xff0c;使其能够分步骤创建复杂对象。允许使用相同的创建代码生成不同类型和形式的对象。 (一) 解决问题 对象的创建问题&#xff1a;当一个对象的构建过程复杂&#xff0c;且部分构建过程相互独立时&#xff0c;可…

leetcode-704.二分查找

题目 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9输出: 4 解释: 9 …

Web性能优化之如何评估网页性能——性能指标和度量工具介绍

前言 用户在访问 web 网页时&#xff0c;大部分都希望网页能够在一秒完成。事实上&#xff0c;加载时间每多 1 秒&#xff0c;就会流失 7%的用户。如果时间超过 8s 用户就会感到不耐烦、会放弃访问。这也就是著名的 “8秒原则”。 虽然当今设备及网络环境都大幅提升&#xff…

Android 跳转应用设置/热点界面或等常用操作

Android 跳转应用设置/热点界面或等常用操作 https://www.jianshu.com/p/ba7164126690 android学习进阶——Setting https://blog.csdn.net/csdn_wanziooo/article/details/81980984 Android 7.1 以太网反射 EthernetManager 配置 DHCP、静态 IP https://codeleading.com/art…

Java List的合并与切分

在Java开发中经常遇到list结构数据的处理&#xff0c;如List的合并或拆分&#xff0c;记录下来&#xff0c;方便备查。 一、List 合并 两个list数据的合并处理&#xff0c;可使用Java8 新特性的stream流&#xff0c;根据实际需要遍历取值。 1、定义 UserInfo 对象 订单的相…

Request对象-获取请求消息

Request 概述&#xff1a;Request 和 Response 对象都是由 Web 服务器(Tomcat)创建的&#xff0c;我们来使用它们&#xff0c;Request 对象是用来 获取请求消息 的&#xff0c;Response 对象是用来 设置响应消息 的 Request 对象的原理 Request 对象的继承体系结构 Reque…

IS-IS的LSP分片扩展

原理 IS-IS通过泛洪LSP来宣告链路状态信息,由于一个LSP能够承载的信息量有限,IS-IS将对LSP进行分片。每个LSP分片由产生该LSP的结点或伪结点的SystemID、PseudnodeID(普通LSP中该值为0,Pseudonode LSP中该值为非0)、LSPNumber(LSP分片号)组合起来唯一标识,由于LSPNumb…

【大数据安全】数据管理安全安全分析隐私保护

目录 一、数据管理安全 &#xff08;一&#xff09;数据溯源 &#xff08;二&#xff09;数字水印 &#xff08;三&#xff09;策略管理 &#xff08;四&#xff09;完整性保护 &#xff08;五&#xff09;数据脱敏 二、安全分析 &#xff08;一&#xff09;大数据安全…

【昕宝爸爸小模块】日志系列之什么是分布式日志系统

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

【Linux】环境基础开发工具的使用(一)

前言&#xff1a;在此之前我们学习了一些Linux的权限&#xff0c;今天我们进一步学习Linux下开发工具的使用。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:Linux的深度刨析 &#x1f448; &#x1f4af;代码仓库:卫卫周大胖的学习日记…

关于谷歌新版调试用具(Chrome Dev Tool ),网络选项(chrome-network)默认开启下拉模式的设置

今天在使用谷歌浏览器进行调试的时候&#xff0c;打开调试工具网络选项发现过滤不同模式的选项卡不见了&#xff0c;转而变成一个下拉式选项&#xff0c;如下图 这样一来使得切换不同类型查看的时候变得非常不方便&#xff0c;然后网上查了一下发现这个功能谷歌在很早版本就已…