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

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

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

前言

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

功能描述

输入:一个文件夹路径。

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

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

StringpathString="/Users/darcy/";FilefileDir=newFile(pathString);if(!fileDir.exists() || !fileDir.isDirectory()) {thrownewRuntimeException("xxx....");}String[] fileList = fileDir.list();intcount=0;for(String filePath : fileList) {if(newFile(pathString + filePath).isDirectory()) {        count++;    }}System.out.println(count);

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

Pathpath=Paths.get("/Users/darcy/");if(!Files.exists(path) || !Files.isDirectory(path)) {thrownewRuntimeException("xxx....");}longdirCount=Files.list(path).filter(Files::isDirectory).count();System.out.println(dirCount);

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

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

问题分析

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

问题复现

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

while(true){Pathpath=Paths.get("/Users/darcy/");if(!Files.exists(path) || !Files.isDirectory(path)) {thrownewRuntimeException("xxx....");    }longdirCount=Files.list(path).filter(Files::isDirectory).count();    System.out.println(dirCount); }

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

问题原因

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

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

while(true){Pathpath=Paths.get("/Users/darcy/");if(!Files.exists(path) || !Files.isDirectory(path)) {thrownewRuntimeException("xxx....");    }longdirCount=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){Pathpath=Paths.get("/Users/darcy/");if(!Files.exists(path) || !Files.isDirectory(path)) {thrownewRuntimeException("xxx....");    }// 方式1:手动关闭流// Stream<Path> pathStream = Files.list(path);// long dirCount = pathStream.filter(Files::isDirectory).count();// pathStream.close();// 方式2:使用 try resource 自动关闭流try(Stream pathStream = Files.list(path);){longdirCount=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 代码有什么问题吗?

Pathpath=Paths.get("/Users/darcy/");if(!Files.exists(path) || !Files.isDirectory(path)) {thrownewRuntimeException("fileDir.isDirectory() error");}longdirCount=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 stream = Files.list(path)) {longdirCount=stream.filter(Files::isDirectory).count();    System.out.println(dirCount);}catch(IOException e) {// 处理异常}

下面是对话截图:

ChatGPT 实现功能

问:

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

输入:一个文件夹路径。

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

ChatGPT 回答:

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

importjava.io.File;publicclassFolderCount{publicstaticvoidmain(String[] args){Stringpath="path/to/folder";// 替换成你的文件夹路径Filefolder=newFile(path);intcount=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代码,它接受一个时间字符串作为输入,并从中提取出月份并计算该月有多少天:

importjava.time.*;importjava.time.format.DateTimeFormatter;publicclassMonthDays{publicstaticvoidmain(String[] args){StringdateString="2023-02-14 12:34:56";// 输入时间字符串DateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTimedateTime=LocalDateTime.parse(dateString, formatter);intdaysInMonth=YearMonth.from(dateTime).lengthOfMonth();// 提取月份并计算该月有多少天System.out.println(daysInMonth);// 输出该月天数}}

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

下面是测试截图:

问2

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

ChatGPT 回答:

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

importjava.time.LocalDateTime;importjava.time.LocalTime;importjava.time.format.DateTimeFormatter;publicclassMain{publicstaticvoidmain(String[] args){StringtimeString="2023-02-14 12:34:56";// 代表输入的时间字符串DateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTimedateTime=LocalDateTime.parse(timeString, formatter);// 解析时间字符串// 获取当天的3:00:01LocalDateTimetargetDateTime=LocalDateTime.of(dateTime.toLocalDate(), LocalTime.of(3,0,1));        System.out.println(targetDateTime);    }}

解释:

首先,定义一个代表输入时间的字符串 timeString。

然后,使用 DateTimeFormatter 类创建一个日期时间格式化器,以便将时间字符串转换为 LocalDateTime 对象。此处使用的格式化器模式字符串为 "yyyy-MM-dd HH:mm:ss",其中:

yyyy 代表四位数的年份,例如 2023。

MM 代表两位数的月份,例如 02。

dd 代表两位数的日期,例如 14。

HH 代表两位数的小时数(24 小时制),例如 12。

mm 代表两位数的分钟数,例如 34。

ss 代表两位数的秒数,例如 56。

使用 LocalDateTime.parse() 方法将时间字符串解析为 LocalDateTime 对象。

接下来,使用 LocalDateTime.toLocalDate() 方法获取输入时间所在的日期,并使用 LocalTime.of() 方法创建一个 LocalTime 对象,代表目标时间当天的 3:00:01。

最后,使用 LocalDateTime.of() 方法将日期和时间组合成一个 LocalDateTime 对象,即获取了输入时间当天的第三小时第一秒。打印该对象即可。

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

问3

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

ChatGPT 回答:

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

publicclassMain{publicstaticvoidmain(String[] args){StringinputStr="abcdefghij";// 输入的字符串// 处理1:按字母逆序StringreverseStr=newStringBuilder(inputStr).reverse().toString();// 处理2:第3,6,9位字母转换成大写StringBuilderresultStrBuilder=newStringBuilder(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)));StringresultStr=resultStrBuilder.toString();// 处理3:截取前7位进行输出StringoutputStr=resultStr.substring(0,7);        System.out.println(outputStr);    }}

解释:

首先,定义一个代表输入字符串的变量 inputStr。

处理1:使用 StringBuilder 类的 reverse() 方法将字符串逆序,并使用 toString() 方法转换为普通字符串。将逆序后的字符串保存在 reverseStr 变量中。

处理2:使用 StringBuilder 类创建一个新的字符串构建器,将逆序后的字符串 reverseStr 作为初始字符串。

使用 setCharAt() 方法将第3,6,9位字母转换成大写字母。

注意:字符串下标从0开始,因此第3,6,9位分别对应下标2、5、8。

将处理后的字符串构建器转换为普通字符串,保存在 resultStr 变量中。

处理3:使用 substring() 方法从处理后的字符串 resultStr 中截取前7位,保存在 outputStr 变量中。

最后,使用 System.out.println() 方法输出 outputStr 变量的值。

<完>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • Java 语言支持的类型分为两类:基本类型和引用类型。整型(byte 1, short 2, int 4, lon...
    xiaogmail阅读 1,348评论 0 10
  • 废话不多说,自己进入今天的主题 1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: - 抽象:...
    传奇内服号阅读 2,351评论 1 31
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,419评论 0 17
  • 21.将一个任意长的数字变成逗号分隔的格式 方法一: 方法二: 方法三: 方法四: var s = '123456...
    花开有声是我阅读 433评论 0 1
  • ChatGPT 作为一个自然语言处理工具,已经火了一段时间。对待 ChatGPT 不同人有着不同的看法,新事物的出...
    代码我写的怎么阅读 279评论 0 0