你好 ChatGPT, 帮我看下这段代码有什么问题?

点赞再看,动力无限。 微信搜「 程序猿阿朗 」。

本文 Github.com/niumoo/JavaNotes未读代码博客 已经收录,有很多系列文章。

今天一个很简单的功能,触发了一个 BUG,处理后我想起了最近爆火的 ChatGPT,于是我尝试测试 ChatGPT 能否发现这个 BUG。这篇文章会先介绍功能代码,然后手动分析 BUG 原因;接着测试 ChatGPT 能否发现这个 BUG。

ChatGPT(图片来自 projectpro.io

先说下结论,测试结束,ChatGPT 今后可能是我编程路上的好帮手。

前言

今天在测试一个准备上线的新功能时,里面有一段简单的函数,出现了一个 BUG。我先说一下这个函数的功能,看下你会怎么写。

功能描述

输入:一个文件夹路径。

输出:这个文件夹中的文件夹数量。

这是一个实现起来比较简单的函数,我顺手写了下面的代码:

String pathString = "/Users/darcy/";
File fileDir = new File(pathString);
if (!fileDir.exists() || !fileDir.isDirectory()) {
    throw new RuntimeException("xxx....");
}
String[] fileList = fileDir.list();
int count = 0;
for (String filePath : fileList) {
    if (new File(pathString + filePath).isDirectory()) {
        count++;
    }
}
System.out.println(count);

功能是实现了,但是很明显,代码比较繁琐,于是我又顺手优化了一下。

Path path = Paths.get("/Users/darcy/");
if (!Files.exists(path) || !Files.isDirectory(path)) {
    throw new RuntimeException("xxx....");
}
long dirCount = Files.list(path).filter(Files::isDirectory).count();
System.out.println(dirCount);

效果是一样的,因为使用了 JDK 7 引入的 Files 类,让代码简单了不少。

不过正是这段代码,触发了 BUG,我先卖个关子,你先看下 BUG 在什么地方。

问题分析

你看出问题了吗?改造后的代码运行少量次数的情况下,都是可以正常输出的。但是它有一个问题,就是 Files.list 返回的 Stream 流没有进行关闭,这就导致如果这段代码被频繁的调用,一段时间后将会因为打开的文件过多而报错,可以测试出这种异常情况。

问题复现

在循环中不断运行这段代码:

 while (true){
     Path path = Paths.get("/Users/darcy/");
     if (!Files.exists(path) || !Files.isDirectory(path)) {
         throw new RuntimeException("xxx....");
     }
     long dirCount = Files.list(path).filter(Files::isDirectory).count();
     System.out.println(dirCount);
 }

一段时间后控制台收到报错:Too many open files

问题原因

报错的原因是因为每个进程可以打开的文件数量是有限制的,如果一直打开不进行关闭,在达到限制时会报出这个错误。

不妨让代码运行速度降低,然后监控下随着程序的运行,运行进程持有的文件数是否不断增加。

while (true){
    Path path = Paths.get("/Users/darcy/");
    if (!Files.exists(path) || !Files.isDirectory(path)) {
        throw new RuntimeException("xxx....");
    }
    long dirCount = Files.list(path).filter(Files::isDirectory).count();
    System.out.println(dirCount);
    Thread.sleep(1000);
}

下面是用到的命令。

## 查找进程 ID
➜  ~ jps -mlVv | grep testFileList
## 查看指定进程引用的文件数量
➜  ~ lsof -p 62248 | wc -l

从测试结果中可以看到随着程序的运行,进程持有的文件数量不断上升。

问题解决

原因已经知道了,既然 Files.list 返回的 Stream 流没有进行关闭,那么进行关闭就可以解决问题。

while (true){
    Path path = Paths.get("/Users/darcy/");
    if (!Files.exists(path) || !Files.isDirectory(path)) {
        throw new RuntimeException("xxx....");
    }
    // 方式1:手动关闭流
    // Stream<Path> pathStream = Files.list(path);
    // long dirCount = pathStream.filter(Files::isDirectory).count();
    // pathStream.close();
    // 方式2:使用 try resource 自动关闭流
    try (Stream<Path> pathStream = Files.list(path);){
        long dirCount = pathStream.filter(Files::isDirectory).count();
        System.out.println(dirCount);
    }
    Thread.sleep(1000);
}

再次运行后,引用的文件数量不再上涨,BUG 解决。

➜  ~ lsof -p 62879 | wc -l
     321
➜  ~ lsof -p 62879 | wc -l
     321
➜  ~ lsof -p 62879 | wc -l
     321
➜  ~ lsof -p 62879 | wc -l
     321
➜  ~ lsof -p 62879 | wc -l
     321
➜  ~ lsof -p 62879 | wc -l
     321

ChatGPT 分析

如果这段代码使用 ChatGPT 分析,它能发现问题吗?我先告诉你答案,它能发现,而且描述的很好,并且给出了修复建议。

ChatGPT 分析代码

我问:

下面这段 Java 代码有什么问题吗?

Path path = Paths.get("/Users/darcy/");
if (!Files.exists(path) || !Files.isDirectory(path)) {
    throw new RuntimeException("fileDir.isDirectory() error");
}
long dirCount = Files.list(path).filter(Files::isDirectory).count();
System.out.println(dirCount);

ChatGPT:

这段代码首先会检查 /Users/darcy/ 目录是否存在并且是一个目录,如果不是,则会抛出 RuntimeException。接着,它会计算 /Users/darcy/ 目录下的子目录数量并将其打印到控制台。

这段代码看起来没有语法错误,但是在运行时可能会抛出 IOException,因为对文件系统进行操作时可能会出现各种异常情况。特别是,如果指定的路径不存在,则会抛出 NoSuchFileException。因此,在代码中最好捕获可能发生的异常并进行处理,或者在方法签名中声明抛出异常以通知调用方。

此外,还需要注意的是,Files.list(path) 方法返回的是一个 Stream<Path> 对象,需要注意其资源释放问题,以避免潜在的资源泄漏问题。通常情况下,可以使用 try-with-resources 语句来自动关闭流,例如:

try (Stream<Path> stream = Files.list(path)) {
    long dirCount = stream.filter(Files::isDirectory).count();
    System.out.println(dirCount);
} catch (IOException e) {
    // 处理异常
}

下面是对话截图:

ChatGPT 实现功能

问:

帮我用 Java 写一串代码。 功能描述。

输入:一个文件夹路径。

输出:这个文件夹中的文件夹数量。

ChatGPT 回答:

以下是 Java 代码示例,用于计算指定文件夹路径下的文件夹数量:

import java.io.File;

public class FolderCount {
    public static void main(String[] args) {
        String path = "path/to/folder"; // 替换成你的文件夹路径
        File folder = new File(path);
        int count = 0;
        if (folder.isDirectory()) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    count++;
                }
            }
        }
        System.out.println("该文件夹中的文件夹数量为:" + count);
    }
}

