gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具

分类归档Java

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  大数据开发
  • 分类归档: "Java"
Java 3月 31,2025

SpringBoot打包冲突导致找不到主类问题分析与解决

在Java开发中,使用Maven构建SpringBoot应用时,经常会遇到”找不到或无法加载主类”的错误。这个问题通常与打包配置有关,特别是当项目同时使用了多个打包插件时。本文将深入分析这一问题的原因和解决方案。

问题现象

当我们使用命令java -cp target/xxx-jar-with-dependencies.jar com.example.MainClass 运行应用时,出现以下错误:

错误: 找不到或无法加载主类 com.example.MainClass

这表明Java虚拟机无法在指定的JAR包中找到主类,即使该类确实存在于源代码中。

原因分析

1. SpringBoot打包机制与传统打包的冲突

SpringBoot应用使用spring-boot-maven-plugin 打包时,会将类文件放在BOOT-INF/classes 目录下,而不是传统JAR包的根目录。这导致使用-cp 参数指定类路径时,JVM无法在预期位置找到主类。

2. 多插件打包导致的结构混乱

当项目同时配置了spring-boot-maven-plugin 和maven-assembly-plugin 时,两个插件会各自执行打包逻辑,可能导致最终JAR包结构不符合预期。特别是,maven-assembly-plugin 可能无法正确处理SpringBoot的特殊目录结构。

3. 主类声明位置不正确

在Maven配置中,主类可以在多个位置声明:

  • spring-boot-maven-plugin 的<configuration><mainClass> 元素
  • maven-assembly-plugin 的<archive><manifest><mainClass> 元素
  • maven-jar-plugin 的<archive><manifest><mainClass> 元素
    如果这些配置不一致或缺失,可能导致生成的JAR包中没有正确的主类信息。

解决方案

1. 禁用SpringBoot重新打包功能

如果使用maven-assembly-plugin 创建包含依赖的JAR包,可以禁用SpringBoot的重新打包功能:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

2. 使用正确的运行命令

对于SpringBoot应用,应使用java -jar 命令而非java -cp 命令:

java -jar target/application.jar

3. 统一打包策略

选择一种打包策略并坚持使用:

  • 使用SpringBoot的打包机制:依赖spring-boot-maven-plugin
  • 使用传统打包:依赖maven-assembly-plugin 或maven-shade-plugin
    避免混合使用多种打包插件,以防止目录结构冲突。

4. 检查类路径和包名

确保源代码中的包名与Maven配置中声明的主类包名完全一致,包括大小写。同时,验证编译后的类文件确实存在于JAR包中的预期位置。

作者 east
Java, 大数据开发 1月 22,2025

手撕代码刷题秘籍,小白到Offer收割机的进阶之路

​

 要准备面试,无非就是要准备熟背八股文,做有深度的项目,好好学习数据结构和算法,刷题准备手撕面试题。

其中手撕面试题的准备时间是比较长的。八股文和大厂手撕面试题可以看下面专栏,花了半个月时间从上千份大厂面经分类整理出面试题及手撕面试题。

嵌入式最全面试题-Offer直通车

大数据最全面试题-Offer直通车

Java开发及前端最全面试题-Offer直通车

Android最全面试题-Offer直通车

C++后端开发最全面试题-从入门到Offer

线上笔试

不少公司面试的第一关就是线上笔试题📃。咱得自己在家对着电脑做,就像考试一样,限定时间内完成题目。这种时候,环境相对熟悉,压力可能没那么大,但也得注意别因为在家就放松警惕,一定要严格按照考试要求来,提前调试好设备,保证网络稳定,别到时候因为这些小问题影响发挥。

现场手撕代码

等过了笔试,到了现场面试,这可就刺激了🤯。面试官会给你一台电脑,让你当场敲代码,有时候甚至直接给你一张纸,让你手写代码。这种情况对咱的心理素质和编程能力要求更高。在面试官眼皮子底下写代码,紧张是肯定的,但越这时候越得稳住,千万别慌了神。咱平时刷题练的就是这时候的底气💪。

入门指南:选对资料,开启刷题第一步

刚接触手撕代码刷题,选对入门资料太重要了📚,除了学校的数据结构和算法教科书,公认是推荐《剑指 Offer》。这本书对数据结构和算法的讲解特别详细,还有对应的 Java 代码示例,很适合新手。你可以先从基础的数据结构,像数组、链表这些开始看,把基础打牢。网上也有不少相关的在线教程,比如慕课网、网易云课堂上都有优质课程,跟着视频一步步学,理解起来更容易。

手撕面试题很多,下面这些是大厂高频的手撕面试题:

从上千份大厂面经呕心沥血整理:大厂高频手撕面试题(数据结构和算法篇 ,Java实现亲试可跑)

 从上千份大厂面经呕心沥血整理:大厂高频手撕面试题(数据结构和算法篇 ,C++实现亲试可跑)

循序渐进:从易到难,稳步提升

刷题就像爬山,得一步一个脚印👣。先从简单的题目入手,比如求数组的和、判断一个数是否为素数这类基础题。把这些简单题做熟了,不仅能建立自信,还能让咱熟悉编程的基本语法和逻辑。等简单题得心应手了,再慢慢增加难度,比如做一些涉及排序算法优化、查找算法应用的题目。面对难题别害怕,就像拆解乐高积木一样,把问题拆分成一个个小问题,逐个击破。每次成功解决一道难题,你会发现自己的编程能力又上了一个台阶。

实战演练:参加竞赛,提升实战能力

如果是在校生,参加算法竞赛对提升大数据刷题能力简直太有帮助了🎉。像 ACM 国际大学生程序设计竞赛、蓝桥杯这些,都是很不错的平台。在竞赛中,你会遇到来自各地的高手,和他们同场竞技,能让你见识到各种巧妙的解题思路和编程技巧。而且竞赛的时间压力很大,能锻炼你在紧张环境下快速思考和编写代码的能力。就像我认识一个学长,参加了几次 ACM 竞赛后,再去面试大数据岗位,那些手撕代码的题目对他来说轻松多了。

