【Qt】图片绘制不清晰的问题

背景

实现一个图片浏览器,可以支持放大/缩小查看图片。主要组件如下:

// canvaswidget.h
#ifndef CANVASWIDGET_H
#define CANVASWIDGET_H

#include <QWidget>

class CanvasWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CanvasWidget(QImage img, QWidget *parent = nullptr);
    void zoomIn();
    void zoomOut();

signals:

protected:
    QSize sizeHint();
    void paintEvent(QPaintEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    qreal scale;
    QPixmap pixmap;
};

#endif // CANVASWIDGET_H
// canvaswidget.cpp
#include "canvaswidget.h"
#include <QWheelEvent>
#include <QPainter>
#include <QPixmap>

CanvasWidget::CanvasWidget(QImage img, QWidget *parent)
    : QWidget{parent}, scale(1.0)
{
    pixmap = QPixmap::fromImage(img);
}

void CanvasWidget::zoomIn() {
    scale = fmin(scale + 0.1, 10);
    update();
}

void CanvasWidget::zoomOut() {
    scale = fmax(scale - 0.1, 0.1);
    update();
}

void CanvasWidget::paintEvent(QPaintEvent *event) {
    if(!pixmap) {
        return QWidget::paintEvent(event);
    }
    QPainter p(this);

    p.setRenderHint(QPainter::Antialiasing);
    p.setRenderHint(QPainter::SmoothPixmapTransform);
    p.scale(scale, scale);
    p.drawPixmap(0,0,pixmap); // draw image
}

void CanvasWidget::wheelEvent(QWheelEvent *event)
{
    if(event->modifiers() == Qt::ControlModifier) {
        QPointF delta = event->angleDelta();
        int v_delta = delta.y();
        if(v_delta > 0) {
            zoomIn();
        } else {
            zoomOut();
        }
        update();
        adjustSize();
    } else {
        QWidget::wheelEvent(event);
    }
}
QSize CanvasWidget::sizeHint()
{
    return QSize(800,800);
}

问题

在这种实现方式下,缩小图片时,图片会变得非常模糊,有非常明显的锯齿问题。
如下图所示,A是Windows自带图片查看器的效果,B是上述实现的效果。可以看出虽然B比A更大,但却更不清晰,有明显的锯齿。

在这里插入图片描述

尝试解决

为了解决这个不清晰的问题,尝试了很多种方案,方案及其实现方法如下:

不scale QPainter,而是在指定区域绘制Pixmap

p.drawPixmap(0,0,pixmap.size().width() * scale, pixmap.size().height * scale, pixmap);

使用QGraphicsView绘制图片

    QPixmap pixmap("/path/to/image.png");
    
    QGraphicsScene scene;
    QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
    scene.addItem(item);
    
    QGraphicsView view;
    view.resize(800,600);
    view.setScene(&scene);

    // Optionally set view properties
    view.setRenderHint(QPainter::Antialiasing);   // Improve rendering quality
    view.setDragMode(QGraphicsView::ScrollHandDrag); // Enable dragging
    view.setAlignment(Qt::AlignCenter);           // Center the image
    view.fitInView(item, Qt::KeepAspectRatio);    // Scale to fit the view

    // Show the view
    view.show();

使用QWebEngineView绘制图片

    QWebEngineView web_view;

    QString htmlContent = R"(
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; }
                img { max-width: 100%; max-height: 100%; }
            </style>
        </head>
        <body>
            <img src="/path/to/image.png" alt="Image Not Found">
        </body>
        </html>
    )";

    web_view.setHtml(htmlContent, QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + "/"));
    web_view.resize(800, 600);
    web_view.show();

将图片作为texture在QOpenGLWidget中绘制图片

#ifndef OPENGLIMAGE_H
#define OPENGLIMAGE_H

#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <memory>

