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

news2024/11/20 3:21:32

文章目录

  • 一、前言
  • 二、项目背景
  • 三、实现方案
  • 四、思路延伸
    • 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
     */
    @SneakyThrows
    public 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);
            }

            // 绘制图片转为 PDF
            Image 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();
        // 构造一个类型为预定义图像类型之一的 BufferedImage
        BufferedImage 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));
    }

    /**
     * 优先级文件
     */
    @Getter
    static 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;
        }

        @Override
        public 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));
    }

  /**
     * 优先级文件
     */
    @Getter
    static 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;
        }

        @Override
        public int compareTo(PriorityFile o) {
            return o.getPriority() - this.priority;
        }
    }
    
	...
	
}

总结:

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

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

    
        @Override
        public boolean offer(V e) {
            return add(e);
        }
        
    	@Override
        public 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
https://zhuanlan.zhihu.com/p/657006095

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

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

相关文章

【算法分析与设计】交换两个节点

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本…

实习日志11

1.文件上传报错 1.1.报错信息Invalid typed array length: -2 1.2.查看源码找出错误 定位到检查代码上传是否成功的代码出错&#xff0c;rDataArr[3] 0x03 var pData new Uint8Array(pDataLen);的pDataLen4 说明rDataArr只有0-3&#xff0c;其他数据都没有上传上来 说明…

DoubleEnsemble:基于样本重加权和特征选择的金融数据分析方法

现代机器学习模型&#xff08;如深度神经网络和梯度提升决策树&#xff09;由于其提取复杂非线性模式的优越能力&#xff0c;在金融市场预测中越来越受欢迎。然而&#xff0c;由于金融数据集的信噪比非常低&#xff0c;并且是非平稳的&#xff0c;复杂的模型往往很容易过拟合。…

leetcode189.轮转数组|超简单易于理解方法

题目 https://leetcode.cn/problems/rotate-array/description/https://leetcode.cn/problems/rotate-array/description/ 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输…

基于uniapp+vue酒店宾馆客房民宿管理系统设计 微信小程序_54ybz

APP性能需求 &#xff08;1&#xff09;顾客在安卓APP页面各种操作可及时得到反馈。 &#xff08;2&#xff09;该平台是提供给多个用户使用的平台&#xff0c;用户使用之前需要注册登录。登录验证后&#xff0c;用户才可进行各种操作[10]。 &#xff08;3&#xff09;管理员、…

Java 集合 04 综合练习-查找用户是否存在

练习、 代码&#xff1a; public class User{private String id;private String username;private int password;public User() {}public User(String id, String username, int password) {this.id id;this.username username;this.password password;}public String getI…

舟山长寿医学中心:引领生命科技前沿

在浩瀚的东海之滨&#xff0c;舟山群岛如一颗璀璨的明珠&#xff0c;镶嵌在碧波荡漾的大海之中。这里不仅拥有得天独厚的自然美景&#xff0c;更是一块充满生机与活力的健康宝地。舟山长寿医学中心&#xff0c;正是这片神奇的土地上的一颗璀璨明珠&#xff0c;致力于为全球人士…

Linux权限【上篇】

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 扩展知识&#xff1a…

react 之 UseReducer

UseReducer作用: 让 React 管理多个相对关联的状态数据 import { useReducer } from react// 1. 定义reducer函数&#xff0c;根据不同的action返回不同的新状态 function reducer(state, action) {switch (action.type) {case INC:return state 1case DEC:return state - 1de…

系统移植--无法启动Linux内核--报错VFS--挂载nfs失败

问题 找信息&#xff1a;VFS 可能的原因 1、开发板上内核启动参数中的虚拟机ubuntu IP和真实的 虚拟机的IP不一致 2、开发板上内核启动参数中虚拟机的共享目录和虚拟机 ubuntu上配置的nfs服务器上的共享目录不一致 3、nfs配置文件(/etc/exports)路径错误 与自己的共享文件…