合理规划:把握刷题节奏和时间

刷题可不是一蹴而就的事儿,得合理安排时间和节奏🕙。每天刷几道题,这个得根据自己的情况来。要是你时间比较充裕,每天刷 3 – 5 道题也没问题;要是平时学业或者工作忙,每天保证 1 – 2 道题的练习量。别一开始就猛刷,把自己累到了,后面反而坚持不下去。一般来说,先把基础的算法和数据结构题目刷完,再去刷一些综合应用的题目。刷完一本书或者一个阶段的题目后,可以去力扣、牛客网这些平台上找一些大数据专项题目来巩固,刷个 80 – 150 道,基本就差不多了。

效果检验:判断刷题能力是否提升

怎么知道自己刷题有没有效果呢🧐?首先就是看刷题的数量,量变引起质变,刷的题多了,自然会有感觉。但光数量可不够,还得看质量。比如你能不能用多种方法解决同一道题,这说明你对知识点理解得很透彻。还有就是尝试挑战一些难度更高的题目,如果能顺利解决,那能力肯定提升了。另外,刷题平台一般都会给出代码的时间复杂度和空间复杂度分析,看看自己的代码效率有没有提高,这也是检验能力的重要标准。 ​

作者 east
Java, 面试 1月 5,2025

《Java开发及前端最全面试题-Offer直通车》目录

​

 Java开发是需求最广,工资高的细分领域,同时也是会的人最多,有的公司招1个Java开发,要面试几十甚至上百面试者,同时要求很高,要求通晓技术栈很多及很深深度。

如果你正准备面试,想要脱颖而出,那么《Java开发及前端最全面试题-Offer直通车》是你的不二选择。

本书汇集了多篇超过1万字的精华内容,无论是Java基础、数据库、SpringBoot和SpringCloud等必问的,还是前端、k8s等容器和虚拟化和大数据各类技术面试,本书都为你提供了最全面的试题和参考答案。

无论你是Java开发的新手还是有一定经验的老手,本书都能为你提供宝贵的参考和指导。无论你是准备面试还是想要提升自己的技能,本书都能帮助你更好地应对挑战。

现在就加入我们,开启你的大数据面试之旅吧!让《Java开发及前端最全面试题-Offer直通车》成为你的得力助手,助你顺利通过面试,迈向大数据领域的成功之路!

本书共分为以下几个部分:

一、Java语言篇

在Java领域,掌握扎实的Java基础和虚拟机知识是必不可少的。本专栏将为您提供Java虚拟机最全面试题及参考答案,助您轻松应对各种面试挑战。同时,我们还将为您呈现通往大厂Java面试题及参考答案2万字长文,让您在激烈的求职竞争中脱颖而出。

二、数据库及SQL篇

数据库作为软件开发的核心组件,其重要性不言而喻。本专栏将为您带来MySQL、SQL Server、Oracle、PostgreSQL等主流数据库的面试题及参考答案,让您在数据库领域的面试中游刃有余。此外,我们还将提供最全手写SQL面试题及代码实现,让您在SQL技能上更上一层楼。

三、框架篇

在Java开发中,SpringBoot和SpringCloud等框架的使用已成为标配。本专栏将为您带来互联网大厂Spring Cloud面试题及参考答案、Nacos高频面试题及参考答案、Seata面试宝典等一系列精彩内容,助您在框架领域的面试中轻松过关。

四、综合素质篇

除了专业技能外,综合素质也是面试官关注的重点。本专栏将为您提供程序员入职新公司如何快速上手项目的实用建议,让您在试用期也能表现出色。

五、NoSQL篇

随着大数据时代的到来,NoSQL数据库在数据处理中的应用越来越广泛。本专栏将为您带来Elasticsearch、Redis、Pulsar等NoSQL数据库的高频面试题及参考答案,助您在NoSQL领域大展拳脚。

六、容器和虚拟化篇

容器化和虚拟化技术已经成为现代软件开发的重要组成部分。本专栏将为您带来k8s(Kubernetes)、Docker等容器和虚拟化技术的最新面试题及参考答案,让您在这一领域更具竞争力。

七、消息队列篇

消息队列作为异步处理的关键组件,在大型系统中发挥着重要作用。本专栏将为您提供RocketMQ等消息队列的最全面试题详解,助您在消息队列领域的面试中取得成功。

八、前端篇

在前端领域,Angular、React、Vue等框架的使用日益普及。本专栏将为您带来这些框架的必问面试题及参考答案,让您在前端领域的面试中更加自信。

九、其他语言网页开发篇

除了Java外,Python、Django等其他语言和框架也在网页开发中占据一席之地。本专栏将为您带来这些语言和框架的面试题及参考答案,让您在多元化的网页开发领域更具竞争力。

十、大数据篇

在大数据时代背景下,掌握Kafka、Zookeeper等大数据处理技术已成为必备技能。本专栏将为您带来这些技术的面试题及参考答案,助您在大数据领域的面试中脱颖而出。

经验或案例干货篇

软件产品国际化:前后端及App多语言版本解决方案(超详细实现过程)

综合技术面试篇

大厂计算机网络高频八股文面试题及参考答案(面试必问,持续更新)

后端高频考点:网站限流机制与算法详解

Java语言篇

Java虚拟机最全面试题及参考答案

通往大厂Java面试题及参考答案2万字长文

Java多线程和并发编程面试题和参考答案100多道(持续更新)

Java中的Lock、synchronize、CAS关系及其应用场景

 进阶面试题:Java反射机制最全面试题及参考答案

 大厂校招必懂:Java反射机制原理详解(2万字长文)

