判题机的开发(代码沙箱、三种模式、工厂模式、策略模式优化、代理模式)

判题机模块预开发

  1. 梳理判题模块和代码沙箱的关系
    判题模块:调用代码沙箱,把代码和输入交给代码沙箱去执行。
    代码沙箱:只负责接收代码和输入,返回编译运行的结果,不负责判题。(可以作为独立的项目/服务,提供给其他的需要执行代码的项目去使用)
    这两个模块完全解耦。
    思考:为什么代码沙箱要接受和输出一组运行用例?
    每道题有多组用例,如果每条用例都单独调用一次代码沙箱,会调用多个接口、需要多次网络传输、程序要多次编译、记录程序的执行状态(重复代码不重复编译)

代码沙箱开发

  1. 定义代码沙箱的接口,提高通用性(之后我们的项目代码只调用接口,不调用具体实现类,这样在调用其他代码沙箱实现类,就不用去修改名称了,便于扩展)
    扩展思路:代码可以增加一个查看代码沙箱状态的接口
  2. 定义多种不同代码沙箱实现:
    示例代码沙箱、远程代码沙箱、第三方代码沙箱

LomBok Builder注解:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
  1. 编写单元测试,验证单个代码沙箱的执行

    @Test
    void executeCode() {
    CodeSandbox codeSandbox=new ExampleCodeSandbox();
    String userCode="int main(){}";
    String codeLanguage= QuestionSubmitLanguageEnum.JAVA.getValue();
    List<String>inputList= Arrays.asList("1 2","2 3");
    ExecuteCodeRequest executeCodeRequest= ExecuteCodeRequest.builder()
    .userCode(userCode)
    .codeLanguage(codeLanguage)
    .inputList(inputList)
    .build();
    ExecuteCodeResponse executeCodeResponse=codeSandbox.executeCode(executeCodeRequest);
    Assertions.assertNotNull(executeCodeResponse);
    }

存在问题:new某个沙箱代码写死了,若要改用其他沙箱,需要改动很多处代码

  1. 使用工厂模式,根据用户传入的字符串参数来生成对应代码沙箱实现类

    public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    while (scanner.hasNext()) {
    String type = scanner.next();
    CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(type);
    String userCode = "int main(){}";
    String codeLanguage = QuestionSubmitLanguageEnum.JAVA.getValue();
    List<String> inputList = Arrays.asList("1 2", "2 3");
    ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
    .userCode(userCode)
    .codeLanguage(codeLanguage)
    .inputList(inputList)
    .build();
    ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
    }
    }

只需要根据字符串来判断然后生成沙箱,无需再手动创建。

  1. 参数配置化,把项目中一些可选项交给用户去自定义选项或字符串,写到配置文件中。这样只需改变配置文件,而无需看代码内容,就可以更方便自由的自定义使用项目更多功能。

    #代码沙箱配置
    codesandbox:
    type: example

    @Value("${codesandbox.type}")
    private String type;

  2. 代码沙箱能力增强:比如在调用代码沙箱前,输出请求参数;在代码沙箱调用后,输出响应结果日志,便于管理员分析
    ------>使用代理模式,提供一个Proxy,来增强代码沙箱的能力
    原本需要用户自己多次调用日志,使用代理后,调用者只需要调用代理类,代理类调用沙箱类(代理类可以完成一些额外的功能)。优点:不仅不需要改变原本沙箱,对调用者来说,调用方式几乎没有改变,无需在每个调用沙箱的代码上方再去调用日志

    @Slf4j
    public class CodeSandboxProxy implements CodeSandbox{
    private final CodeSandbox codeSandbox;
    public CodeSandboxProxy(CodeSandbox codeSandbox){
    this.codeSandbox=codeSandbox;
    }
    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
    log.info("请求信息"+executeCodeRequest.toString());
    ExecuteCodeResponse executeCodeResponse= codeSandbox.executeCode(executeCodeRequest);
    log.info("响应信息"+executeCodeResponse.toString());
    return executeCodeResponse;
    }
    }

    CodeSandbox codeSandbox=CodeSandboxFactory.newInstance(type);
    codeSandbox=new CodeSandboxProxy(codeSandbox);

    /**

    • 示例代码沙箱(单纯跑通业务流程)
      */
      public class ExampleCodeSandbox implements CodeSandbox {
      @Override
      public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
      List<String> inputList = executeCodeRequest.getInputList();
      ExecuteCodeResponse executeCodeResponse=new ExecuteCodeResponse();
      executeCodeResponse.setOutputList(inputList);
      executeCodeResponse.setMessage("测试成功!");
      executeCodeResponse.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
      JudgeInfo judgeInfo=new JudgeInfo();
      judgeInfo.setTime(100L);
      judgeInfo.setMemory(100L);
      judgeInfo.setMessage(JudgeInfoMessageEnum.ACCEPTED.getText());
      executeCodeResponse.setJudgeInfo(judgeInfo);
      return executeCodeResponse;
      }
      }

