点赞再看,能源有限。 微信搜「 程序猿阿朗 」。

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

明天一个很简略的性能,触发了一个 BUG,解决后我想起了最近爆火的 ChatGPT,于是我尝试测试 ChatGPT 是否发现这个 BUG。这篇文章会先介绍性能代码,而后手动剖析 BUG 起因;接着测试 ChatGPT 是否发现这个 BUG。

先说下结论,测试完结,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。