Java架构师必知的 JVM 调优知识

NIO和Netty保姆级5万字面试题及参考答案

Java设计模式面试题及参考答案

 Java RMI最全面试题及参考答案

 Java中的Lock、synchronize、CAS关系及其应用场景

SpringBoot和SpringCloud

互联网大厂Spring Cloud面试题及参考答案(持续更新)

Nacos高频面试题及参考答案(2万字长文)

Seata面试宝典(2万字长文)

Spring Security高频面试题及参考答案(3万字长文)

Spring Boot过百道保姆级面试题及参考答案(持续更新)

Spring MVC八股文面试题及参考答案(4万字长文)

 精通Spring Cloud: Spring Cloud Config面试题详解及参考答案(3万字长文)

 Eureka从入门到精通面试题及答案参考

Dubbo面试题甄选及参考答案

Spring框架原理面试题及参考答案

数据库及SQL篇

数据库篇

2万字长文:MySQL面试题及参考答案(持续更新)

3万字长文:SQL Server面试题和参考答案(持续更新)

DBA必备知识:Oracle面试题及参考答案(3万字长文)

大厂PostgreSQL面试题100道及参考答案(5万字长文)

MyCat面试题及参考答案(3万字长文)

关系数据库/SQL篇

最全手写SQL面试题及代码实现(万字长文持续更新)

DBA必懂选型:MySQL、PostgreSQL与Oracle对比研究

 后台开发必问题:分库分表面试题及参考答案(3万字长文)

 一文看懂ProxySQL面试题(3万字长文)

ShardingSphere面试题及参考答案(3万字长文)

Mysql海量数据经常有下面这些操作,离被开除就不远了(持续更新)

Oracle Sql查询和性能优化(持续更新)

看完不怕面试官拷打了:mysql sql优化最全总结(万字长文持续更新)

MySQL存储过程原理、实现及优化

DBA必懂选型:MySQL、PostgreSQL与Oracle对比研究

ORM框架

MyBatis-Plus 2万字面试题详解

Hibernate最新6万字面试题及参考答案

Struts2 3万字经典面试题及参考答案

JPA 3万字面试宝典

万字长文看懂MyBatis一二级缓存机制原理、使用和优化

万字长文看懂MyBatis一二级缓存机制原理、使用和优化

框架篇

RxJava最全面试题及参考答案

综合素质篇

做好这些不用担心试用期不通过:程序员入职新公司如何快速上手项目

装上大模型翅膀,程序员入职新公司如何快速上手代码(老员工如何选择大模型编程如虎添翼)

简历面试篇 

技术简历优化秘籍:迈向求职成功第一步

 外企面企必备:IT技术面试英文高频面试题

一文让你不怕Java开发英文自我介绍:英文面试保姆级教程

 如何把自己卖个好价钱:实战面试谈薪水

Nosql篇

Elasticsearch 面试题及参考答案:深入解析与实战应用

大厂Redis高频面试题及参考答案(持续更新)

Pulsar高频面试题及参考答案(持续更新)

通往大厂之路:Solr面试题及参考答案100道题

一文搞懂MongoDB面试题(2万字长文)

Cassandra面试题及答案详解(3万字长文)

时序数据库InfluxDB面试题和参考答案

Lucene最新最全面试题及参考答案

Redis 性能优化策略与实战保姆级教程

Redis如何实现高性能和高可用

万字长文详解缓存穿透、击穿、雪崩现象的原因和解决方案

Lucene最新最全面试题及参考答案

公司篇