判题服务完整业务流程实现

判题服务业务流程

  1. 获取题目id,获取对应题目提交信息(代码,编程语言)

  2. 如果提交状态不为等待中就不用重复执行

  3. 更改题目提交状态"判题中",防止重复执行,也能让用户看到判题状态

  4. 调用沙箱,获取执行结果

  5. 根据执行结果,设置题目判题状态和信息
    判断逻辑:1、先判断沙箱执行的结果输出数量是否和预期数量相等;2、判断每一项输出和预期输出是否相等;3、判断题目的限制是否符合要求;4、还可能有其他异常情况

    @Service
    public class JudegeServiceImpl implements JudegeService {
    @Value("${codesandbox.type}")
    private String type;
    @Resource
    private QuestionService questionService;
    @Resource
    private QuestionSubmitService questionSubmitService;
    /**
    * 1. 获取题目id,获取对应题目提交信息(代码,编程语言)
    * 2. 如果提交状态不为等待中就不用重复执行
    * 3. 更改题目提交状态"判题中",防止重复执行,也能让用户看到判题状态
    * 4. 调用沙箱,获取执行结果
    * 5. 根据执行结果,设置题目判题状态和信息
    * @param questionSubmitId
    * @return
    */
    @Override
    public QuestionSubmitVO doJudege(long questionSubmitId) {
    // 1. 获取题目id,获取对应题目提交信息(代码,编程语言)
    QuestionSubmit questionSubmit=questionSubmitService.getById(questionSubmitId);
    if(questionSubmit==null){
    throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"提交信息不存在!");
    }
    Long questionId = questionSubmit.getQuestionId();
    Question question=questionService.getById(questionId);
    if(question==null){
    throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"题目不存在!");
    }
    // 2. 如果提交状态不为等待中就不用重复执行
    if(!questionSubmit.getCodeStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())){
    throw new BusinessException(ErrorCode.OPERATION_ERROR,"正在判题中!");
    }
    //3. 更改题目提交状态"判题中",防止重复执行,也能让用户看到判题状态
    QuestionSubmit questionSubmitUpdate=new QuestionSubmit();
    questionSubmitUpdate.setId(questionSubmitId);
    questionSubmitUpdate.setCodeStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
    boolean update=questionSubmitService.updateById(questionSubmitUpdate);
    if(!update){
    throw new BusinessException(ErrorCode.SYSTEM_ERROR,"题目状态更新错误!");
    }
    // 4. 调用沙箱,获取执行结果
    CodeSandbox codeSandbox= CodeSandboxFactory.newInstance(type);
    codeSandbox=new CodeSandboxProxy(codeSandbox);
    String codeLanguage = questionSubmit.getCodeLanguage();
    String userCode = questionSubmit.getUserCode();
    //获取输入用例
    String judegeCaseStr=question.getJudgeCase();
    List<JudgeCase>judgeCaseList=JSONUtil.toList(judegeCaseStr, JudgeCase.class);
    List<String>inputList=judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());
    ExecuteCodeRequest executeCodeRequest= ExecuteCodeRequest.builder()
    .userCode(userCode)
    .codeLanguage(codeLanguage)
    .inputList(inputList)
    .build();
    ExecuteCodeResponse executeCodeResponse=codeSandbox.executeCode(executeCodeRequest);

    // 5. 根据执行结果,设置题目判题状态和信息
    JudgeInfoMessageEnum judgeInfoMessageEnum=JudgeInfoMessageEnum.WAITING;
    List<String> outputList = executeCodeResponse.getOutputList();
    // 依次判断每一项输出和预期是否相等
    if(outputList.size()!=inputList.size()){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.WRONG_ANSWER;
    return null;
    }
    for (int i=0;i<judgeCaseList.size();i++){
    if(!judgeCaseList.get(i).getOutput().equals(outputList.get(i))){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.WRONG_ANSWER;
    return null;
    }
    }
    //判断题目限制
    JudgeInfo judgeInfo = executeCodeResponse.getJudgeInfo();
    Long time = judgeInfo.getTime();
    Long memory = judgeInfo.getMemory();
    String judgeConfigStr = question.getJudgeConfig();
    JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);
    Long needMemoryLimit = judgeConfig.getMemoryLimit();
    Long needTimeLimit= judgeConfig.getTimeLimit();
    if(memory>needMemoryLimit){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED;
    return null;
    }
    if(time>needTimeLimit){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED;
    return null;
    }
    return null;
    }
    }


