linux alphine下的文件获取问题

1、问题描述

​ 服务文件对接功能,选中需要同步的文件后,提示"文件不存在"(文件名称为中文存在该问题,英文名称的文件无此问题)

2、问题根本问题原因

  • 核心代码

    复制代码
    Path filePath = Paths.get(sourcePath).resolve(fileName).normalize();
     File file = filePath.toFile();
     // 验证文件存在且是普通文件
     if (!file.exists() || !file.isFile()) {
     		return R.error(fileName+"在服务器的"+sourcePath+"位置不存在该文件");
    }
  • 代码实现逻辑 (java.io.File.exists() 的路径转换)

    • 底层通过 JNI 调用 C 库函数(如 accessstat)。
    • 它依赖 sun.jnu.encoding 将 Java 的 String(UTF-16)转换为字节序列。
    • 在 Linux 上,这个字节序列会被直接传递给系统调用。如果 sun.jnu.encoding 设置不正确,或与文件系统实际使用的编码不匹配,字节序列就会错误,导致 access 找不到文件。
  • 问题说明

    • 查看docker容器内的jvm编码

      复制代码
      bash-5.1# java -XshowSettings:properties -version 2>&1 | grep jnu.encoding
          sun.jnu.encoding = ANSI_X3.4-1968
    • 问题描述

      代码中使用的文件名称是utf-8编码,而jvm使用的是ansi编码,导致传递到操作系统层面后识别为"???"文件找不到

3.问题处理

3.0 模拟验证代码
复制代码
import java.nio.file.*;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;

public class TestPath {
    public static void main(String[] args) {
        String sourcePath = "/home/app/csvfile";
        String fileName = args.length > 0 ? args[0] : "标签labels.csv";

        System.out.println("fileName: " + fileName);
        System.out.println("fileName bytes (UTF-8): " + Arrays.toString(fileName.getBytes(StandardCharsets.UTF_8)));

        // 跟应用代码一模一样
        Path filePath = Paths.get(sourcePath).resolve(fileName).normalize();
        System.out.println("resolved path: " + filePath);
        System.out.println("resolved bytes: " + Arrays.toString(filePath.toString().getBytes(StandardCharsets.UTF_8)));

        java.io.File file = filePath.toFile();
        System.out.println("file.exists(): " + file.exists());
        System.out.println("file.isFile(): " + file.isFile());
        System.out.println("Files.exists(path): " + Files.exists(filePath));
    }
}
3.1 环境层面

既然默认的sun.jnu.encoding的为设置为utf-8,那需要将其设置为utf-8,而其于系统环境变量(如 LANGLC_ALL)。如果容器的 LANG 未设置为 UTF-8,C 库可能使用默认的 C locale,从而将路径视为 ASCII,非 ASCII 字符会被视为非法。

  • 验证方法 :在容器内执行 locale 命令,查看 LANGLC_CTYPE 的值。

    apk add --no-cache musl-locales musl-locales-lang
    export LANG=en_US.UTF-8
    export LC_ALL=en_US.UTF-8

  • 设置后应该如下

    系统语言环境

    708d64338dbb:/home# locale
    LANG=en_US.UTF-8
    LC_CTYPE=en_US.UTF-8
    LC_NUMERIC=en_US.UTF-8
    LC_TIME=en_US.UTF-8
    LC_COLLATE=en_US.UTF-8
    LC_MONETARY=en_US.UTF-8
    LC_MESSAGES=en_US.UTF-8
    LC_ALL=en_US.UTF-8

    jvm语言环境

    708d64338dbb:/home# java -XshowSettings:properties -version 2>&1 | grep jnu.encoding
    sun.jnu.encoding = UTF-8

  • 执行测试代码

    708d64338dbb:/home# java TestPath.java
    fileName: 标签labels.csv
    fileName bytes (UTF-8): [-26, -96, -121, -25, -83, -66, 108, 97, 98, 101, 108, 115, 46, 99, 115, 118]
    resolved path: /home/app/csvfile/标签labels.csv
    resolved bytes: [47, 104, 111, 109, 101, 47, 97, 112, 112, 47, 99, 115, 118, 102, 105, 108, 101, 47, -26, -96, -121, -25, -83, -66, 108, 97, 98, 101, 108, 115, 46, 99, 115, 118]
    file.exists(): true
    file.isFile(): true
    Files.exists(path): true

3.2 代码层面
API JNI 实现类 路径处理
File.exists() java.io.UnixFileSystem 走 musl realpath() 规范化,对非 ASCII 路径处理有问题
Files.exists() sun.nio.fs.UnixNativeDispatcher 直接传字节数组给内核,不做规范化
  • 检查文件是否存在

    复制代码
    // 当前(有问题的)
    File file = filePath.toFile();
    if (!file.exists() || !file.isFile()) {
    
    // 修复后
    if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
  • 读取文件

1. DLakeInfoServiceImpl.java 第 1437 行 --- 日志用:

java 复制代码
// 改前
file.length()
// 改后
Files.size(filePath)

2. DLakeInfoServiceImpl.java 第 1439 行 --- 编码检测,需要给 FileEncodingHandler 加一个接受 Path 的重载:

java 复制代码
// 改后
String detectedEncoding = String.valueOf(FileEncodingHandler.detectEncoding(filePath));

3. DLakeInfoServiceImpl.java 第 1453 行 --- 读取文件:

java 复制代码
// 改前
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), detectedEncoding))
// 改后
BufferedReader br = Files.newBufferedReader(filePath, Charset.forName(detectedEncoding))