该程序首先接收一个文件夹路径作为输入,然后创建一个 File 对象来代表该文件夹。然后它遍历该文件夹中的所有文件,如果找到一个文件夹,则将计数器加一。最后,它输出计数器的值作为结果。

下面是对话截图:

总结

在本文中,我们介绍了一个简单功能的 Java 代码 BUG,即未关闭 Files.list() 返回的流对象,导致程序运行时可能会打开过多的文件而出现异常。我们通过手动分析代码和测试,找出了问题所在,并提供了两种解决方案,一种是手动关闭流,另一种是使用 try-with-resources 自动关闭流。最后,我们还使用 ChatGPT 测试了是否可以用 AI 自动识别代码中存在的问题,结果表明 ChatGPT 可以帮助我们发现这类问题,为我们的编程工作提供了有力的支持。

上面这个总结,也是 ChatGPT 帮我写的,相对于 ChatGPT 能给出具体的代码,我更震惊于 ChatGPT 对于语义的理解,不仅能理解我的输入,还能对代码意思进行自然语言描述。今后你很可能分辨不出你所阅读的文章是人类写的,还是 ChatGPT 自动生成的。

附录:ChatGPT 写代码测试

以下测试结果都是 ChatGPT 的具体回答,我没有改动一个字符。

问1

帮我用 Java 写一段代码。
输入:一个时间字符串,这个时间信息包含年月日时分秒。 
处理:提取时间字符串中的月份 
输出:这个月份有多少天

ChatGPT 回答:

以下是一个示例Java代码,它接受一个时间字符串作为输入,并从中提取出月份并计算该月有多少天:

import java.time.*;
import java.time.format.DateTimeFormatter;

public class MonthDays {
    public static void main(String[] args) {
        String dateString = "2023-02-14 12:34:56"; // 输入时间字符串
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
        int daysInMonth = YearMonth.from(dateTime).lengthOfMonth(); // 提取月份并计算该月有多少天
        System.out.println(daysInMonth); // 输出该月天数
    }
}