策略模式

思考:我们的代码沙箱本身执行时间,对于不同的编程语言是不同的。所以我们可以采用策略模式。针对不同的情况,定义独立的策略,而不是把所有的判题逻辑全部混在一起。

首先编写默认判题模块,如果所有的选择判题策略都写在判题服务代码中,代码会过于复杂,产生很多if-else,建议单独编写判断策略的方法。-->定义JudgeManager,尽量简化对判题功能的调用,

/**
 * 判题管理,简化调用
 *
 * @Author Adellle
 * @Date 2024/12/18 19:44
 * @Version 1.0
 */
 @Service
public class JudgeManager {
    JudgeInfo doJudge(JudgeContext judgeContext) {
        QuestionSubmit questionSubmit = judgeContext.getQuestionSubmit();
        String codeLanguage = questionSubmit.getCodeLanguage();
        JudgeStrategy judgeStrategy = new DefaultJudgeStrategy();
        if ("java".equals(codeLanguage)) {
            judgeStrategy = new JavaLanguageDefaultJudgeStrategy();
        }
        return judgeStrategy.doJudge(judgeContext);
    }
}

代码逻辑梳理

 @Resource
    @Lazy
    private JudgeService judgeService;

    /**
     * 提交题目
     *
     * @param questionSubmitAddRequest
     * @param loginUser
     * @return
     */
    @Override
    public long doQuestionSubmit(QuestionSubmitAddRequest questionSubmitAddRequest, User loginUser) {
        // 校验编程语言是否合法(校验合法性)
        String language = questionSubmitAddRequest.getCodeLanguage();
        QuestionSubmitLanguageEnum languageEnum = QuestionSubmitLanguageEnum.getEnumByValue(language);
        if (languageEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "编程语言错误");
        }
        Long questionId = questionSubmitAddRequest.getQuestionId();
        // 判断实体是否存在,根据类别获取实体
        Question question = questionService.getById(questionId);
        if (question == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        // 是否已提交题目
        long userId = loginUser.getId();
        // 每个用户串行提交题目
        QuestionSubmit questionSubmit = new QuestionSubmit();
        questionSubmit.setUserId(userId);
        questionSubmit.setQuestionId(questionId);
        questionSubmit.setUserCode(questionSubmitAddRequest.getUserCode());
        questionSubmit.setCodeLanguage(questionSubmitAddRequest.getCodeLanguage());
        // 设置初始状态
        questionSubmit.setCodeStatus(QuestionSubmitStatusEnum.WAITING.getValue());
        questionSubmit.setJudgeInfo("{}");
        boolean save = this.save(questionSubmit);
        if (!save) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "插入数据失败!");
        }
        long questionSubmitId=questionSubmit.getId();
        //  (执行判题服务(异步执行))
        //作用:调用 judgeService.doJudege 方法来执行判题操作,使用CompletableFuture.runAsync() 来异步执行判题任务。
        CompletableFuture.runAsync(() -> {
            judgeService.doJudege(questionSubmitId);
        });
        return questionSubmitId;
    }

注:CompletableFuture.runAsync 会在独立的线程中异步执行判题操作,不会阻塞主线程。需要确保 judgeService.doJudege(questionSubmitId) 方法能够正确处理判题逻辑,并且判题是一个耗时操作,因此采用了异步方式。这里使用异步执行来提高系统性能,避免主线程被阻塞,确保用户能够快速得到提交的反馈。

开始判题

public interface JudgeService {
    /**
     * 判题服务
     *
     * @param questionSubmitId
     * @return
     */
    QuestionSubmit doJudege(long questionSubmitId);
}