class OpenGLImage : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLImage(QWidget *parent = nullptr);
    ~OpenGLImage();
    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;
    void loadImage(QString& path);
    QMatrix4x4 getViewMatrix() const;
    QMatrix4x4 getModelMatrix() const;

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;
    void wheelEvent(QWheelEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;
    void keyReleaseEvent(QKeyEvent* event) override;

private:
    void setupDefaultShaderProgram();
    void setupDefaultTransform();
    void drawImage();
    void moveImage(const QPointF& cursorPos);
    void rotateImage(const QPointF& cursorPos);

    std::unique_ptr<QOpenGLShaderProgram> shaderProgram;
    std::unique_ptr<QOpenGLTexture> texture;
    std::unique_ptr<QImage> image;
    QOpenGLBuffer vbo;
    QOpenGLVertexArrayObject vao;
    QOpenGLBuffer ebo;
    bool isTextureSync;
    QColor clearColor;
    float norm_h;
    QSize viewSize;

    QVector3D cameraPos;
    QVector3D imagePos;
    QVector3D imageAngle;
    float viewAngle;
    float focalLength;

    QPointF lastClickPos;
    bool isRotMode;
};

#endif // OPENGLIMAGE_H
#include "glimageview.h"
#include <vector>
#include <QtMath>
#include <iostream>
#include <QResizeEvent>

#define PROGRAM_VERTEX_ATTRIBUTE 0
#define PROGRAM_TEXCOORD_ATTRIBUTE 1

#define DEFAULT_CAMERA_POS_X (0.0f)
#define DEFAULT_CAMERA_POS_Y (0.0f)
#define DEFAULT_CAMERA_POS_Z (-2.0f)

#define CLIP_NEAR (0.01f)
#define CLIP_FAR (100.0f)

#define MIN_FOCAL 1.0f
#define MAX_FOCAL 150.0f

OpenGLImage::OpenGLImage(QWidget *parent)
    : QOpenGLWidget(parent),
      shaderProgram(nullptr),
      texture(nullptr),
      image(nullptr),
      isTextureSync(false),
      clearColor(Qt::gray),
      norm_h(-1.0f),
      viewSize(640,640),
      ebo(QOpenGLBuffer::Type::IndexBuffer),
      viewAngle(45.0f),
      isRotMode(false)
{
    focalLength = 1/qTan(qDegreesToRadians(viewAngle/2.0f));
}

OpenGLImage::~OpenGLImage()
{
}

void OpenGLImage::initializeGL()
{
    initializeOpenGLFunctions();
    setupDefaultShaderProgram();
}

void OpenGLImage::paintGL()
{
    glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), clearColor.alphaF());
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawImage();
}

void OpenGLImage::resizeGL(int width, int height)
{
    viewSize = QSize(width, height);
}

QSize OpenGLImage::minimumSizeHint() const
{
    int min_h = (int)(320.0f * norm_h);
    return QSize(320, min_h);
}

QSize OpenGLImage::sizeHint() const
{
    return viewSize;
}

void OpenGLImage::wheelEvent(QWheelEvent *event)
{
    QPoint numDegrees = event->angleDelta() / 8;
    float degree = (float)numDegrees.y() * -1.0f;
    degree /= 2.0f;
    if (viewAngle+degree > MIN_FOCAL && viewAngle+degree < MAX_FOCAL) {
        viewAngle += degree;
        focalLength = 1/qTan(qDegreesToRadians(viewAngle/2.0f));
    }
    event->accept();
    update();
}

