• [知识分享] java代码七夕魔方照片墙
    介绍七夕魔方照片墙是一款创意应用,旨在让用户通过一个3D魔方展示他们的照片。每个面都可以显示不同的图片,用户可以旋转和翻转魔方来浏览所有的照片。这种方式不仅仅是简单地展示照片,而是将视觉效果与互动体验结合起来,使得照片展示更加生动有趣。应用使用场景个人相册展示: 用户可以将自己的旅游照片、家庭照片等按日期或主题放在魔方上展示。企业产品宣传: 企业可以将产品图片制作成魔方,用于展会或者网站上进行互动展示。艺术作品展示: 艺术家可以将自己的作品集成在魔方上,观众可以通过旋转魔方查看每一个细节。教育培训: 可以用于教学内容的展示,比如展示几何图形的各个面。原理解释七夕魔方照片墙基于三维图形渲染技术,将多个二维图像拼接在一个立方体的六个面上。通过用户的交互(如鼠标拖拽、触摸屏滑动),实现立方体的旋转变化,从而展现不同的面上的照片。算法原理流程图及解释+----------------+| Load Images |+--------+-------+ | v+--------+----------+| Initialize Cube || - Create 3D model || - Map images |+--------+----------+ | v+--------+-----------+| Render Cube || - Set up camera || - Apply textures |+--------+-----------+ | v+--------+-----------+| User Interaction || - Detect input || - Update rotation |+--------+-----------+ | v+--------+-----------+| Update Display || - Re-render cube |+--------------------+加载图片:从本地或远程资源加载用户选择的图片。初始化魔方:创建一个3D模型,并将图片映射到魔方的六个面上。渲染魔方:设置摄像机视角,将已贴好图像的3D魔方渲染到屏幕上。用户交互:检测用户输入(如鼠标拖拽、手指滑动等),并根据输入更新魔方的旋转状态。更新显示:重新渲染魔方,以反映最新的旋转状态。应用场景代码示例实现以下是一个利用 Java 和 JavaFX 实现七夕魔方照片墙的简单示例:import javafx.application.Application;import javafx.scene.*;import javafx.scene.image.Image;import javafx.scene.paint.PhongMaterial;import javafx.scene.shape.Box;import javafx.stage.Stage;import javafx.scene.transform.Rotate;public class PhotoCube extends Application { private static final double CUBE_SIZE = 200.0; private static final String[] IMAGES = { "image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "image5.jpg", "image6.jpg" }; @Override public void start(Stage primaryStage) throws Exception { Group root = new Group(); Scene scene = new Scene(root, 800, 600, true); PerspectiveCamera camera = new PerspectiveCamera(true); camera.setTranslateZ(-500); Box cube = createPhotoCube(); root.getChildren().add(cube); scene.setOnMouseDragged(event -> { cube.getTransforms().add(new Rotate(event.getSceneX(), Rotate.Y_AXIS)); cube.getTransforms().add(new Rotate(event.getSceneY(), Rotate.X_AXIS)); }); primaryStage.setTitle("七夕魔方照片墙"); primaryStage.setScene(scene); primaryStage.show(); } private Box createPhotoCube() { Box box = new Box(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE); PhongMaterial material = new PhongMaterial(); material.setDiffuseMap(new Image(IMAGES[0])); box.setMaterial(material); return box; } public static void main(String[] args) { launch(args); }}部署测试场景开发环境: 安装 JDK 和 JavaFX SDK。确保你的开发环境可以编译和运行 JavaFX 项目。构建项目: 使用 IDE(如 IntelliJ IDEA 或 Eclipse)创建并构建该项目。运行测试: 在本地运行应用程序,测试魔方的旋转和图片展示功能。材料链接​​JavaFX 官方文档​​​​JavaFX 示例项目​​​​Java 图形编程入门教程​​总结七夕魔方照片墙通过3D图形渲染和用户交互,为照片展示提供了一种创新和有趣的方式。它不仅适用于个人照片展示,还可以用于企业宣传、教育培训等多个领域。通过详细的算法解释和代码实现,我们了解了其工作原理和实现方法。未来展望未来,可以考虑加入更多的交互功能,例如:动画效果:增加自动旋转和缩放的动画效果,使展示更具动态性。多媒体支持:除了图片外,还可以支持视频和音频展示,增加内容的丰富性。云端存储和分享:集成云存储服务,让用户可以方便地上传和分享自己的魔方照片墙。这些改进将进一步提升用户体验和应用的实用性。
  • [技术干货] JAVA编辑word替换指定内容,解决插入图片显示不全问题
    在开发过程中,我们可能会遇到需要生成word,或者通过模板word替换相应内容的需求。但在文档中插入图片时,如果段落格式设置不对,就会导致图片只显示一点点或者不显示。接下来就介绍一下java编辑word和插入图片需怎么处理。1.引入依赖首先我们在项目中引入Apache POI,用于读取和操作word,这里我使用的版本是4.1.2,版本可以根据项目需求自己选择。12345<dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>4.1.2</version></dependency>   2.编辑word这里是通过模板加入占位符,然后替换占位符的内容首先我们打开word模板文件12345678910String path = "***.docx";File file = new File(path);try {    XWPFDocument template = new XWPFDocument(new FileInputStream(file));    // 替换内容    XWPFDocument outWord = PoiWordUtil.replaceWithPlaceholder(template, list);    return outWord;} catch (IOException e) {    log.error("读取模板文件失败", e);}替换相应内容1// 这里我定义了Placeholder来封装替换数据public static XWPFDocument replaceTextAndImage(XWPFDocument document, List<Placeholder> list) {    for (XWPFParagraph xwpfParagraph : document.getParagraphs()) {        String paragraphText = xwpfParagraph.getText(<em id="__mceDel">        if (StringUtils.isEmpty(paragraphText)) contin</em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel">        for (Placeholder placeholder : list) {</em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel">            String key = placeholder.getKey();</em></em></em></em></em></em></em><em><em><em><em><em><em><em>            if (paragraphText.contains(key)) {<br>                for (XWPFRun cellRun : xwpfParagraph.getRuns()) {<br>                    String text = cellRun.getText(0);<br>                    if (text != null && text.contains(key)) {<br>                        //获取占位符类型<br>                        String type = placeholder.getType();<br>                        //获取对应key的value<br>                        String value = placeholder.getValue();<br>                        if("0".equals(type)){<br>                            //把文本的内容,key替换为value<br>                            text = text.replace(key, value);<br>                            //把替换好的文本内容,保存到当前这个文本对象<br>                            cellRun.setText(text, 0);<br>                        }else {<br>                            text = text.replace(key, "");<br>                            cellRun.setText(text, 0);<br>                            if (StringUtils.isEmpty(value)) </em></em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel">continue;</em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel">                            try {<br>                   // 获取段落行距模式<br>                                int rule = xwpfParagraph.getSpacingLineRule().getValue();<br>                                // 如果段落行距为固定值,会导致图片显示不全,所以需要改成其他模式<br>                                if (LineSpacingRule.EXACT.getValue() == rule) {<br>                                    // 设置段落行距为单倍行距<br>                                    xwpfParagraph.setSpacingBetween(1);<br>                                }<br>                    // 获取文件流<br>                   InputStream imageStream = ImageUtils.getFile(value);</em></em></em></em></em></em></em><em id="__mceDel">                                if (imageStream == null) continue;<br>                                // 通过BufferedImage获取图片信息<br>                                BufferedImage bufferedImage = ImageIO.read(imageStream);<br>                                int height = bufferedImage.getHeight();<br>                                int width = bufferedImage.getWidth();<br>                                // 这里需要重新获取流,之前的流已经被BufferedImage使用掉了<br>                                cellRun.addPicture(ImageUtils.getFile(value), XWPFDocument.PICTURE_TYPE_JPEG, "", Units.toEMU(width), Units.toEMU(height));</em>                            } catch (Exception e) {<br>                                e.printStackTrace();<br>                            }<br>                        }<br>                    }<br>                }<br>            }<br>        }<br>    }<br>    return  document;<br>}在插入图片时,如果段落的行距设置成了固定值,那么在显示图片时只能显示行距大小的部分,所以当插入图片的段落行距为固定值时,我们需要修改为其他模式,这样图片就能正常大小显示。然后我们使用cellRun.addPicture()来插入图片,这里我们可以通过BufferedImage来获取图片的尺寸大小。这样就解决了插入图片显示异常的问题了。转载自https://www.cnblogs.com/qq545505061/p/18302543
  • [技术干货] Volatile不保证原子性及解决方案
    原子性的意义原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。Volatile 的限制不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。一个例子用一个示例来解释会更清楚点,假如我们有一段代码是这样的:class Counter { private volatile int count = 0; void increment() { count++; } int getCount() { return count; } } 尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。volatile 不保证原子性的代码验证以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:public class VolatileTest { private static volatile int counter = 0; public static void main(String[] args) throws InterruptedException { int numberOfThreads = 10000; Thread[] threads = new Thread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) { counter++; } }); threads[i].start(); } for (int i = 0; i < numberOfThreads; i++) { threads[i].join(); } System.out.println("Expected count: " + (numberOfThreads * 100)); System.out.println("Actual count: " + counter); } }在这个例子中:counter 是一个 volatile 变量。每个线程都会对 counter 执行 100 次自增操作。理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。解决方案1. 使用 synchronized 方法或块:将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。class Counter { private volatile int count = 0; synchronized void increment() { count++; } synchronized int getCount() { return count; } }2. 使用 AtomicInteger 类:java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。import java.util.concurrent.atomic.AtomicInteger; class Counter { private AtomicInteger count = new AtomicInteger(0); void increment() { count.incrementAndGet(); } int getCount() { return count.get(); } }3. 使用锁(如 ReentrantLock):使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Counter { private volatile int count = 0; private final Lock lock = new ReentrantLock(); void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }使用volatile变量的正确使用场景如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。转载自https://www.cnblogs.com/wgjava/p/18311697
  • [技术干货] 使用 @Audited 增强Spring Boot 应用程序的数据审计能力
    介绍在Spring Boot开发的动态世界中,确保数据完整性和跟踪变化是至关重要的。实现这一目标的一个强大工具是@Audited注解。本文深入探讨了该注解的复杂性、其目的、实现步骤以及如何利用其功能进行有效的实体审计。理解@AuditedSpring Boot中的@Audited注解用于审计实体,提供对数据随时间变化的详细记录。这在需要跟踪修改、用户操作或合规要求的情况下非常有价值。实现步骤1. 依赖项:要包含@Audited,需要在项目中添加spring-data-envers依赖。确保你的pom.xml或build.gradle反映这一添加。<!-- Maven Dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency>spring-boot-starter-data-jpa依赖包含了使用Spring Data JPA进行数据访问所需的组件。然而,如果你特别想在Spring Boot中使用@Audited注解启用实体审计,还需要包含hibernate-envers依赖。该依赖添加了对Hibernate Envers的支持,这是负责实体版本控制和审计的工具。2. 实体配置:将@Audited注解应用于你想要审计的实体类。import org.hibernate.envers.Audited; @Entity @Audited public class YourEntity { // 你的实体字段和方法 }3. application.yml配置:确保你的application.yml或application.properties包含Hibernate Envers所需的配置。spring: data: jpa: repositories: enabled: true auditing: enabled: true4. 审计表字段:Hibernate Envers生成的审计表通常包括REV(修订号)、REVTYPE(修订类型)、AUDIT_TIMESTAMP(审计时间戳)等字段。这些字段共同存储对审计实体的历史更改。Spring Boot会自动创建审计表(例如,‘YourEntity_AUD’)以存储元数据。探索审计表中的字段:– REV:修订号(递增)– REVTYPE:修订类型(插入、更新、删除)– AUDITEDFIELD:审计字段值– MODIFIEDBY:进行更改的用户– MODIFIEDDATE:修改日期和时间5. 检索审计数据:使用Spring Data JPA仓库查询审计历史。import org.springframework.data.repository.history.RevisionRepository; import org.springframework.data.history.Revision; import java.util.List; public interface YourEntityAuditRepository extends RevisionRepository<YourEntity, Long, Integer> { List<Revision<Integer, YourEntity>> findRevisionsById(Long entityId); } 在这个例子中:– YourEntityAuditRepository扩展了RevisionRepository,这是一个处理修订的Spring Data JPA接口。– findRevisionsById方法允许你检索具有指定ID的实体的所有修订。然后,你可以在服务或控制器中使用此仓库查询审计历史:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class AuditService { private final YourEntityAuditRepository entityAuditRepository; @Autowired public AuditService(YourEntityAuditRepository entityAuditRepository) { this.entityAuditRepository = entityAuditRepository; } public List<Revision<Integer, YourEntity>> getEntityRevisions(Long entityId) { return entityAuditRepository.findRevisionsById(entityId); } }另一个例子使用Hibernate Envers查询具有给定ID的特定实体的审计历史List<YourEntity_AUD> revisions = auditReader.findRevisions(YourEntity.class, entityld);– auditReader:一个AuditReader实例,由Hibernate Envers提供。它允许你与实体的审计历史进行交互。– findRevisions:Hibernate Envers提供的方法,用于检索具有指定ID的给定实体的所有修订。– YourEntity.class:你想要检索审计历史的实体类。– entityId:你想要获取修订的实体的特定ID。– List<YourEntity_AUD>:结果是一个审计实体(YourEntity_AUD)的列表,列表中的每个条目代表实体的一个修订。在Hibernate Envers中,当你为一个实体启用审计时,它会生成一个带有“_AUD”后缀的相应审计实体(默认情况下)。这个审计实体会跟踪原始实体随时间变化的所有更改。因此,这行代码本质上是在查询具有给定ID的实体的所有修订的审计历史,并将结果存储在一个审计实体列表中。然后,可以使用此列表分析或显示实体在不同修订中的更改。转载自https://www.cnblogs.com/didispace/p/18322448
  • [技术干货] Spring的Bean生命周期中@PostConstruct注解
    前言在Spring框架中,@PostConstruct注解用于在Bean初始化完成后立即执行某些方法。这个注解的作用是保证在依赖注入完成后,执行一些初始化工作。诞生背景@PostConstruct注解的诞生是为了提供一种标准化的、简单的方法来进行对象初始化工作。1. 简化初始化逻辑在传统的Java开发中,进行对象初始化通常需要在构造函数中完成。然而,构造函数的局限性使得一些初始化操作变得复杂,例如:构造函数不能依赖于容器注入的资源,因为这些资源在对象实例化时可能还没有准备好。复杂的初始化逻辑会导致构造函数变得冗长且难以维护。@Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } }@PostConstruct提供了一种更清晰的方式来处理初始化逻辑。依赖注入完成后,容器会自动调用标注了 @PostConstruct的方法,使得开发者可以在这时安全地访问所有依赖资源。简化逻辑代码示例import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; @Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } @PostConstruct public void init() { // 在这里进行初始化逻辑 // 这时依赖已经完全准备好,可以安全地使用 dependency.setup(); System.out.println("MyService 初始化完成"); } // 其他业务方法 public void performTask() { System.out.println("执行任务"); } }2. 标准化的生命周期管理在Java EE和Spring这样的依赖注入框架中,Bean的生命周期管理是一个重要的特性。@PostConstruct和@PreDestroy注解是Java EE(JSR-250)标准的一部分,为生命周期管理提供了标准化的解决方案。这使得开发者可以在不同的框架中使用相同的注解,而不用依赖于特定框架的API。3. 提高代码的可读性和可维护性@PostConstruct注解可以将初始化逻辑从构造函数中分离出来,使代码更具可读性和可维护性。开发者可以更清晰地看到哪些方法是用于初始化的,哪些方法是用于业务逻辑的。这种分离还可以使单元测试更加容易,因为初始化逻辑可以被单独测试。4. 与依赖注入框架的结合在依赖注入框架(如Spring)中,Bean的依赖关系是由容器管理的。在Bean实例化和依赖注入完成之后,框架会自动调用 @PostConstruct方法。这种机制确保了所有依赖都已准备就绪,开发者可以安全地在 @PostConstruct方法中进行进一步的初始化操作。生命周期的详细介绍Bean实例化:Spring容器根据配置文件或注解扫描创建Bean实例。依赖注入:Spring容器进行依赖注入,将所需的依赖对象注入到Bean中。调用@PostConstruct方法:依赖注入完成后,Spring容器会调用标注了@PostConstruct的方法。这个方法通常用于执行初始化操作,比如配置资源、进行检查或执行一些准备工作。Bean可用:在调用了@PostConstruct方法后,Bean被标记为可用,并准备好接受请求或执行其预定的功能。Bean销毁(可选,使用@PreDestroy):当Spring容器关闭时,可以通过@PreDestroy注解来指定一个方法,在Bean被销毁之前执行清理工作。生命周期的代码示例import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component public class MyBean { // 构造方法 public MyBean() { System.out.println("MyBean实例化"); } // @PostConstruct注解的方法 @PostConstruct public void init() { System.out.println("MyBean初始化 - @PostConstruct方法调用"); // 这里可以执行一些初始化操作 } // @PreDestroy注解的方法 @PreDestroy public void cleanup() { System.out.println("MyBean销毁之前 - @PreDestroy方法调用"); // 这里可以执行一些清理操作 } // 其他业务方法 public void performTask() { System.out.println("MyBean执行任务"); } }关键点总结依赖注入后执行:@PostConstruct方法在Bean的依赖注入完成之后执行,确保所有依赖都已经准备就绪。只执行一次:@PostConstruct方法在Bean的整个生命周期中只会执行一次。用于初始化逻辑:适合用来执行一些初始化逻辑,比如检查配置、初始化资源、启动辅助线程等。与Spring结合:@PostConstruct是JavaEE规范中的一部分,但在Spring中也得到了广泛应用,主要用于简化Bean的初始化工作。注意事项只适用于单个方法:一个类中只能有一个 @PostConstruct方法。方法签名:@PostConstruct方法不能有任何参数,也不能有返回值。异常处理:@PostConstruct方法如果抛出异常,会阻止Bean的创建,Spring容器会在启动时抛出异常。转载自https://www.cnblogs.com/hyg0513/p/18314560
  • [知识分享] 扑克的算牌公式及软件制作
    扑克算牌公式及Java和C++软件制作要实现一个扑克的算牌公式,并开发相应的Java和C++程序,首先需要定义一些基本的步骤和概念。具体来说,我们需要:定义扑克数据结构:表示扑克牌和其属性。历史数据导入:从外部文件或数据库中导入历史数据。概率计算:基于历史数据计算剩余牌的出现次数和概率。界面及交互:提供用户接口来发牌并显示结果。1. 数据结构定义我们用简单的数据结构来表示扑克牌。在Java和C++中,可以分别使用类和结构体来实现。Java代码public class Card { public enum Suit {HEARTS, DIAMONDS, CLUBS, SPADES} private Suit suit; private int value; public Card(Suit suit, int value) { this.suit = suit; this.value = value; } public Suit getSuit() { return suit; } public int getValue() { return value; } @Override public String toString() { return suit.toString() + value; } }C++代码#include <string> #include <iostream> struct Card { enum Suit { HEARTS, DIAMONDS, CLUBS, SPADES } suit; int value; std::string toString() const { static const char* suitNames[] = {"Hearts", "Diamonds", "Clubs", "Spades"}; return suitNames[suit] + std::to_string(value); } };2. 历史数据导入假设历史数据存储在一个CSV文件中,我们可以编写函数来读取这些数据。Java代码import java.io.*; import java.util.*; public class HistoryData { public static List<List<Card>> loadHistory(String filename) throws IOException { List<List<Card>> history = new ArrayList<>(); BufferedReader br = new BufferedReader(new FileReader(filename)); String line; while ((line = br.readLine()) != null) { String[] parts = line.split(","); List<Card> hand = new ArrayList<>(); for (String part : parts) { String[] cardParts = part.split(" "); Card.Suit suit = Card.Suit.valueOf(cardParts[0].toUpperCase()); int value = Integer.parseInt(cardParts[1]); hand.add(new Card(suit, value)); } history.add(hand); } br.close(); return history; } }C++代码#include <vector> #include <fstream> #include <sstream> #include <iostream> std::vector<std::vector<Card>> loadHistory(const std::string& filename) { std::vector<std::vector<Card>> history; std::ifstream file(filename); std::string line; while (std::getline(file, line)) { std::istringstream ss(line); std::vector<Card> hand; std::string part; while (std::getline(ss, part, ',')) { std::istringstream cardStream(part); std::string suitStr; int value; cardStream >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; hand.push_back({suit, value}); } history.push_back(hand); } return history; }3. 概率计算基于导入的历史数据进行概率计算。Java代码import java.util.HashMap; import java.util.List; import java.util.Map; public class ProbabilityCalculator { public static Map<Card, Double> calculateProbabilities(List<List<Card>> history, List<Card> givenCards) { Map<Card, Integer> counts = new HashMap<>(); for (List<Card> hand : history) { boolean match = true; for (Card card : givenCards) { if (!hand.contains(card)) { match = false; break; } } if (match) { for (Card card : hand) { counts.put(card, counts.getOrDefault(card, 0) + 1); } } } int totalHands = history.size(); Map<Card, Double> probabilities = new HashMap<>(); for (Map.Entry<Card, Integer> entry : counts.entrySet()) { probabilities.put(entry.getKey(), entry.getValue() / (double) totalHands); } return probabilities; } }C++代码#include <unordered_map> #include <vector> std::unordered_map<Card, double, CardHash> calculateProbabilities( const std::vector<std::vector<Card>>& history, const std::vector<Card>& givenCards) { std::unordered_map<Card, int, CardHash> counts; int totalHands = 0; for (const auto& hand : history) { bool match = true; for (const auto& card : givenCards) { if (std::find(hand.begin(), hand.end(), card) == hand.end()) { match = false; break; } } if (match) { totalHands++; for (const auto& card : hand) { counts[card]++; } } } std::unordered_map<Card, double, CardHash> probabilities; for (const auto& entry : counts) { probabilities[entry.first] = entry.second / static_cast<double>(totalHands); } return probabilities; }4. 主程序及交互将所有部分整合到主程序中,并提供交互界面。Java代码import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.println("Enter the path to the historical data file:"); String filepath = scanner.nextLine(); List<List<Card>> history = HistoryData.loadHistory(filepath); System.out.println("Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):"); List<Card> givenCards = new ArrayList<>(); for (int i = 0; i < 3; i++) { String input = scanner.nextLine(); String[] parts = input.split(" "); Card.Suit suit = Card.Suit.valueOf(parts[0].toUpperCase()); int value = Integer.parseInt(parts[1]); givenCards.add(new Card(suit, value)); } Map<Card, Double> probabilities = ProbabilityCalculator.calculateProbabilities(history, givenCards); System.out.println("Probabilities of remaining cards:"); for (Map.Entry<Card, Double> entry : probabilities.entrySet()) { System.out.printf("%s: %.2f%%\n", entry.getKey(), entry.getValue() * 100); } } }C++代码#include <iostream> #include <vector> #include <unordered_map> int main() { std::cout << "Enter the path to the historical data file:" << std::endl; std::string filepath; std::cin >> filepath; auto history = loadHistory(filepath); std::cout << "Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):" << std::endl; std::vector<Card> givenCards; for (int i = 0; i < 3; ++i) { std::string suitStr; int value; std::cin >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; givenCards.push_back({suit, value}); } auto probabilities = calculateProbabilities(history, givenCards); std::cout << "Probabilities of remaining cards:" << std::endl; for (const auto& entry : probabilities) { std::cout << entry.first.toString() << ": " << entry.second * 100 << "%" << std::endl; } return 0; }总结通过上述步骤,我们实现了一个扑克算牌工具,可以根据给定的三张牌计算后续牌的出现概率。该工具使用Java和C++分别实现了数据结构、历史数据导入、概率计算以及用户交互。
  • [技术干货] Java经典算法之快速排序算法-转载
    前言 快速排序算法可以分为两部分来看:  第一部分:将枢轴元素移动到最终位置  第二部分:分别处理枢轴元素左右两边的元素  tips:上面的第一、二步是不断递归的过程。读者可以去某站看一下王道的数据结构课  建议:1.学习算法最重要的是理解算法的每一步,而不是记住算法。             2.建议读者学习算法的时候,自己手动一步一步地运行算法。  1. 快速排序简介 快速排序是一种分治法(Divide and Conquer)的排序算法,由英国计算机科学家Tony Hoare于1960年提出。其基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有元素均比另一部分的元素小,然后分别对这两部分继续进行排序,最终达到整个序列有序的效果。  2. 快速排序的基本原理 快速排序的基本原理可以总结为以下三个步骤:  2.1 选择基准元素 从待排序的数组中选择一个元素作为基准元素。选择基准元素的方式有多种,常见的方法包括选择第一个元素、最后一个元素或者随机选择一个元素。  2.2 分割操作 将数组中比基准元素小的元素移到基准元素的左边,比基准元素大的元素移到右边。这个过程称为分割操作。  2.3 递归排序 递归地对基准元素左右两侧的子数组进行快速排序。  3. Java中的快速排序实现 下面是一个简单的Java实现快速排序的例子:  public class QuickSort {       public static void quickSort(int[] arr, int low, int high) {         if (low < high) {             // 找到划分点             int pivotIndex = partition(arr, low, high);               // 递归地对左右两部分进行快速排序             quickSort(arr, low, pivotIndex - 1);             quickSort(arr, pivotIndex + 1, high);         }     }       private static int partition(int[] arr, int low, int high) {         // 选择最后一个元素作为基准         int pivot = arr[high];         int i = (low - 1);  // 小于基准值的索引           for (int j = low; j < high; j++) {             // 当前元素小于或等于基准值时             if (arr[j] <= pivot) {                 i++;                   // 交换元素,使得小于基准值的元素移到左边                 int temp = arr[i];                 arr[i] = arr[j];                 arr[j] = temp;             }         }           // 把基准值放到正确的位置上(即i+1)         int temp = arr[i + 1];         arr[i + 1] = arr[high];         arr[high] = temp;           return i + 1;     }       public static void main(String[] args) {         int[] arrayToSort = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};         int n = arrayToSort.length;         quickSort(arrayToSort, 0, n - 1);           System.out.println("Sorted array: ");         for (int num : arrayToSort) {             System.out.print(num + " ");         }     } }  在上述代码中:  quickSort 方法是用于递归调用的主要方法,它接受数组、以及待排序区间的起始和结束索引。 partition 方法负责执行分区操作,返回基准元素最终应该所在的位置。 在 partition 方法内部,使用了一个指针 i 来跟踪小于基准值的所有元素的边界,并通过遍历整个区间来进行元素的移动和交换,最后确保基准元素处于正确位置,并返回其索引。 通过这样的方式,快速排序能够以平均时间复杂度为 的效率完成对数据的排序。  4.时空复杂度 4.1 时间复杂度: 最好情况(平均情况): 当每次划分都能将数组大致均等地分为两部分时,快速排序的时间复杂度为 ,其中n是待排序数组中的元素个数。这是由于每次递归调用都会减少问题规模大约一半,并且需要层递归。  最坏情况: 在最坏情况下,即输入数组已经有序或接近有序时,每次划分只能将数组划分为一个元素和剩余元素两部分,这会导致递归树退化为线性结构,时间复杂度上升到 。不过,通过随机化选择基准元素或采用三数取中等优化方法,可以在实际应用中避免这种情况的发生,使得快速排序在实践中通常能保持接近其平均时间复杂度。  4.2 空间复杂度: 原地排序: 快速排序在理想情况下只需要常数级别的额外空间,即空间复杂度为 O(1)。这是因为交换操作可以直接在原数组上进行,不需要额外存储空间。  递归栈空间: 然而,在实际实现中,快速排序是递归实现的,因此递归调用会占用一定的栈空间。在最坏的情况下,递归深度可达 n 层,所以空间复杂度可能达到 。然而,在平均情况下,由于快速排序的平衡特性,递归树的高度约为,故空间复杂度通常是。  4.3 总结: 总结来说,快速排序在理想情况下具有非常高的效率,尤其在处理大型数据集时表现出色。但要注意的是,对于小规模数据或者近乎有序的数据,其他排序算法(如插入排序)可能会更优。同时,为了保证算法性能稳定,通常会对基准元素的选择进行优化以减小最坏情况发生的概率。  5.优缺点 5.1 优点: 高效性:  在平均情况下,快速排序的时间复杂度为,这使得它在处理大规模数据时表现出极高的效率,并且是实践中最快的通用内部排序算法之一。 原地排序(In-place):  快速排序不需要额外的存储空间来保存临时数组,只需要一个很小的栈空间用于递归调用,所以空间复杂度在理想情况下可以达到。 广泛应用:  由于其高效的性能和灵活的实现,快速排序被广泛应用于实际系统中的各种排序场景,尤其是在内存受限或需要实时排序的情况下。 适应性强:  对于不同的输入数据分布有较好的适应性,尤其是当采用随机化版本选择基准元素时,能有效地避免最坏情况的发生。 易于理解与实现:  基本思想简单直观,通过分治策略将大问题划分为小问题,便于理解和编程实现。 5.2 缺点: 最坏情况下的性能:  当待排序的数据已经部分有序或者完全有序时,快速排序可能退化成的时间复杂度。不过,可以通过优化基准元素的选择(例如三数取中、随机化选取等方法)来减少这种情况发生的概率。 不稳定排序:  快速排序不是稳定的排序算法,即相等元素的相对顺序可能会在排序过程中改变。 递归深度:  由于使用了递归,如果数据规模很大且递归树不平衡时,可能会导致栈溢出的问题。虽然通过尾递归优化或其他非递归实现方式可以缓解这个问题,但这也增加了实现的复杂性。 边界条件处理:  对于非常小的数据集(如小于一定阈值),快速排序可能不如插入排序等其他简单的排序算法高效,因此通常会结合其他算法进行混合排序以提升整体性能。 6.现实中的应用 快速排序算法在现实中的应用非常广泛,因其高效性、通用性和易于实现的特点,被大量用于各种软件和硬件系统中。以下是一些典型的应用场景:  数据处理与分析:  在大数据处理、数据分析以及数据库管理系统中,快速排序是内部排序操作的核心算法之一,尤其适用于大规模数据集的排序,比如SQL查询语句执行过程中的ORDER BY子句。 编程语言标准库:  许多现代编程语言的标准库或内置函数都包含了快速排序算法,例如Java的Arrays类、C++的std::sort函数等,为开发者提供了现成的排序工具。 操作系统内核:  操作系统内核在处理内存管理、文件系统排序目录结构、进程调度时可能使用快速排序或其他优化过的排序算法来提高效率。 机器学习与人工智能:  在机器学习领域,快速排序可应用于特征选择、模型训练前的数据预处理阶段,例如对输入样本进行排序以构建更高效的索引结构。 编译器优化:  编译器在符号表管理和代码生成阶段,可能会用到快速排序对变量名、函数名等进行排序,以加速查找和比较操作。 科学计算与工程应用:  在数值计算、信号处理、图像处理等领域,快速排序可用于数组和矩阵元素的排序,尤其是在处理稀疏矩阵和大型向量时。 嵌入式系统与实时系统:  尽管快速排序在最坏情况下的性能不理想,但在许多实际应用中通过随机化选取基准值等方法可以有效避免性能退化,因此也被用于嵌入式系统或实时系统的数据排序。 竞赛编程与算法研究:  在ACM国际大学生程序设计竞赛(ACM-ICPC)以及其他算法竞赛中,快速排序作为基础排序算法,经常出现在解题方案中。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_56154577/article/details/136596325 
  • [技术干货] 自行搭建rustdesk服务器-转载
    应用介绍 RustDesk 是一款可以平替 TeamViewer 的开源软件,旨在提供安全便捷的自建方案。 描述  跨平台支持:RustDesk可以在Windows、Linux、MacOS以及Android等多个平台上使用,为用户提供了极大的便利,无需担心设备兼容性问题。 安全性:它采用了安全的加密传输技术,确保用户数据的安全。在数据传输过程中,RustDesk会对数据进行加密,以防止数据泄露或被篡改。 高性能和低延迟:RustDesk的高性能和低延迟特性使得用户在进行远程操作时几乎感觉不到延迟,大大提高了用户体验。 多功能性:RustDesk支持多屏幕显示,对于需要同时操作多个屏幕的用户来说非常实用。此外,它还支持文件传输、剪贴板共享以及多种连接方式,包括基于IP的连接和基于域名的连接,以满足不同用户的需求。 易用性:相较于其他远程工具,RustDesk无需繁琐配置,用户只需在受控机上安装RustDesk并获取其ID和密码,然后在主控机上输入这些信息,即可快速建立连接。这种简洁的操作方式使得用户无需专业知识即可轻松上手。 灵活性:用户可以选择使用RustDesk的官方服务器或自建服务器,同时,如果用户有自己的云服务器且服务器带宽足够,那么使用RustDesk的体验将会更加流畅。  特性:  随时随地访问任何设备 支持 Windows、macOS、Linux、iOS、Android、Web 等多个平台。 支持 VP8 / VP9 / AV1 软件编解码器和 H264 / H265 硬件编解码器。 完全掌控数据,轻松自建。 P2P 连接,端到端加密。 在 Windows上可以非管理员不安装运行,根据需要在本地或远程提升权限。 操作简单  前期准备 本文将通过Linux宝塔面板Docker部署RustDesk服务器  应用部署 创建应用目录,我的地址/www/server/rustdesk,为了保证后续操作成功,希望与我的目录设置统一。 创建docker-compose.yml配置文件,这里我已经帮大家写好了,直接复制修改即可 version: '3'  networks:   rustdesk-net:     external: false  services:   hbbs:     container_name: hbbs     ports:       - 21115:21115       - 21116:21116 # 自定义 hbbs 映射端口       - 21116:21116/udp # 自定义 hbbs 映射端口     image: rustdesk/rustdesk-server:latest # 注意这里要加:latest,防止docker镜像缓存未更新的问题     command: hbbs -r xxx.xxx.com:21117 -k _ # 填入个人域名或 IP + hbbr 暴露端口,这里填写你解析后的域名或服务器ip都行, -k _意为使用key进行认证     volumes:       - /www/server/rustdesk:/root # 自定义挂载目录     networks:       - rustdesk-net     depends_on:       - hbbr     restart: unless-stopped     deploy:       resources:         limits:           memory: 64M    hbbr:     container_name: hbbr     ports:       - 21117:21117 # 自定义 hbbr 映射端口     image: rustdesk/rustdesk-server:latest #注意,这里同样需要拉取最新镜像     command: hbbr -k _ #这里的key也是需要key认证的话则要加     volumes:       - /www/server/rustdesk:/root # 自定义挂载目录     networks:       - rustdesk-net     restart: unless-stopped     deploy:       resources:         limits:           memory: 64M 注意:  hbbr 与 hbbs 的挂载目录必须为同一个,否则后面链接会提示对方已挂断提示 如果你想所有人都可以通过你的服务器来进行链接rustdesk,则需要删掉配置文件中 -k _ 配置文件中xxx.xxx.com需要替换为解析到目标服务器的域名或目标服务器公网ip地址 设置服务器安全组开放端口规则 注意:  将自己服务器与上方配置文件中有关所有端口全部放开21115,21116/TCP,21116/UDP,21117 开放宝塔面板中端口,同上 执行配置文件下载并启动容器 docker-compose up -d 1 查看启动容器运行中 docker ps -a 1 设备链接 下载rustdesk客户端并完成服务器对接 DownloadFile  下载完成进入软件,打开软件网络设置  注意:  ID服务器为文件中配置的个人域名或ip。格式例:www.baidu.com or 127.0.0.1 如果配置文件中增加了上述提到的-k _配置,则需要找到文件挂载目录/www/server/rustdesk中的id_ed25519.pub文件,将内容复制即可。 如果没有配置则不需要进行配置 错误解决 如果完全按照以上操作步骤进行部署任然出现了错误请看这里👇  ID不存在 配置了key的情况下,如果两台电脑key不匹配则会提示此问题。将两边电脑key配置相同即可  链接被对方关闭 这个问题就是前面提到的,hbbr 和 hbbs的挂载目录必须相同。更新配置文件,删除旧容器,重新启动即可  结语 真的很推荐有自己服务器的自己去使用这个。真的比收费某些远程工具好用很多。也流畅很多。 如果部署出现其他问题请留言或私信我~ ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/qq_45602658/article/details/138702681 
  • [互动交流] obs java的sdk在流式下载的时候有几率io异常
    sdk版本为3.21.11,实现的功能是依次下载obs内多个文件,并打包生成压缩包放在硬盘路径内首先在本地pc测试无问题,均能正常依次下载文件,但在华为云服务器(华北北京4的服务器,带宽5M)上运行,有很高频率压缩包会生成失败使用文档提供的 带获取进度的下载 示例来测试,观察日志发现,压缩包生成失败的原因,是在某几个文件上会有很高的频率下载到一定进度后就无响应(进度百分比的日志输出到不固定的某个进度后就停止了),直到超过设置的ConnectionTimeout和SocketTimeout时间,才抛出了SocketTimeoutException: Read timed out的异常(未捕获到ObsException异常),进而造成压缩包生成失败问题1:是否有办法继续定位 文件下载到一定进度后就无响应 问题原因的办法?问题2:如果考虑 捕获到SocketTimeoutException: Read timed out后进行重试机制,这边就希望能将设置的ConnectionTimeout和SocketTimeout时间缩小以减少重试的等待时间,但如果因为下载的文件过大,导致文件没有在这两个时间内下载完成,是否也会直接下载失败?注:试过直接在服务器下的浏览器访问这几个文件的url地址,均能正常获取到,所以我猜不是网络原因
  • [问题求助] 华为鸿蒙系统 安卓原生APP做VPN连接时手机息屏后就断
    我开发了一个安卓VPN应用,安装在华为鸿蒙系统上   一按电源键息屏就会发现数据包不发送了,已经设置了wakelock和手机设置息屏状态下wifi始终保持连接均不好使,请教各位需要怎么做
  • [专题汇总] 6月份干货合集来了,速进。
     大家好,6月份干货合集又来了,本次带来的内容涵盖了,java,Android,html,git,mysql,linux,网络协议,Jenkins等诸多内容供您选择 1.模拟买票小练习-线程资源同步小练习-synchronized使用 https://bbs.huaweicloud.com/forum/thread-02109154511981591054-1-1.html  2.Android Intent-Filter匹配规则解析 https://bbs.huaweicloud.com/forum/thread-02109154511945084053-1-1.html  3.web前端入门面对的git、angular和web开发必备的技术以及前端开发如何的运用技术? https://bbs.huaweicloud.com/forum/thread-02109154511910212052-1-1.html  4.前端技术分享(html总结) https://bbs.huaweicloud.com/forum/thread-02109154511861684051-1-1.html  5.0基础小白如何玩转前端开发? https://bbs.huaweicloud.com/forum/thread-02109154466914607049-1-1.html  6.RTSP 和 RTMP通过ffmpeg实现将本地摄像头推流到RTSP服务器-转载 https://bbs.huaweicloud.com/forum/thread-0244153476749248016-1-1.html  7.mysql8.0 性能优化配置 innodb_buffer_pool_size-转载 https://bbs.huaweicloud.com/forum/thread-02109153476715536012-1-1.html  8.linux 服务器无 sudo 权限非 root 用户安装特定版本 cuda -转载 https://bbs.huaweicloud.com/forum/thread-0224153476680638017-1-1.html  9.网络网络层之(6)ICMPv4协议-转载 https://bbs.huaweicloud.com/forum/thread-0273153476617308017-1-1.html  10.Window下SRS服务器的搭建-转载 https://bbs.huaweicloud.com/forum/thread-0264153476579428008-1-1.html  11.探索SRS-GB28181:一款强大的国标GB28181视频服务器-转载 https://bbs.huaweicloud.com/forum/thread-0210153476525852014-1-1.html  12.容器化部署 Jenkins,并配置SSH远程操作服务器-转载 https://bbs.huaweicloud.com/forum/thread-0224153476500551016-1-1.html  13.如何查看ubuntu服务器上防火墙信息-转载 https://bbs.huaweicloud.com/forum/thread-02127153476446295011-1-1.html  14.探索GoServer:高效、易用的Golang服务器框架 -转载 https://bbs.huaweicloud.com/forum/thread-0210153476421991013-1-1.html  15.使用Linux命令修改服务器时间及设置时区-转载 https://bbs.huaweicloud.com/forum/thread-0273153476396290016-1-1.html  16.pg_rman在恢复服务器上恢复源库的备份-转载 https://bbs.huaweicloud.com/forum/thread-0244153476353118015-1-1.html  17.linux之用户和权限-转载     https://bbs.huaweicloud.com/forum/thread-0273153476326497015-1-1.html  18.Linux-查看服务器--硬件配置信息-转载 https://bbs.huaweicloud.com/forum/thread-0273153476291979014-1-1.html  19.Python如何对文件进行重命名操作?-转载 
  • [二次开发] 如何获取华为FusionInsight MRS二次开发Redis样例代码
    如何获取 华为FusionInsight MRS二次开发Redis样例代码 redis-examples项目呢?
  • [技术干货] Java中Volatile关键字详解及代码示例
    背景知识volatile 是 Java 中的一个关键字,主要用于确保多线程环境下的变量可见性和禁止指令重排序。当一个变量被声明为 volatile 时,它告诉 JVM 和其他线程,这个变量是不稳定的,每次使用它的时候都必须从主内存中读取,而当它改变时也必须立即同步回主内存。这可以确保多个线程在访问这个变量时能够获取到最新的值。可见性在多线程环境中,每个线程都有自己的工作内存(也称为本地内存),线程之间共享的数据存储在主内存中。当线程需要读取或修改共享数据时,它首先会在自己的工作内存中复制一份,完成操作后再将结果写回主内存。这种机制可能导致一个线程修改了共享变量的值,但其他线程无法立即看到这个修改,这就是所谓的“可见性问题”。volatile 关键字可以确保所有线程都能看到共享变量的最新值。禁止指令重排序在 Java 虚拟机中,为了提高执行效率,编译器和处理器可能会对指令进行重排序。但是,这种重排序可能会违反程序的语义。volatile 关键字可以禁止这种重排序,确保程序按照代码的顺序来执行。代码示例下面是一个简单的示例,展示了 volatile 关键字的使用:public class VolatileExample { // 使用 volatile 关键字修饰的变量 private volatile boolean flag = false; // 启动一个新线程来修改 flag 的值 public void startThread() { new Thread(() -> { try { // 等待一段时间,确保主线程已经开始运行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 修改 flag 的值 flag = true; System.out.println("Flag in thread: " + flag); }).start(); } public void mainLoop() { while (!flag) { // 循环检查 flag 的值,直到它为 true // 由于 flag 是 volatile 的,所以每次循环都会从主内存中读取它的值 System.out.println("Waiting for flag to become true..."); } System.out.println("Flag in main thread: " + flag); } public static void main(String[] args) { VolatileExample example = new VolatileExample(); example.startThread(); // 启动一个新线程来修改 flag 的值 example.mainLoop(); // 在主线程中循环检查 flag 的值 } }在这个示例中,我们创建了一个 volatile 变量 flag,并在一个新线程中修改它的值。在主线程中,我们通过一个循环来检查 flag 的值,直到它变为 true。由于 flag 是 volatile 的,所以每次循环都会从主内存中读取它的值,确保我们能看到最新的修改。
  • [技术干货] Java 中使用 Optionals 解决 NullPointException(NPE)问题
    背景null 在 Java 中经常是一个问题源头,因为它可能导致 NullPointerException(NPE),这是 Java 应用程序中常见的运行时异常。为了解决这个问题,Java 8 引入了 Optional 类,这是一个可以为 null 的容器对象。它提供了一种更好的方法来处理可能不存在的值,从而避免了直接返回 null 导致的潜在问题。案例说明下面是您给出的反例和正例的详细解释:反例:public String getValue() { // TODO return null; // 这可能导致 NullPointerException 如果调用者没有检查返回值是否为 null }在上面的反例中,getValue 方法返回一个 String,但在某些情况下,它可能返回 null。任何调用 getValue 的代码都需要确保检查返回的字符串是否为 null,否则如果尝试在 null 上调用方法或访问字段,就会抛出 NullPointerException。正例:import java.util.Optional; public Optional<String> getValue() { // TODO return Optional.empty(); // 使用 Optional.empty() 代替 null }在上面的正例中,getValue 方法返回一个 Optional<String> 而不是 String。如果值不存在,则返回 Optional.empty() 而不是 null。这样,调用者就可以使用 Optional 类提供的方法来更明确地处理可能不存在的值。例如,他们可以使用 isPresent() 方法来检查值是否存在,或者使用 orElse()、orElseGet()、orElseThrow() 等方法来提供一个默认值或抛出异常。下面是如何使用 Optional 的一些示例:Optional<String> valueOptional = getValue(); // 检查值是否存在 if (valueOptional.isPresent()) { String value = valueOptional.get(); // 使用 value } // 提供默认值 String valueWithDefault = valueOptional.orElse("defaultValue"); // 使用 lambda 表达式提供默认值 String valueWithLambda = valueOptional.orElseGet(() -> createDefaultValue()); // 如果值不存在则抛出异常 valueOptional.orElseThrow(() -> new IllegalStateException("Value is not present"));总结通过使用 Optional,代码可以更清晰地表达其意图,并减少 NullPointerException 的风险。然而,需要注意的是,过度使用 Optional 也可能导致代码变得难以阅读和理解,因此应该谨慎使用。在大多数情况下,当返回的值可能不存在时,使用 Optional 是一个好选择。但在其他情况下,比如返回值始终存在,或者可以通过其他方式(如异常)来清晰地表达错误情况时,使用 Optional 可能就不是必需的。
  • [技术干货] Java中的原子操作
    一、什么是原子操作原子操作是指在一个操作序列中,要么全部执行,要么全部不执行,不会被其他线程中断的操作。在Java中,原子操作主要通过java.util.concurrent.atomic包下的类来提供,这些类中的方法都是线程安全的,可以在多线程环境中安全地进行数据更新操作。二、基本类型的原子操作Java的java.util.concurrent.atomic包提供了一系列原子操作类,用于对基本数据类型进行原子操作。这些类包括AtomicInteger、AtomicLong、AtomicBoolean等,它们分别提供了对int、long、boolean等基本数据类型的原子操作。三、AtomicIntegerAtomicInteger类提供了对int类型的原子操作。它提供了诸如增加、减少、设置、获取并设置等操作,这些操作都是线程安全的。3.1 使用案例import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerExample { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); // 原子性地增加并返回新的值 } public int getCount() { return count.get(); // 原子性地获取当前值 } public static void main(String[] args) { AtomicIntegerExample example = new AtomicIntegerExample(); // 模拟多线程环境 for (int i = 0; i < 1000; i++) { new Thread(() -> example.increment()).start(); } // 等待所有线程执行完毕 // ...(此处省略等待所有线程完成的代码) System.out.println("Final count: " + example.getCount()); // 应该接近1000 } }在上面的例子中,多个线程同时调用increment()方法来增加count的值,由于incrementAndGet()是原子操作,因此不会出现线程安全问题。四、AtomicLongAtomicLong类提供了对long类型的原子操作,其使用方式与AtomicInteger类似。4.1 使用案例import java.util.concurrent.atomic.AtomicLong; public class AtomicLongExample { private AtomicLong sequenceNumber = new AtomicLong(0); public long next() { return sequenceNumber.incrementAndGet(); // 原子性地获取并增加下一个序号 } public static void main(String[] args) { AtomicLongExample example = new AtomicLongExample(); // 演示获取几个序号 for (int i = 0; i < 5; i++) { System.out.println("Next sequence number: " + example.next()); } } }五、AtomicBooleanAtomicBoolean类提供了对boolean类型的原子操作,其使用方式与前面的类类似。5.1 使用案例import java.util.concurrent.atomic.AtomicBoolean; public class AtomicBooleanExample { private AtomicBoolean flag = new AtomicBoolean(false); public void toggleFlag() { flag.set(!flag.get()); // 原子性地切换标志位的状态 } public boolean isFlagSet() { return flag.get(); // 原子性地获取标志位的状态 } public static void main(String[] args) { AtomicBooleanExample example = new AtomicBooleanExample(); example.toggleFlag(); // 切换标志位的状态 System.out.println("Is flag set? " + example.isFlagSet()); // 输出: Is flag set? true example.toggleFlag(); // 再次切换标志位的状态 System.out.println("Is flag set? " + example.isFlagSet()); // 输出: Is flag set? false } }在上面的例子中,AtomicBoolean被用来存储一个标志位,通过toggleFlag()方法原子性地切换标志位的状态,并通过isFlagSet()方法原子性地获取标志位的状态。
总条数:739 到第
上滑加载中