字节跳动后端或大数据基础知识面试题及参考答案(2万字长文)

 阿里2024年Java开发最新最全面试题及参考答案(11万长文

 呕血整理4万字长文:百度Java后台开发面试题及参考答案

进BAT必懂:大厂高频八股文面试题及参考答案(6万字长文)

 阿里Java开发社会招聘面试题及参考答案

虾皮Java后台开发校园招聘面试题及参考答案

大厂面试:2024年虾皮Java开发面试题及参考答案(5万字长文)

 希音(Shein)Java后台开发面试题集锦和参考答案

大厂校招:希音(Shein)校园招聘面试题及参考答案

 大厂校招:唯品会Java面试题及参考答案

 4399 Java开发最全面试题及参考答案

 阿里Java开发校园招聘面试题及参考答案

 搜狐畅游Java后台开发面试题大全及参考答案

 搜狐2024年最新Java开发面试题及参考答案

斗鱼Java后端开发面试题及参考答案

金山云Java 开发面试题及参考答案

蔚来Java面试题及参考答案

小马智行Java开发面试题及参考答案

宁德时代Java面试题及参考答案

​​​​​​​

行业篇 

一文吃透物联网(IoT)的面试题及参考答案

物联网(IoT)及物联网网络协议面试题及参考答案(2万字长文)

大厂物联网(IoT)高频面试题及参考答案

容器和虚拟化

k8s(Kubernetes)最新100道面试题及参考答案(5万字长文)

最新5万字长文:Docker 100道面试题及参考答案

分布式篇

分布式架构最新最全面试题及参考答案

数据结构篇

数据结构高频问题:数组与链表的特点、细节及其原理详解

消息队列

最全RocketMQ面试题详解

 ZeroMQ最全面试题解读(3万字长文)

WebSocket面试常见知识点和面试题

3万字长文看透ActiveMQ面试题及参考答案

RabbitMQ 2万字面试题及参考答案

消息队列原理面试题及参考答案

消息代理工具Apollo面试宝典及参考答案

网络及协议篇

网络丢包问题分析和解决最全解析

开发和测试要懂知识:如何进行网络抓包最全宝典

前端篇

Angular必问面试题及参考答案

React常见面试题及参考答案(3万字长文)

JQuery从入门到精通2万字面试题

前端HTML5从入门到精通面试题及参考答案(2万字长文)

Ajax面试题精选及参考答案(3万字长文)

4万字长文让人看懂ElementUI面试题及参考答案

Vue面试题精选大全及参考答案(5万字长文)

前端面试高频知识点:Angular知识点精华汇总

前端Express.js面试题甄选及参考答案

前端UniApp面试题及参考答案(100道题)

Bootstrap最新最全面试题及参考答案

Linux/Shell篇

Linux Shell面试题大全及参考答案(3万字长文)

其它语言网页开发

3万字长文看懂Django面试

CGI面试题及参考答案

大数据篇

Kafka 面试题及参考答案(持续更新)

深入解析Zookeeper面试题及参考答案(超过万字精华篇)

2万字长文:ELK高频面试题及参考答案

ETL利器:Kettle 2万字长文详解面试题

自动化及调度篇

 DevOps在提升软件质量方面的作用

SVN 80道面试题及参考答案(2万字长文)

SonarQube面试题一卷到底60问及参考答案(3万字长文)

 通晓Git操作-git面试题及参考答案

 Jenkins从入门到精通面试题及参考答案(3万字长文)

分布式任务调度XXL – JOB面试题及参考答案

监控篇 

Nagios高频面试题及参考答案(2万字长文)

 Ganglia面试大全及参考答案(2万字长文)

Prometheus面试题精选及参考答案(2万字长文)

测试篇

Java单元测试面试题及参考答案

安全加密篇

 7万字详解Apache Shiro面试题、示例、参考答案

密码学与信息安全面试题及参考答案(2万字长文)

服务器和运维

JBoss面试题精要和参考答案(3万字长文)

 WebLogic面试题精要和参考答案(2万字长文)

WebSphere面试题精选和参考答案(3万字长文)

2.5万字长文吃透Tomcat面试题及参考答案

Nginx面试题精选及参考答案(3万字长文) ​

作者 east
Java, 面试 12月 29,2024

java多线程的实现的各种方法?

​

在 Java 中,实现多线程主要有以下几种方法:

  • 继承 Thread 类:通过创建一个继承自 Thread 类的子类,并重写 run () 方法来定义线程的执行逻辑。然后创建该子类的实例,并调用 start () 方法启动线程。例如,以下代码创建了一个简单的线程类:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • 实现 Runnable 接口:创建一个实现 Runnable 接口的类,并重写 run () 方法。然后创建该类的实例,并将其作为参数传递给 Thread 类的构造函数,最后调用 start () 方法启动线程。例如:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 使用 Callable 和 Future 接口:Callable 接口类似于 Runnable 接口,但它可以返回一个结果。Future 接口用于获取 Callable 任务的结果。可以通过创建一个实现 Callable 接口的类,并重写 call () 方法来定义线程的执行逻辑和返回结果。然后使用 ExecutorService 提交 Callable 任务,并通过 Future 获取任务的结果。例如:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 1;
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable());
        Integer result = future.get();
        System.out.println("结果:" + result);
        executor.shutdown();
    }
}
  • 使用线程池:线程池可以管理和复用线程,减少线程创建和销毁的开销。可以通过使用 ExecutorService 接口和其实现类来创建线程池,并提交任务到线程池中执行。例如:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) { executor.execute(() -> {
System.out.println(“线程正在运行”);
});
}
executor.shutdown();
}
}

作者 east
Java, 面试 12月 29,2024

面试必问题:单例模式的5种写法

​

单例模式主要有以下几种分类:

  • 饿汉式单例:在类加载时就创建单例对象,优点是实现简单,线程安全,缺点是如果单例对象在程序运行过程中一直未被使用,会造成资源浪费。例如:
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
  • 懒汉式单例:在第一次调用 getInstance 方法时才创建单例对象,优点是可以延迟加载,节省资源,但是在多线程环境下需要考虑线程安全问题。例如:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 双重检查锁单例:在懒汉式单例的基础上,通过双重检查锁机制来提高性能并保证线程安全。例如:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类单例:利用静态内部类的特性来实现单例,既保证了线程安全,又实现了延迟加载。例如:

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
  • 枚举单例:通过枚举类型来实现单例,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。例如:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 单例对象的方法
    }
}

在多线程环境下,保证单例模式的线程安全可以采用以下几种方法:

  • 使用 synchronized 关键字:在获取单例对象的方法上添加 synchronized 关键字,如懒汉式单例中的 getInstance 方法,这样可以保证在同一时刻只有一个线程能够访问该方法,从而避免多个线程同时创建单例对象。但是这种方式会降低性能,因为每次调用该方法都需要获取锁。
  • 双重检查锁机制:在懒汉式单例的基础上,通过双重检查锁机制来减少同步的开销。在第一次检查实例是否为 null 时,如果不为 null,则直接返回实例,不需要获取锁;只有在第一次检查为 null 时,才进入同步块再次检查并创建实例。同时,需要将实例变量声明为 volatile,以保证可见性和禁止指令重排序。
  • 静态内部类:利用静态内部类的特性,在类加载时不会立即初始化内部类,只有在第一次调用 getInstance 方法时,才会加载并初始化内部类,从而创建单例对象。由于类加载过程是线程安全的,所以这种方式可以保证单例的线程安全,并且实现了延迟加载。
  • 枚举:枚举类型在 Java 中是天然的单例,因为 Java 虚拟机在加载枚举类时会保证只有一个实例存在,并且枚举类的实例是线程安全的,所以可以通过枚举来实现单例模式,避免了复杂的线程安全处理。

​

作者 east
Java 12月 22,2024

大厂面试手撕面试题:反转链表中前 K 个节点(亲测可用的java实现)

我们需要反转链表中前 k 个节点,且可能会有多个这样的反转段(每 k 个节点为一组)。如果链表的剩余部分不满 k 个节点,我们就不对其进行反转,直接保留原样。