@Service
public class JudgeServiceImpl implements JudgeService {
    @Value("${codesandbox.type}")
    private String type;
    @Resource
    private QuestionService questionService;
    @Resource
    private QuestionSubmitService questionSubmitService;
    @Resource
    private JudgeManager judgeManager;

    /**
     * 1. 获取题目id,获取对应题目提交信息(代码,编程语言)
     * 2. 如果提交状态不为等待中就不用重复执行
     * 3. 更改题目提交状态"判题中",防止重复执行,也能让用户看到判题状态
     * 4. 调用沙箱,获取执行结果
     * 5. 根据执行结果,设置题目判题状态和信息
     *
     * @param questionSubmitId
     * @return
     */
    @Override
    public QuestionSubmit doJudege(long questionSubmitId) {
//        1. 获取题目id,获取对应题目提交信息(代码,编程语言)
        QuestionSubmit questionSubmit = questionSubmitService.getById(questionSubmitId);
        if (questionSubmit == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "提交信息不存在!");
        }
        Long questionId = questionSubmit.getQuestionId();
        Question question = questionService.getById(questionId);
        if (question == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "题目不存在!");
        }
//        2. 如果提交状态不为等待中就不用重复执行
        if (!questionSubmit.getCodeStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "正在判题中!");
        }
        //3. 更改题目提交状态"判题中",防止重复执行,也能让用户看到判题状态
        QuestionSubmit questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmitId);
        questionSubmitUpdate.setCodeStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        boolean update = questionSubmitService.updateById(questionSubmitUpdate);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误!");
        }
//        4. 调用沙箱,获取执行结果
        CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(type);
        codeSandbox = new CodeSandboxProxy(codeSandbox);
        String codeLanguage = questionSubmit.getCodeLanguage();
        String userCode = questionSubmit.getUserCode();
        //获取输入用例
        String judegeCaseStr = question.getJudgeCase();
        List<JudgeCase> judgeCaseList = JSONUtil.toList(judegeCaseStr, JudgeCase.class);
        List<String> inputList = judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());
        ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
                .userCode(userCode)
                .codeLanguage(codeLanguage)
                .inputList(inputList)
                .build();
        ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
        List<String> outputList = executeCodeResponse.getOutputList();