void OpenGLImage::drawImage() {
    if (image.get() == nullptr) return;
    glViewport(0, 0, viewSize.width(), viewSize.height());
    // qDebug() << viewSize.width() << ", " << viewSize.height() << "\n";
    // setup vertex array object
    if (!vao.isCreated())
    {
        vao.create();
    }
    vao.bind();

    // setup vertex buffer object
    std::vector<GLfloat> coords;
    // bottom left;
    coords.push_back(-1.0f);
    coords.push_back(-1.0f * norm_h);
    coords.push_back(0.0f);
    // tex coordinate
    coords.push_back(0.0f);
    coords.push_back(0.0f);

    // bottom right
    coords.push_back(1.0f);
    coords.push_back(-1.0f * norm_h);
    coords.push_back(0.0f);
    // tex coordinate
    coords.push_back(1.0f);
    coords.push_back(0.0f);

    // top right
    coords.push_back(1.0f);
    coords.push_back(1.0f * norm_h);
    coords.push_back(0.0f);
    // tex coordinate
    coords.push_back(1.0f);
    coords.push_back(1.0f);

    // top left
    coords.push_back(-1.0f);
    coords.push_back(1.0f * norm_h);
    coords.push_back(0.0f);
    // tex coordinate
    coords.push_back(0.0f);
    coords.push_back(1.0f);

    if (!vbo.isCreated())
    {
        vbo.create();
    }
    vbo.bind();
    vbo.allocate(coords.data(), coords.size()*sizeof(GLfloat));

    // setup vertex element object
    // [bl, br, tr, tl]
    static const std::vector<GLuint> indices {
        0, 1, 2,
        2, 3, 0
    };
    if (!ebo.isCreated())
    {
        ebo.create();
    }
    ebo.bind();
    ebo.allocate(indices.data(), indices.size()*sizeof(GLuint));

    // associate vertex and buffer
    shaderProgram->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
    shaderProgram->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
    shaderProgram->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat));
    shaderProgram->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));

    // assign transform matrices
    QMatrix4x4 projection; // projection matrxi must update everytime!
    float ratio = ((float)viewSize.width())/((float)viewSize.height());
    projection.perspective(viewAngle, ratio, CLIP_NEAR, CLIP_FAR);
    QMatrix4x4 model = getModelMatrix();
    model.rotate(imageAngle.x(), 0.0f, 1.0f, 0.0f);
    model.rotate(imageAngle.y()*-1.0f, 1.0f, 0.0f, 0.0f);
    shaderProgram->setUniformValue("model", model);
    QMatrix4x4 viewMat = getViewMatrix();
    shaderProgram->setUniformValue("view", viewMat);
    shaderProgram->setUniformValue("projection", projection);

    // setup texture
    if (texture.get() == nullptr || !isTextureSync) {
        QImage& img = *image.get();
        texture = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(img));
        isTextureSync = true;
    }
    texture->bind();
    glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0 );
}

void OpenGLImage::setupDefaultShaderProgram()
{
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    const char *vsrc =
        "attribute highp vec3 vertex;\n"
        "uniform mediump mat4 model;\n"
        "uniform mediump mat4 view;\n"
        "uniform mediump mat4 projection;\n"
        "\n"
        "attribute mediump vec2 texCoord;\n"
        "varying mediump vec2 texc;\n"
        "void main(void)\n"
        "{\n"
        "    gl_Position = projection * view * model * vec4(vertex, 1.0f);\n"
        "    texc = texCoord;\n"
        "}\n";
    vshader->compileSourceCode(vsrc);

    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    const char *fsrc =
        "uniform sampler2D texture;\n"
        "varying mediump vec2 texc;\n"
        "void main(void)\n"
        "{\n"
        "    gl_FragColor = texture2D(texture, texc);\n"
        "}\n";
    fshader->compileSourceCode(fsrc);

    shaderProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram(this));
    shaderProgram->addShader(vshader);
    shaderProgram->addShader(fshader);
    // assign locations of vertex and texture coordinates
    shaderProgram->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);
    shaderProgram->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
    shaderProgram->link();
    shaderProgram->bind();
    shaderProgram->setUniformValue("texture", 0);
}

void OpenGLImage::setupDefaultTransform() {
    cameraPos = QVector3D(DEFAULT_CAMERA_POS_X, DEFAULT_CAMERA_POS_Y, DEFAULT_CAMERA_POS_Z);
    imagePos = QVector3D();
    imageAngle = QVector3D();
}