解题思路:

  1. 链表结构:
    • 我们有一个单链表,定义为 ListNode 类型。每个节点包含一个值 val 和指向下一个节点的指针 next。
    • 目标是反转链表中前 k 个节点,并对每一组长度为 k 的节点进行反转,直到链表遍历完。
  2. 关键步骤:
    • 判断当前段是否满 k 个节点: 在反转之前,我们必须确认当前的节点数是否足够 k 个。如果不足,直接跳过该段,保持原链表顺序。
    • 反转当前 k 个节点: 如果当前段正好有 k 个节点,我们就进行反转。
    • 连接反转后的链表: 反转后的链表需要和之前的链表正确连接,以保证链表结构的完整性。
  3. 具体步骤:
    • 维护一个 dummy 虚拟节点,它的 next 指针指向链表头。这样可以避免链表头部的特殊情况处理,使得头节点的反转与其他节点反转保持一致。
    • 使用 prevGroupEnd 指针来标记上一个反转段的最后一个节点,它在反转完成后需要连接到当前反转段的最后一个节点。
    • 对每一组长度为 k 的节点进行反转。具体做法是使用 prev 和 curr 两个指针,通过迭代的方式完成每组的反转。
    • 完成一组反转后,更新 prevGroupEnd 和 head,继续处理下一组。
  4. 终止条件:
    • 当链表遍历完,或者剩下的节点不足 k 个时,停止反转操作。

代码详解:

public class Solution {
    
    public ListNode reverseKGroup(ListNode head, int k) {
        // 如果链表为空或 k 为 1,直接返回头节点
        if (head == null || k == 1) return head;
        
        // 创建一个虚拟节点,用于简化操作
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // prevGroupEnd 用于指向前一组反转后链表的末尾节点
        ListNode prevGroupEnd = dummy;
        
        // 当前头节点 head
        while (head != null) {
            // 找到当前组的最后一个节点
            ListNode groupEnd = head;
            int count = 1;
            // 计算当前组的节点数量
            while (groupEnd != null && count < k) {
                groupEnd = groupEnd.next;
                count++;
            }
            
            // 如果当前组的节点数不足 K 个,不需要反转,直接返回
            if (groupEnd == null) break;
            
            // 反转当前组的节点
            ListNode nextGroupStart = groupEnd.next;  // 保存下一个组的起始节点
            ListNode prev = nextGroupStart;  // prev 初始指向下一组的起始节点
            ListNode curr = head;  // curr 初始指向当前组的头节点
            
            // 反转当前组的节点
            while (curr != nextGroupStart) {
                ListNode temp = curr.next;  // 暂存当前节点的下一个节点
                curr.next = prev;  // 将当前节点的 next 指向 prev(反转)
                prev = curr;  // prev 更新为当前节点
                curr = temp;  // curr 更新为下一个节点
            }
            
            // 将上一组的尾节点连接到当前反转后的组的头节点
            prevGroupEnd.next = groupEnd;
            // 当前组的头节点(反转后)需要指向下一组的起始节点
            head.next = nextGroupStart;
            
            // 更新 prevGroupEnd 和 head,准备处理下一组
            prevGroupEnd = head;
            head = nextGroupStart;
        }
        
        return dummy.next; // 返回新的链表头节点
    }

    // 辅助方法:打印链表
    public void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        
        // 创建一个链表 1 -> 2 -> 3 -> 4 -> 5
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
        
        int k = 3;
        
        // 输出原链表
        System.out.print("Original List: ");
        solution.printList(head);
        
        // 调用反转链表前 K 个节点的方法
        ListNode newHead = solution.reverseKGroup(head, k);
        
        // 输出反转后的链表
        System.out.print("List after reversing first " + k + " nodes: ");
        solution.printList(newHead);
    }
}

详细解析:

  1. 虚拟节点 dummy:
    • dummy 节点的作用是简化链表头部的操作,避免对链表头的特殊处理。通过将 dummy.next 设置为链表的头节点,我们可以统一处理链表的反转和连接。
  2. 变量 prevGroupEnd:
    • prevGroupEnd 用于连接每次反转后组的最后一个节点。初始化时,它指向虚拟节点 dummy。
    • 在每次反转完成后,prevGroupEnd 被更新为当前反转组的头节点 head。
  3. 变量 groupEnd 和 count:
    • groupEnd 用于记录当前反转组的最后一个节点。如果当前组不足 k 个节点,则不进行反转。
    • count 用来计算当前组的节点数。如果 count 达到 k,则进行反转操作。
  4. 反转操作:
    • 反转时,通过三个指针 prev(指向反转后的部分),curr(当前处理的节点),和 temp(暂存当前节点的下一个节点)来完成每一组的节点反转。
    • 完成反转后,将 prevGroupEnd 的 next 指向当前反转组的尾节点 groupEnd,并将 head.next 指向剩余部分的起始节点。
  5. 循环和终止条件:
    • 每次反转 k 个节点后,将 head 更新为剩余部分的起始节点,继续进行下一组的处理。
    • 当剩余节点不足 k 个时,循环终止。

时间复杂度:

  • O(n): 每个节点会被访问并反转一次,总时间复杂度为 O(n),其中 n 是链表的节点数。

空间复杂度:

  • O(1): 只使用了常数的额外空间,除了输入的链表本身。

示例:

对于链表 1 -> 2 -> 3 -> 4 -> 5 和 k = 3,执行反转操作后,链表将变为 3 -> 2 -> 1 -> 4 -> 5。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:删除字符串中的指定字符(亲测可用的java实现)

要求:给定一个字符串 s 和一个字符 t,要求删除字符串中所有出现的字符 t,并返回处理后的新字符串。

Java代码实现:

