diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c722f69b..b04f00264 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@
* 【db 】 修复JndiDSFactory空指针问题
* 【core 】 修复BiMap.put错误的返回值(pr#1218@Gitee)
* 【core 】 修复BooleanUtil.andOfWrap针对null错误问题(issue#3587@Github)
+* 【core 】 修复FileUtil#getTotalLines在JDK9+结果错误问题(issue#3591@Github)
-------------------------------------------------------------------------------------------------------------
# 5.8.27(2024-03-29)
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
index 0b6f24fa4..fddf657ba 100755
--- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
@@ -14,6 +14,7 @@ import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
@@ -24,20 +25,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.core.util.ZipUtil;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.LineNumberReader;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.RandomAccessFile;
-import java.io.Reader;
+import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
@@ -580,24 +568,65 @@ public class FileUtil extends PathUtil {
/**
* 计算文件的总行数
- * 读取文件采用系统默认编码,一般乱码不会造成行数错误。
+ * 参考:https://stackoverflow.com/questions/453018/number-of-lines-in-a-file-in-java
*
* @param file 文件
* @return 该文件总行数
* @since 5.7.22
*/
public static int getTotalLines(File file) {
+ return getTotalLines(file, 1024);
+ }
+
+ /**
+ * 计算文件的总行数
+ * 参考:https://stackoverflow.com/questions/453018/number-of-lines-in-a-file-in-java
+ *
+ * @param file 文件
+ * @param bufferSize 缓存大小,小于1则使用默认的1024
+ * @return 该文件总行数
+ * @since 5.8.28
+ */
+ public static int getTotalLines(File file, int bufferSize) {
if (false == isFile(file)) {
throw new IORuntimeException("Input must be a File");
}
- try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) {
- // 设置起始为1
- lineNumberReader.setLineNumber(1);
- // 跳过文件中内容
- //noinspection ResultOfMethodCallIgnored
- lineNumberReader.skip(Long.MAX_VALUE);
- // 获取当前行号
- return lineNumberReader.getLineNumber();
+ if (bufferSize < 1) {
+ bufferSize = 1024;
+ }
+ try (InputStream is = getInputStream(file)) {
+ byte[] c = new byte[bufferSize];
+ int readChars = is.read(c);
+ if (readChars == -1) {
+ // 空文件,返回0
+ return 0;
+ }
+
+ // 起始行为1
+ // 如果只有一行,无换行符,则读取结束后返回1
+ // 如果多行,最后一行无换行符,最后一行需要单独计数
+ // 如果多行,最后一行有换行符,则空行算作一行
+ int count = 1;
+ while (readChars == bufferSize) {
+ for (int i = 0; i < bufferSize; i++) {
+ if (c[i] == CharUtil.LF) {
+ ++count;
+ }
+ }
+ readChars = is.read(c);
+ }
+
+ // count remaining characters
+ while (readChars != -1) {
+ for (int i = 0; i < readChars; i++) {
+ if (c[i] == CharUtil.LF) {
+ ++count;
+ }
+ }
+ readChars = is.read(c);
+ }
+
+ return count;
} catch (IOException e) {
throw new IORuntimeException(e);
}
@@ -1415,8 +1444,8 @@ public class FileUtil extends PathUtil {
if (false == file1.exists() || false == file2.exists()) {
// 两个文件都不存在判断其路径是否相同, 对于一个存在一个不存在的情况,一定不相同
return false == file1.exists()//
- && false == file2.exists()//
- && pathEquals(file1, file2);
+ && false == file2.exists()//
+ && pathEquals(file1, file2);
}
return equals(file1.toPath(), file2.toPath());
}
@@ -3510,7 +3539,7 @@ public class FileUtil extends PathUtil {
* @since 4.1.15
*/
public static String getMimeType(String filePath) {
- if(StrUtil.isBlank(filePath)){
+ if (StrUtil.isBlank(filePath)) {
return null;
}
@@ -3637,8 +3666,8 @@ public class FileUtil extends PathUtil {
// 替换Windows路径分隔符为Linux路径分隔符,便于统一处理
fileName = fileName.replace('\\', '/');
if (false == isWindows()
- // 检查文件名中是否包含"/",不考虑以"/"结尾的情况
- && fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
+ // 检查文件名中是否包含"/",不考虑以"/"结尾的情况
+ && fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
// 在Linux下多层目录创建存在问题,/会被当成文件名的一部分,此处做处理
// 使用/拆分路径(zip中无\),级联创建父目录
final List pathParts = StrUtil.split(fileName, '/', false, true);
diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java
index dbecd72b0..913c62580 100644
--- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java
@@ -523,11 +523,17 @@ public class FileUtilTest {
}
@Test
- @Ignore
public void getTotalLinesTest() {
- // 千万行秒级内返回
- final int totalLines = FileUtil.getTotalLines(FileUtil.file(""));
- Assert.assertEquals(10000000, totalLines);
+ // 此文件最后一行有换行符,则最后的空行算作一行
+ final int totalLines = FileUtil.getTotalLines(FileUtil.file("test_lines.csv"));
+ Assert.assertEquals(8, totalLines);
+ }
+
+ @Test
+ public void issue3591Test() {
+ // 此文件最后一行末尾无换行符
+ final int totalLines = FileUtil.getTotalLines(FileUtil.file("1_psi_index_0.txt"));
+ Assert.assertEquals(11, totalLines);
}
@Test
diff --git a/hutool-core/src/test/resources/1_psi_index_0.txt b/hutool-core/src/test/resources/1_psi_index_0.txt
new file mode 100644
index 000000000..2465b23f4
--- /dev/null
+++ b/hutool-core/src/test/resources/1_psi_index_0.txt
@@ -0,0 +1,11 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
\ No newline at end of file