Galah:一款功能强大的LLM驱动型OpenAI Web蜜罐系统

关于Galah Galah是一款功能强大的Web蜜罐&#xff0c;该工具由LLM大语言模型驱动&#xff0c;基于OpenAI API实现其功能。 很多传统的蜜罐系统会模拟一种包含了大量网络应用程序的网络系统&#xff0c;但这种方法非常繁琐&#xff0c;而且有其固有的局限性。Galah则不同&…

Transformer 自然语言处理(四)

原文&#xff1a;Natural Language Processing with Transformers 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十章&#xff1a;从头开始训练变换器 在本书的开头段落中&#xff0c;我们提到了一个名为 GitHub Copilot 的复杂应用&#xff0c;它使用类似 GPT 的…

C语言:详解操作符(上)

摘要&#xff1a; 本篇&#xff0c;我们将学习C语言中操作符的相关内容&#xff0c;操作符是C语言中重要的元素之一&#xff0c;在我们的代码中处处都有&#xff0c;下面我们将详细介绍操作符的相关内容&#xff0c;并结合一些代码例题加深印象。 目录 一、操作符的分类及介绍 …

当无法在Windows 10上更改分辨率时怎么办?这里提供几个方法

一般来说,如果你愿意,你可以很容易地更改Windows 10计算机的屏幕分辨率。如果你发现无法在Windows 10中更改分辨率,可以查看下面的解决方案来解决此问题。 无法在Windows 10上更改分辨率? 要在Windows 10上检查和更改屏幕分辨率,通常有两种简单的方法。 方法一:你可以…

Vue3+vite搭建基础架构(5)--- 使用vue-i18n

Vue3vite搭建基础架构&#xff08;5&#xff09;--- 使用vue-i18n 说明官方文档安装vue-i18n使用vue-i18n测试vue-i18n的国际化配置 说明 这里记录下自己在Vue3vite的项目使用vue-i18n做国际化语言的过程&#xff0c;不使用ts语法&#xff0c;方便以后直接使用。这里承接自己的…

工作七年,对消息推送使用的一些经验和总结

前言&#xff1a;不管是APP还是WEB端都离不开消息推送&#xff0c;尤其是APP端&#xff0c;push消息&#xff0c;小信箱消息&#xff1b;WEB端的代办消息等。因在项目中多次使用消息推送且也是很多项目必不可少的组成部分&#xff0c;故此总结下供自己参考。 一、什么是消息推…

VS2019项目的图标问题

图标问题搞的我很困惑。。。 最开始&#xff1a; 添加图标1&#xff1a; 然后问题出现了&#xff0c;我想给它换个图标&#xff0c;死活成功不了。。。 。。。 替换成功了。。。 但不完全成功。。。 这是怎么回事啊&#xff1f; vs 2019更改exe程序图标简易教程_哔哩哔哩_…

8. 字符串转换整数 (atoi)-LeetCode(Java)

8. 字符串转换整数 (atoi) 题目&#xff1a;8. 字符串转换整数 (atoi) 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入…

三维模型设计新纪元:3D开发工具HOOPS在机械加工行业的应用与优势

在当今快速发展的科技时代&#xff0c;机械加工行业正经历着巨大的变革&#xff0c;而HOOPS技术正是其中一项重要的创新。HOOPS技术不仅仅是一种用于处理和可视化计算机辅助设计&#xff08;CAD&#xff09;数据的工具&#xff0c;更是机械加工领域中提升效率、优化设计的利器。…

2024年美赛B题思路分析 - 搜索潜水器

# 1 赛题 问题B&#xff1a;搜索潜水器 总部位于希腊的小型海上巡航潜艇&#xff08;MCMS&#xff09;公司&#xff0c;制造能够将人类运送到海洋最深处的潜水器。潜水器被移动到该位置&#xff0c;并不受主船的束缚。MCMS现在希望用他们的潜水器带游客在爱奥尼亚海底探险&…