public class Solution {
    public static String removeCharacter(String s, char t) {
        // 使用 StringBuilder 来构建结果字符串
        StringBuilder result = new StringBuilder();

        // 遍历原字符串,拼接不等于 t 的字符
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != t) {
                result.append(s.charAt(i));
            }
        }

        // 返回结果字符串
        return result.toString();
    }

    public static void main(String[] args) {
        String s = "hello world";
        char t = 'o';
        
        String result = removeCharacter(s, t);
        System.out.println(result); // 输出 "hell wrld"
    }
}

代码解释:

  1. removeCharacter 方法:
    • 输入:一个字符串 s 和一个字符 t。
    • 使用 StringBuilder 来构建新的字符串,这样在处理大量字符时会更加高效。
    • 遍历原字符串 s,将不等于 t 的字符添加到 StringBuilder 中。
    • 最终通过 toString() 返回结果。
  2. main 方法:
    • 示例:给定字符串 "hello world",删除字符 'o' 后,结果是 "hell wrld"。

时间复杂度:

  • 遍历一次字符串,时间复杂度为 O(n),其中 n 是字符串 s 的长度。

空间复杂度:

  • 由于使用了 StringBuilder 来存储新的字符串,空间复杂度为 O(n),其中 n 是字符串 s 的长度。
作者 east
Java, 面试 12月 22,2024

大厂面试手撕面试题:一个字符串中最长的没有重复字符的子串(亲测可用的java实现)

要解决 “求解一个字符串中最长的没有重复字符的子串” 的问题,可以使用 滑动窗口(Sliding Window)算法。这个算法通过维护一个动态窗口来跟踪当前没有重复字符的子串,然后逐步扩展窗口来找到最长的子串。

思路:

  1. 使用两个指针,left 和 right,表示当前窗口的左右边界。
  2. 通过哈希集合(HashSet 或 HashMap)来存储窗口中的字符,确保每个字符在窗口内唯一。
  3. 从左到右遍历字符串,每次右指针right向右移动,检查字符是否已经在窗口中存在。如果存在,就将左指针left向右移动,直到窗口中没有重复字符。
  4. 更新最长子串的长度。

Java实现:

import java.util.HashSet;

public class LongestSubstring {
    public static int lengthOfLongestSubstring(String s) {
        // 哈希集合用于存储窗口中的字符
        HashSet<Character> set = new HashSet<>();
        
        // 定义左右指针和最大长度
        int left = 0;
        int maxLength = 0;
        
        // 右指针向右移动,遍历字符串
        for (int right = 0; right < s.length(); right++) {
            // 如果右指针指向的字符在集合中,说明有重复字符
            // 移动左指针直到去除重复字符
            while (set.contains(s.charAt(right))) {
                set.remove(s.charAt(left));
                left++;
            }
            
            // 将当前字符加入集合
            set.add(s.charAt(right));
            
            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        String s = "abcabcbb";
        System.out.println("The length of the longest substring without repeating characters is: " + lengthOfLongestSubstring(s));
    }
}

解释:

  1. HashSet<Character> set = new HashSet<>();:
    • 用于存储当前窗口内的字符,确保窗口内的字符唯一。
  2. left 和 right 指针:
    • right指针用来遍历字符串。
    • left指针控制窗口的起始位置,确保窗口内没有重复字符。
  3. 滑动窗口的调整:
    • 当遇到重复字符时,移动left指针,直到窗口内不再有重复字符。
    • 每次移动时,都会更新maxLength,保持最大窗口的长度。
  4. 时间复杂度:
    • 最坏情况下,left 和 right 每个指针最多各走一次字符串,因此时间复杂度是 O(n),其中 n 是字符串的长度。

示例输出:

对于输入 "abcabcbb",输出结果是:

The length of the longest substring without repeating characters is: 3 

该子串是 "abc",其长度为 3。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:冒泡排序(亲测可用的java实现)

冒泡排序(Bubble Sort)是一种简单的排序算法。它通过重复遍历待排序的列表,比较相邻元素并交换它们的位置,直到整个列表排序完成。每次遍历都会把当前未排序部分的最大值“冒泡”到数组的最后。

思路:

  1. 从数组的第一个元素开始,逐个比较相邻的元素。
  2. 如果当前元素大于下一个元素,交换它们的位置。
  3. 每一轮遍历后,最大的元素会被“冒泡”到数组的末尾。
  4. 每次遍历的长度逐渐减少,因为已经排序好的元素已经“浮到”数组的末尾。
  5. 直到没有需要交换的元素时,排序完成。

冒泡排序的优化:

  • 如果在某次遍历中没有进行交换,说明数组已经是有序的,可以提前结束排序,避免不必要的遍历。

Java 完整代码实现:

public class BubbleSort {
    public static void main(String[] args) {
        // 测试数组
        int[] arr = {5, 2, 9, 1, 5, 6};
        
        // 调用冒泡排序
        bubbleSort(arr);
        
        // 打印排序后的数组
        System.out.println("Sorted array:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
    
    // 冒泡排序方法
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        
        // 外层循环控制排序的轮数
        for (int i = 0; i < n - 1; i++) {
            boolean swapped = false;  // 标记是否发生了交换
            
            // 内层循环进行相邻元素比较和交换
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true;
                }
            }
            
            // 如果没有发生交换,说明数组已经有序,提前结束
            if (!swapped) {
                break;
            }
        }
    }
}

代码解释:

  1. 主方法 (main):初始化一个整数数组并调用 bubbleSort 方法进行排序。排序后打印数组。
  2. 冒泡排序方法 (bubbleSort):
    • 外层循环控制排序的轮次,每一轮确保将最大的元素“冒泡”到末尾。
    • 内层循环执行相邻元素的比较和交换。如果当前元素比下一个元素大,则交换。
    • swapped 是一个标记,用来检查在某一轮遍历中是否发生了交换。如果某轮没有交换,说明数组已经是有序的,可以提前结束排序。