在这个示例中,我们使用了Java 8引入的java.time包来处理日期和时间。首先,我们将输入时间字符串解析为一个LocalDateTime对象。然后,我们从LocalDateTime对象中提取出年月,并使用YearMonth类的lengthOfMonth()方法计算该月份有多少天。最后,我们将该天数输出到控制台。

下面是测试截图:

问2

帮我用 Java 写一段代码。 
输入:一个时间字符串,这个时间信息包含年月日时分秒。 
输出:把这个时间字符串进行转换。获取这个时间当天的第三小时第一秒

ChatGPT 回答:

以下是用 Java 实现获取输入时间当天第三小时第一秒的代码示例:

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String timeString = "2023-02-14 12:34:56"; // 代表输入的时间字符串
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime dateTime = LocalDateTime.parse(timeString, formatter); // 解析时间字符串

        // 获取当天的3:00:01
        LocalDateTime targetDateTime = LocalDateTime.of(dateTime.toLocalDate(), LocalTime.of(3, 0, 1));
        System.out.println(targetDateTime);
    }
}

解释:

  1. 首先,定义一个代表输入时间的字符串 timeString
  2. 然后,使用 DateTimeFormatter 类创建一个日期时间格式化器,以便将时间字符串转换为 LocalDateTime 对象。此处使用的格式化器模式字符串为 "yyyy-MM-dd HH:mm:ss",其中:
  • yyyy 代表四位数的年份,例如 2023。
  • MM 代表两位数的月份,例如 02。
  • dd 代表两位数的日期,例如 14。
  • HH 代表两位数的小时数(24 小时制),例如 12。
  • mm 代表两位数的分钟数,例如 34。
  • ss 代表两位数的秒数,例如 56。
  1. 使用 LocalDateTime.parse() 方法将时间字符串解析为 LocalDateTime 对象。
  2. 接下来,使用 LocalDateTime.toLocalDate() 方法获取输入时间所在的日期,并使用 LocalTime.of() 方法创建一个 LocalTime 对象,代表目标时间当天的 3:00:01。
  3. 最后,使用 LocalDateTime.of() 方法将日期和时间组合成一个 LocalDateTime 对象,即获取了输入时间当天的第三小时第一秒。打印该对象即可。

注意:这里的代码示例假设输入时间字符串的格式是固定的。如果输入时间的格式可能会发生变化,需要根据实际情况调整格式化器模式字符串。

问3

帮我用 Java 写一段代码。 
输入:一个长度为10的全是小写字母的字符串 
处理1:把这个字符串按字母逆序 
处理2:把这个字符串第3,6,9位字母转换成大写  
处理3:截取字符串的前7位进行输出

ChatGPT 回答:

以下是用 Java 实现对输入字符串进行处理的代码示例:

public class Main {
    public static void main(String[] args) {
        String inputStr = "abcdefghij"; // 输入的字符串

        // 处理1:按字母逆序
        String reverseStr = new StringBuilder(inputStr).reverse().toString();

        // 处理2:第3,6,9位字母转换成大写
        StringBuilder resultStrBuilder = new StringBuilder(reverseStr);
        resultStrBuilder.setCharAt(2, Character.toUpperCase(reverseStr.charAt(2)));
        resultStrBuilder.setCharAt(5, Character.toUpperCase(reverseStr.charAt(5)));
        resultStrBuilder.setCharAt(8, Character.toUpperCase(reverseStr.charAt(8)));
        String resultStr = resultStrBuilder.toString();

        // 处理3:截取前7位进行输出
        String outputStr = resultStr.substring(0, 7);

        System.out.println(outputStr);
    }
}

解释:

  1. 首先,定义一个代表输入字符串的变量 inputStr
  2. 处理1:使用 StringBuilder 类的 reverse() 方法将字符串逆序,并使用 toString() 方法转换为普通字符串。将逆序后的字符串保存在 reverseStr 变量中。
  3. 处理2:使用 StringBuilder 类创建一个新的字符串构建器,将逆序后的字符串 reverseStr 作为初始字符串。
  • 使用 setCharAt() 方法将第3,6,9位字母转换成大写字母。
  • 注意:字符串下标从0开始,因此第3,6,9位分别对应下标2、5、8。
  1. 将处理后的字符串构建器转换为普通字符串,保存在 resultStr 变量中。
  2. 处理3:使用 substring() 方法从处理后的字符串 resultStr 中截取前7位,保存在 outputStr 变量中。
  3. 最后,使用 System.out.println() 方法输出 outputStr 变量的值。

<完>

文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/JavaNotes 已经收录,有很多系列文章,欢迎Star。

原文地址:https://www.cnblogs.com/niumoo/p/17121504.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越