void OpenGLImage::loadImage(QString& path) {
    QImage* p = new QImage(QImage(path).mirrored());
    image = std::unique_ptr<QImage>(p);
    isTextureSync = false;
    norm_h = (float)((float)image->height()/(float)image->width());
    int h = (int)((float)viewSize.width()*norm_h);
    viewSize = QSize(viewSize.width(), h);
    resize(viewSize);
    setupDefaultTransform();
}

QMatrix4x4 OpenGLImage::getViewMatrix() const {
    QVector3D up(0.0f, 1.0f, 0.0f);
    QMatrix4x4 ret;
    ret.translate(cameraPos);
    QVector3D center(cameraPos.x(), cameraPos.y(), imagePos.z());
    ret.lookAt(QVector3D(), center, up);
    return ret;
}

QMatrix4x4 OpenGLImage::getModelMatrix() const {
    QMatrix4x4 ret;
    ret.translate(imagePos);
    return ret;
}

void OpenGLImage::mousePressEvent(QMouseEvent *event) {
    lastClickPos = event->localPos();
    qDebug() << lastClickPos;
}

// movement is weird somehow...
void OpenGLImage::mouseMoveEvent(QMouseEvent *event) {
    if (isRotMode) {
        rotateImage(event->localPos());
    } else {
        moveImage(event->localPos());
    }
    lastClickPos = event->pos();
    event->accept();
    update();
}

void OpenGLImage::moveImage(const QPointF &cursorPos) {
    QPointF delta = cursorPos-lastClickPos;
    float factor = qAbs(imagePos.z()-cameraPos.z()) / focalLength;
    factor /= (qMax(viewSize.width(), viewSize.height()));
    factor *= 3.5f;
    qDebug() << "dx=" << delta.x();
    qDebug() << "dy=" << delta.y();
    qDebug() << "L=" << (imagePos.z()-cameraPos.z());
    qDebug() << "focalLength=" << focalLength;
    qDebug() << "factor" << factor;
    delta *= factor;
    imagePos += QVector3D(delta.x(), -1.0f*delta.y(), 0.0f);
}

void OpenGLImage::rotateImage(const QPointF &cursorPos) {
    QPointF delta = cursorPos-lastClickPos;
    delta.setX(delta.x() / (qreal)viewSize.width());
    delta.setX(delta.x() * 180.0f);
    delta.setY(delta.y() / (qreal)viewSize.height());
    delta.setY(delta.y() * -180.0f);
    qDebug() << delta;
    imageAngle += QVector3D(delta.x(), delta.y(), 0.0f);
}

void OpenGLImage::mouseReleaseEvent(QMouseEvent *event) {
}

void OpenGLImage::keyPressEvent(QKeyEvent *event) {
    if (event->key() == Qt::Key_Control) {
        qDebug() << "ctrl is pressed";
        isRotMode = true;
    } else {
        // call base class method as event is not handled.
        QOpenGLWidget::keyPressEvent(event);
    }
}

void OpenGLImage::keyReleaseEvent(QKeyEvent *event) {
    if (event->key() == Qt::Key_Control) {
        qDebug() << "ctrl is released";
        isRotMode = false;
    } else {
        // call base class method as event is not handled.
        QOpenGLWidget::keyReleaseEvent(event);
    }
}

如下图所示,不同方案的效果略有不同,但所有方案都会出现缩小后图片变模糊的问题:
在这里插入图片描述

问题所在

最终在网友们的帮助下,发现了问题所在:这些实现方法在修改图片大小时都会对图片进行压缩。

比如void QPainter::drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source),在指定矩形区域内绘制图片,如果指定的矩形区域比图片本身尺寸小,绘制过程中就会对图片进行压缩,导致图片变得模糊。

如果想要将图片变小的同时,保持图片的清晰度,应该直接使用QPixmap的scaled函数:

p.drawPixmap(0,0,pixmap.scaled(pixmap.size() * scale, Qt::KeepAspectRatio, Qt::SmoothTransformation));

效果如下,左边是新的实现方法的效果,右边是Windows自带的图片查看软件的效果:
在这里插入图片描述

其实我一开始的实现方法不算错,甚至是官方建议的,在QPixmap的文档中提到:

In some cases it can be more beneficial to draw the pixmap to a painter with a scale set rather than scaling the pixmap. This is the case when the painter is for instance based on OpenGL or when the scale factor changes rapidly.

图片查看器其实就会频繁改变scale按照建议就是应该采用修改QPainter的scale的方法,但这种方法确实会导致图片清晰度变低,出现模糊的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vscode 怎么下载 vsix 文件?

参考&#xff1a;https://marketplace.visualstudio.com/items?itemNameMarsCode.marscode-extension 更好的办法&#xff1a;直接去相关插件的 github repo 下载老版本 https://github.com/VSCodeVim/Vim/releases?page5 或者&#xff0c;去 open-vsx.org 下载老版本 点击这…

学习笔记043——HashMap源码学习1

文章目录 1、HashMap2、Hashtable3、TreeMap4、HashMap 底层结构4.1、什么是红黑树&#xff1f; 1、HashMap HashMap key 是不能重复的&#xff0c;value 可以重复 底层结构 key-value 进行存储&#xff0c;key-value 存入到 Set 中&#xff0c;再将 Set 装载到 HashMap pack…

火语言RPA流程组件介绍--键盘按键

&#x1f6a9;【组件功能】&#xff1a;模拟键盘按键 配置预览 配置说明 按键 点击后,在弹出的软键盘上选择需要的按键 执行后等待时间(ms) 默认值300,执行该组件后等待300毫秒后执行下一个组件. 输入输出 输入类型 万能对象类型(System.Object)输出类型 万能对象类型…

电子应用设计方案-30:智能扫地机器人系统方案设计

智能扫地机器人系统方案设计 一、引言 随着人们生活节奏的加快和对生活品质的追求&#xff0c;智能家居产品越来越受到消费者的青睐。智能扫地机器人作为一种能够自动清扫地面的智能设备&#xff0c;为人们节省了大量的时间和精力。本方案旨在设计一款功能强大、智能化程度高、…

从简单的自动化脚本到复杂的智能助手:Agent技术的实践与应用

现代软件开发中&#xff0c;Agent技术正在悄然改变着我们构建应用程序的方式。一个Agent就像是一个能独立完成特定任务的智能助手&#xff0c;它可以感知环境、作出决策并采取行动。让我们通过实际案例&#xff0c;深入了解如何运用Agent技术来构建智能系统。 想象你正在开发一…

Ubuntu Server 22.04.5 从零到一:详尽安装部署指南

文章目录 Ubuntu Server 22.04.5 从零到一&#xff1a;详尽安装部署指南一、部署环境二、安装系统2.1 安装2.1.1 选择安装方式2.1.2 选择语言2.1.3 选择不更新2.1.4 选择键盘标准2.1.5 选择安装版本2.1.6 设置网卡2.1.7 配置代理2.1.8 设置镜像源2.1.9 选择装系统的硬盘2.1.10 …

定时/延时任务-ScheduledThreadPoolExecutor的使用

文章目录 1. 概要2. 固定速率和固定延时2.1 固定速率2.2 固定延时 3. API 解释3.1 schedule3.2 固定延时 - scheduleWithFixedDelay3.2 固定速率 - scheduleWithFixedDelay 4. 小结 1. 概要 前三篇文章的地址&#xff1a; 定时/延时任务-自己实现一个简单的定时器定时/延时任…

Linux操作系统学习---初识环境变量

目录 ​编辑 环境变量的概念&#xff1a; 小插曲&#xff1a;main函数的第一、二个参数 获取环境变量信息&#xff1a; 1.main函数的第三个参数 2.查看单个环境变量 3.c语言库函数getenv() 和环境变量相关的操作指令&#xff1a; 1.export---导出环境变量&#xff1a; 2.unse…

husky,commit规范,生成CHANGELOG.md,npm发版