时间复杂度:

  • 最坏情况下,时间复杂度为 O(n^2),即当数组是逆序时。
  • 最好情况下(当数组已经是有序的),时间复杂度为 O(n),因为只会进行一次遍历且没有发生任何交换。

空间复杂度:

  • O(1),因为该算法是原地排序,不需要额外的空间。
作者 east
Java 12月 22,2024

大厂面试手撕面试题:快速排序(亲测可用的java实现)

快速排序(Quick Sort)是一种常用的排序算法,采用分治法(Divide and Conquer)进行排序。其基本思路是通过选择一个基准元素(pivot),将待排序的数组分成两部分,一部分所有元素都小于基准元素,另一部分所有元素都大于基准元素。然后递归地对这两部分继续进行排序,最终达到排序整个数组的效果。

快速排序的步骤:

  1. 选择基准元素:选择数组中的一个元素作为基准元素(常见的选择有第一个元素、最后一个元素、随机选择等)。
  2. 分区操作:将数组分成两部分,小于基准的放左边,大于基准的放右边。基准元素最终的位置已经确定。
  3. 递归排序:对基准元素左侧和右侧的子数组进行递归调用快速排序,直到子数组的大小为1或0,排序完成。

时间复杂度:

  • 最佳情况:O(n log n),发生在每次分割时都能平衡地分成两部分。
  • 最坏情况:O(n^2),当数组已经有序或反向有序时,每次选择的基准元素都可能是最小或最大的元素,从而导致不均匀的分割。
  • 平均情况:O(n log n),在大多数情况下,快速排序的时间复杂度表现良好。

空间复杂度:

  • 快速排序是原地排序,只需要 O(log n) 的栈空间来存储递归调用的状态。
  • 空间复杂度主要取决于递归的深度,最坏情况下是 O(n),但平均情况下是 O(log n)。

快速排序的Java实现代码:

public class QuickSort {

    // 主函数:调用快速排序
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSortHelper(arr, 0, arr.length - 1);
    }

    // 快速排序的核心递归函数
    private static void quickSortHelper(int[] arr, int left, int right) {
        if (left < right) {
            // 分区操作,返回基准元素的正确位置
            int pivotIndex = partition(arr, left, right);
            // 递归对基准元素左侧和右侧的子数组排序
            quickSortHelper(arr, left, pivotIndex - 1);
            quickSortHelper(arr, pivotIndex + 1, right);
        }
    }

    // 分区操作,返回基准元素的最终位置
    private static int partition(int[] arr, int left, int right) {
        // 选择最右边的元素作为基准元素
        int pivot = arr[right];
        int i = left - 1; // i 指向比基准小的元素区域的最后一个元素
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                // 交换 arr[i + 1] 和 arr[j]
                i++;
                swap(arr, i, j);
            }
        }
        // 将基准元素放到正确位置
        swap(arr, i + 1, right);
        return i + 1; // 返回基准元素的索引
    }

    // 交换数组中两个元素的位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 主函数入口,打印排序后的结果
    public static void main(String[] args) {
        int[] arr = {3, 6, 8, 10, 1, 2, 1};
        System.out.println("Original array: ");
        printArray(arr);
        
        quickSort(arr);

        System.out.println("Sorted array: ");
        printArray(arr);
    }

    // 打印数组的辅助函数
    private static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

代码解析:

  • quickSort:是快速排序的入口方法,接收一个数组作为参数,检查数组是否为空或大小小于2,若满足条件则无需排序,直接返回。
  • quickSortHelper:是一个递归函数,通过分区操作将数组划分为左右两部分,然后递归地对这两部分继续排序。
  • partition:分区操作方法,它选择数组的最后一个元素作为基准元素,并通过两端的指针进行交换,将小于基准的元素放到左边,大于基准的元素放到右边。最终,基准元素被放到正确位置并返回其索引。
  • swap:交换数组中两个元素的辅助函数。
  • printArray:打印数组内容的辅助函数。

复杂度分析:

  • 时间复杂度:
    • 最佳情况:O(n log n),当每次分区都能将数组平衡地分成两部分。
    • 最坏情况:O(n^2),当每次分区都选到数组的极端元素(如最小或最大),导致每次只能减少一个元素的排序,递归的深度为 n。
    • 平均情况:O(n log n),是快速排序最常见的表现。
  • 空间复杂度:
    • 最好和平均情况下:O(log n),由于递归的深度受限于树的高度。
    • 最坏情况下:O(n),当数组已经是排序好的,或者每次选到的基准元素极端时,递归的深度为 n。
作者 east
Java 12月 22,2024

大厂面试手撕面试题:堆排序(亲测可用的java实现)

堆排序(Heap Sort)是一种基于比较的排序算法,利用堆这种数据结构来实现排序。堆是一种完全二叉树,通常用数组来表示。堆排序分为两个步骤:构建最大堆和不断将堆顶元素与末尾元素交换,再进行堆调整。

思路:

  1. 构建最大堆:首先要将数组转化为一个最大堆。最大堆的特点是父节点的值大于等于其左右子节点的值。通过调整从最后一个非叶子节点开始,向上调整整个堆。
  2. 堆排序过程:
    • 将堆顶元素(最大值)与堆的最后一个元素交换。
    • 然后将堆的大小减1,调用堆调整操作(heapify)恢复堆的性质,继续重复上述过程,直到堆的大小为1。

时间复杂度:

  • 构建堆:O(n)。
    • 从最后一个非叶子节点开始调整,每次调整的时间是 O(log n),一共进行 n/2 次调整,因此时间复杂度是 O(n)。
  • 堆排序过程:每次需要调整堆结构,进行 n 次交换,每次调整堆的时间复杂度是 O(log n),所以堆排序的时间复杂度是 O(n log n)。

空间复杂度:

  • 堆排序是原地排序算法,它不需要额外的空间来存储数据。时间复杂度是 O(1)。