//        5. 根据执行结果,设置题目判题状态和信息
        JudgeContext judgeContext = new JudgeContext();
        judgeContext.setJudgeInfo(executeCodeResponse.getJudgeInfo());
        judgeContext.setInputList(inputList);
        judgeContext.setOutputList(outputList);
        judgeContext.setQuestion(question);
        judgeContext.setJudgeCaseList(judgeCaseList);
        judgeContext.setQuestionSubmit(questionSubmit);
        JudgeInfo judgeInfo = judgeManager.doJudge(judgeContext);
        //6. 修改数据库中的判题结果
        questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmitId);
        questionSubmitUpdate.setCodeStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        questionSubmitUpdate.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        update = questionSubmitService.updateById(questionSubmitUpdate);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误!");
        }
        QuestionSubmit res = questionSubmitService.getById(questionId);
        return res;
    }
}
  • 使用策略模式,不把所有的if-else都放在判题服务中,减少对判题功能的调用

    @Service
    public class JudgeManager {
    JudgeInfo doJudge(JudgeContext judgeContext) {
    QuestionSubmit questionSubmit = judgeContext.getQuestionSubmit();
    String codeLanguage = questionSubmit.getCodeLanguage();
    JudgeStrategy judgeStrategy = new DefaultJudgeStrategy();
    if ("java".equals(codeLanguage)) {
    judgeStrategy = new JavaLanguageDefaultJudgeStrategy();
    }
    return judgeStrategy.doJudge(judgeContext);
    }
    }

    public class DefaultJudgeStrategy implements JudgeStrategy {

      @Override
      public JudgeInfo doJudge(JudgeContext judgeContext) {
          JudgeInfo judgeInfo = judgeContext.getJudgeInfo();
          Long time = judgeInfo.getTime();
          Long memory = judgeInfo.getMemory();
          JudgeInfo judgeInfoResponse=new JudgeInfo();
          judgeInfoResponse.setTime(time);
          judgeInfoResponse.setMemory(memory);
          List<String> inputList = judgeContext.getInputList();
          List<String> outputList = judgeContext.getOutputList();
          Question question = judgeContext.getQuestion();
          List<JudgeCase> judgeCaseList = judgeContext.getJudgeCaseList();
          JudgeInfoMessageEnum judgeInfoMessageEnum=JudgeInfoMessageEnum.ACCEPTED;
    

    // 依次判断每一项输出和预期是否相等
    if(outputList.size()!=inputList.size()){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.WRONG_ANSWER;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    for (int i=0;i<judgeCaseList.size();i++){
    if(!judgeCaseList.get(i).getOutput().equals(outputList.get(i))){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.WRONG_ANSWER;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    }
    //判断题目限制
    String judgeConfigStr = question.getJudgeConfig();
    JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);
    Long needMemoryLimit = judgeConfig.getMemoryLimit();
    Long needTimeLimit= judgeConfig.getTimeLimit();
    if(memory>needMemoryLimit){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    if(time>needTimeLimit){
    judgeInfoMessageEnum=JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;

      }
    

    }

    public class JavaLanguageDefaultJudgeStrategy implements JudgeStrategy {

      @Override
      public JudgeInfo doJudge(JudgeContext judgeContext) {
          JudgeInfo judgeInfo = judgeContext.getJudgeInfo();
          Long time = judgeInfo.getTime();
          Long memory = judgeInfo.getMemory();
          JudgeInfo judgeInfoResponse = new JudgeInfo();
          judgeInfoResponse.setTime(time);
          judgeInfoResponse.setMemory(memory);
          List<String> inputList = judgeContext.getInputList();
          List<String> outputList = judgeContext.getOutputList();
          Question question = judgeContext.getQuestion();
          List<JudgeCase> judgeCaseList = judgeContext.getJudgeCaseList();
          JudgeInfoMessageEnum judgeInfoMessageEnum = JudgeInfoMessageEnum.ACCEPTED;
    

    // 依次判断每一项输出和预期是否相等
    if (outputList.size() != inputList.size()) {
    judgeInfoMessageEnum = JudgeInfoMessageEnum.WRONG_ANSWER;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    for (int i = 0; i < judgeCaseList.size(); i++) {
    if (!judgeCaseList.get(i).getOutput().equals(outputList.get(i))) {
    judgeInfoMessageEnum = JudgeInfoMessageEnum.WRONG_ANSWER;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    }
    //判断题目限制
    String judgeConfigStr = question.getJudgeConfig();
    JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);
    Long needMemoryLimit = judgeConfig.getMemoryLimit();
    Long needTimeLimit = judgeConfig.getTimeLimit();
    if (memory > needMemoryLimit) {
    judgeInfoMessageEnum = JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    //Java程序本身需要额外执行10s
    long JAVA_PROGRAM_TIME_COST = 10000L;
    if ((time - JAVA_PROGRAM_TIME_COST) > needTimeLimit) {
    judgeInfoMessageEnum = JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED;
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());
    return judgeInfoResponse;
    }
    }

相关推荐
Qzer_4079 分钟前
在JVM(Java虚拟机)中,PC寄存器(Program Counter Register)扮演着至关重要的角色,它是JVM执行引擎的核心组成部分之一。
java·开发语言·jvm
星沁城11 分钟前
JVM的垃圾回收机制
java·开发语言·jvm
FF在路上19 分钟前
MybatisPlus使用LambdaQueryWrapper更新时 int默认值问题
java·开发语言·mybatis
gb421528729 分钟前
java中sha256和md5某个字符串实例代码
java·开发语言·哈希算法
三十六煩惱風30 分钟前
Java中常见的锁及其应用场景
java
二闹1 小时前
青训营试题算法解析十九
后端·算法
陈沧夜1 小时前
【openssl】 version `OPENSSL_3.0.3‘ not found 问题
后端·中间件
Watermelon_Mr1 小时前
Spring(二)AOP、切入点表达式、AspecJ常用通知的类型、Spring中的事务管理
java·后端·spring
出发行进1 小时前
Maven的介绍以及安装,仓库的使用和在idea使用maven
java·大数据·数据分析·maven
zhrb1 小时前
Maven简要使用说明:在IDEA中创建一个基于POI的处理Excel文件的简单Java Maven项目...
java·ide·maven·intellij-idea·excel