项目git提交工程化&#xff08;钩子&#xff0c;提交信息commit message&#xff09;&#xff0c;npm修改版本&#xff0c;需要涉及到的包&#xff1a; husky&#xff0c;允许在git钩子中执行不同的脚步&#xff0c;如commitlint&#xff0c;eslint&#xff0c;prettier&#…

基于Python的飞机大战复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【趣味】斗破苍穹修炼文字游戏HTML,CSS,JS

目录 图片展示 游戏功能 扩展功能 完整代码 实现一个简单的斗破苍穹修炼文字游戏&#xff0c;你可以使用HTML、CSS和JavaScript结合来构建游戏的界面和逻辑。以下是一个简化版的游戏框架示例&#xff0c;其中包含玩家修炼的过程、增加修炼进度和显示经验值的基本功能。 图片…

005 MATLAB符号微积分

前言&#xff1a; 在MATLAB中&#xff0c;数值与符号的主要区别在于它们的处理方式和应用场景 数值计算适用于实际的数值计算问题&#xff0c;如矩阵运算、数据分析等。符号计算适用于符号推导、公式化简和符号解析&#xff0c;如理论物理和工程计算。 01 符号对象 1.基本符…

Android 13 编译Android Studio版本的Launcher3

Android 13 Aosp源码 源码版本Android Studio版本Launcher3QuickStepLib (主要代码) Launcher3ResLib(主要资源)Launcher3IconLoaderLib(图

Ubuntu交叉编译 opencv for QNX

前言 在高通板子上开发一些程序的时候,会用到opencv帮助处理一下图像数据,高通车载板子sa8155和sm8295都有QNX os,需要交叉编译opencv的库,(这个交叉编译真是搞得我太恶心了,所以进行一个记录和分享) 搜了很多资料,有些太过于复杂,有些也存在错误导致最后没有编译成…

NVR监测软件EasyNVR多个NVR同时管理:录播主机的5条常见问题与解决办法

视频监控广泛应用于城市治安、交通管理、商业安保及家庭监控等领域。在使用EasyNVR平台管理多个NVR设备时&#xff0c;尤其是涉及到海康录播主机的场景中&#xff0c;使用者可能会遇到一些常见问题。本文将探讨海康录播主机的五个常见问题及其解决办法。 1、海康录播主机的5条常…

力扣刷题TOP101:6.BM7 链表中环的入口结点

目录&#xff1a; 目的 思路 复杂度 记忆秘诀 python代码 目的 {1,2},{3,4,5}, 3 是环入口。 思路 这个任务是找到带环链表的环入口。可以看作是上一题龟兔赛跑&#xff08;Floyd 判圈算法&#xff09;的延续版&#xff1a;乌龟愤愤不平地举报兔子跑得太快&#xff0c;偷偷…

网关: 用途和产品对比

概述 微服务中的有一个非常关键的组件: API网关 和配置中心一样&#xff0c;在没有采用微服务架构的时候 我们可以自己搭建自己的API网作作为统一的 API 出口和安全验证 在微服务架构之下&#xff0c;服务被拆的非常的零散&#xff0c;在降低了耦合度的同时 也给服务的统一…

Java ConcurrentHashMap

Java Map本质不是线程安全的&#xff0c;HashTable和Collections同步包装器&#xff08;Synchronized Wrapper&#xff09;在并发场景下性能低。Java还为实现 Map 的线程安全提供了并发包&#xff0c;保证线程安全的方式从synchronize简单方式到精细化&#xff0c;比如Concurre…

Spring 自调用事务失效分析及解决办法

前言 博主在写公司需求的时候&#xff0c;有一个操作涉及到多次对数据库数据的修改。当时就想着要加 Transactional注解来声名事务。并且由于一个方法中有太多行了&#xff0c;于是就想着修改数据库的操作单独提取出来抽象成一个方法。但这个时候&#xff0c;IDEA 提示我自调用…

【LeetCode每日一题】——189.轮转数组

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【解题思路】九【时空频度】十【代码实现】十一【提交结果】 一【题目类别】 数组 二【题目难度】 中等 三【题目编号】 189.轮转数组 四【题目描述】 …