Java代码实现:

public class HeapSort {

    // 堆化操作,维护堆的性质
    public static void heapify(int[] arr, int n, int i) {
        int largest = i; // 初始化父节点为最大值
        int left = 2 * i + 1; // 左子节点
        int right = 2 * i + 2; // 右子节点

        // 如果左子节点比父节点大
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 如果右子节点比父节点大
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大值不是父节点,交换并继续堆化
        if (largest != i) {
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;

            // 递归堆化
            heapify(arr, n, largest);
        }
    }

    // 堆排序
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 一个个地把最大元素移动到数组末尾
        for (int i = n - 1; i >= 1; i--) {
            // 将当前堆的根节点与末尾元素交换
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            // 调整堆
            heapify(arr, i, 0);
        }
    }

    // 输出数组
    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    // 主函数
    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};

        System.out.println("Original array:");
        printArray(arr);

        heapSort(arr);

        System.out.println("Sorted array:");
        printArray(arr);
    }
}

代码解析:

  1. heapify:这是一个递归函数,用于调整堆。每次比较父节点与左右子节点的大小,确保父节点是最大值。如果不是,则交换并继续对调整后的子树进行堆化。
  2. heapSort:
    • 第一部分是构建最大堆。从数组的最后一个非叶子节点开始,逐个堆化直到根节点。
    • 第二部分是排序,将堆顶元素与最后一个元素交换,然后调整堆,重复此过程,直到数组完全排序。
  3. printArray:用来输出数组的内容,便于调试和查看结果。

示例输出:

Original array: 12 11 13 5 6 7  
Sorted array: 5 6 7 11 12 13

作者 east
Java 12月 22,2024

大厂面试手撕面试题:归并排序(亲测可用的java实现)

归并排序(Merge Sort)是一种典型的分治算法,它将一个大问题分解成多个小问题,逐步解决,再合并结果,最终得到排序后的序列。

归并排序的思路:

  1. 分解(Divide):将待排序的数组分成两半,递归地对这两半分别进行归并排序。
  2. 解决(Conquer):对分解出的子数组继续进行排序,直到每个子数组只有一个元素(因为一个元素自然是有序的)。
  3. 合并(Combine):将排序后的子数组合并成一个有序的大数组。合并是归并排序的核心步骤。

归并排序的时间复杂度与空间复杂度:

  1. 时间复杂度:
    • 归并排序的时间复杂度是 O(nlogn),无论是最坏情况、最佳情况还是平均情况。这里的 n 是待排序元素的数量,logn 是因为每次递归将数组分成两半,直到数组大小为1。
    • 每一层递归的合并操作是线性时间复杂度O(n),递归深度是logn,所以总的时间复杂度是 O(nlogn)。
  2. 空间复杂度:
    • 归并排序需要额外的空间来存储合并过程中的中间结果,空间复杂度为O(n)。
    • 在递归调用过程中,每次递归调用栈的深度是logn,但归并过程中需要额外的存储空间来存储中间的合并结果,所以总空间复杂度是O(n)。

归并排序的Java实现:

public class MergeSort {
    
    // 归并排序函数
    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // 调用辅助函数进行递归排序
        mergeSortHelper(arr, 0, arr.length - 1);
    }

    // 递归的归并排序方法
    private static void mergeSortHelper(int[] arr, int left, int right) {
        if (left < right) {
            int mid = left + (right - left) / 2;  // 防止溢出
            mergeSortHelper(arr, left, mid);       // 排序左半部分
            mergeSortHelper(arr, mid + 1, right);  // 排序右半部分
            merge(arr, left, mid, right);          // 合并两部分
        }
    }

    // 合并两个有序子数组
    private static void merge(int[] arr, int left, int mid, int right) {
        // 创建临时数组存储合并结果
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;

        // 合并两个有序子数组
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        // 如果左半部分有剩余元素,直接复制到temp数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 如果右半部分有剩余元素,直接复制到temp数组
        while (j <= right) {
            temp[k++] = arr[j++];
        }

        // 将临时数组的内容拷贝回原数组
        System.arraycopy(temp, 0, arr, left, temp.length);
    }

    // 测试归并排序
    public static void main(String[] args) {
        int[] arr = {38, 27, 43, 3, 9, 82, 10};
        System.out.println("排序前: ");
        printArray(arr);

        mergeSort(arr);

        System.out.println("排序后: ");
        printArray(arr);
    }

    // 打印数组
    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

代码解析:

  1. mergeSort 函数:是外部调用的接口,接收待排序的数组。首先检查数组的有效性(如果为空或长度小于2直接返回)。
  2. mergeSortHelper 函数:是递归的核心部分,负责将数组分成两部分,分别递归排序,直到分割到只有一个元素为止。
  3. merge 函数:这是合并的关键部分,它接受三个参数:left, mid, right,分别表示左部分的起始索引,右部分的结束索引。合并操作会创建一个临时数组,按顺序将两个已排序的子数组合并到这个临时数组中,然后再将其复制回原数组。
  4. printArray 函数:打印数组元素的辅助函数。

测试:

给定输入数组 {38, 27, 43, 3, 9, 82, 10},归并排序后的输出应为:

排序前:  38 27 43 3 9 82 10  
排序后: 3 9 10 27 38 43 82
作者 east

1 2 … 6 下一个

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?
  • C++ 中避免悬挂引用的企业策略有哪些?
  • 嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
  • C++如何在插件式架构中使用反射实现模块隔离?
  • C++如何追踪内存泄漏(valgrind/ASan等)并定位到业务代码?
  • C++大型系统中如何组织头文件和依赖树?

文章归档

  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (43)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (4)
  • 大数据开发 (488)
    • CDH (6)
    • datax (4)
    • doris (30)
    • Elasticsearch (15)
    • Flink (78)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (40)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (99)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (34)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (70)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (6)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.