为什么 path.toString() 的结果未能在 Linux 上显示所有字符,但在 windows 上显示正常

Why are results of path.toString() failing to show all characters on Linux but ok on windows

在我的 Java 代码中,我使用 FileVisitor 遍历文件系统并创建路径结构,然后将其转换为 json 对象以在 html 中呈现.

运行 在 Windows 上即使针对 linux 文件系统也能正常运行,运行 在 Linux 上针对相同(现在是本地)文件系统它失败在路径

上调用 toString() 时正确呈现特殊字符

即Windows调试输出

CreateFolderTree:createJsonData:SEVERE: AddingNode(1):Duarte Lôbo- Requiem

和 html 显示正常

Duarte Lôbo- Requiem

但 linux 调试输出给出

CreateFolderTree:createJsonData:SEVERE: AddingNode(1):Duarte L??bo- Requiem

和 html 显示为两个带有问号的黑色菱形而不是 ô 字符

为什么会发生这种情况,路径由 FileVisitor class 提供,因此必须正确构建(即我不是自己破解它),然后我只是在路径上调用 toString() .

这是字体问题吗,我在 linux 系统上遇到过一些字体问题,但在这里我只是将字符串返回到 html,所以看不到连接。

可能是编码问题,但我看不到我明确设置编码的地方

下面的代码块,调试显示 linux 的无效输出在 createJsonData() 方法中

编辑:我已经修复了日志记录问题,因此输出被写为 UTF-8

  FileHandler fe = new FileHandler(logFileName, LOG_SIZE_IN_BYTES, 10, true);
  fe.setEncoding(StandardCharsets.UTF_8.name());

所以我们现在看到 Windows 输出正确

CreateFolderTree:createJsonData:SEVERE: AddingNode(1):Duarte Lôbo- Requiem

但是Linux正在输出

CreateFolderTree:createJsonData:SEVERE: AddingNode(1):Duarte L��bo- Requiem

如果我在 HexEditor 中查看它,它会给出 L��bo

的输出

4C EF BF BD EF BF BD 62 6F

编辑:部分解决方案

我遇到了What exactly is sun.jnu.encoding?

发现建议加这个

 -Dsun.jnu.encoding=UTF-8

它的工作文件现在显示正常

不幸的是,如果用户随后单击此类文件并将其发送回服务器,我现在会收到此错误

java.lang.NullPointerException
    at java.base/sun.nio.fs.UnixPath.normalizeAndCheck(Unknown Source)
    at java.base/sun.nio.fs.UnixPath.<init>(Unknown Source)
    at java.base/sun.nio.fs.UnixFileSystem.getPath(Unknown Source)
    at java.base/java.nio.file.Paths.get(Unknown Source)
    at com.jthink.songkong.server.callback.ServerFixSongs.configureFileMapping(ServerFixSongs.java:59)
    at com.jthink.songkong.server.callback.ServerFixSongs.startTask(ServerFixSongs.java:88)
    at com.jthink.songkong.server.CmdRemote.lambda$null(CmdRemote.java:107) 

我尝试添加 -Dfile.encoding=UTF-8 作为 jnu 选项的补充或替代,但没有帮助,jnu 选项正是我需要的。

我不必添加这个未记录的 sun-jnu-encoding 选项,所以服务器似乎以某种方式损坏了?

代码

   import com.google.common.base.Strings;
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.jthink.songkong.analyse.analyser.Counters;
    import com.jthink.songkong.analyse.general.Errors;
    import com.jthink.songkong.cmdline.SongKong;
    import com.jthink.songkong.fileloader.RecycleBinFolderNames;
    import com.jthink.songkong.server.fs.Data;
    import com.jthink.songkong.server.fs.PathWalker2;
    import com.jthink.songkong.server.fs.State;
    import com.jthink.songkong.ui.MainWindow;
    import com.jthink.songkong.ui.progressdialog.FixSongsCounters;
    import spark.Request;
    import spark.Response;

    import java.io.IOException;
    import java.net.UnknownHostException;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    import java.util.logging.Level;


    /**
     * Count the number of files that can be loaded, for information purposes only
     */
    public class CreateFolderTree
    {
        private Path treeRoot;

        Set<Path> keys = new HashSet<Path>();


        public static class VisitFolder
                extends SimpleFileVisitor<Path>
        {

            private Set<Path> keys;
            private Integer maxDepth;
            private int depth;

            public VisitFolder(Set<Path> keys, Integer maxDepth)
            {
                this.keys=keys;
                this.maxDepth = maxDepth;
            }

            /**
             *
             * @param dir
             * @param attrs
             * @return
             * @throws IOException
             */
             /*
             * Ignore some dirs
             * @param dir
             * @param attrs
             * @return
             * @throws IOException
             */
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                    throws IOException
            {
                try
                {
                    if (dir.toFile().getName().equals(".AppleDouble"))
                    {
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    else if (dir.toString().equals("/proc"))
                    {
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    else if (dir.toString().equals("/dev"))
                    {
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    else if (RecycleBinFolderNames.isMatch(dir.toFile().getName()))
                    {
                        MainWindow.logger.log(Level.SEVERE, "Ignoring " + dir.toString());
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    else if (dir.toString().toLowerCase().endsWith(".tar"))
                    {
                        return FileVisitResult.SKIP_SUBTREE;
                    }

                    depth++;

                    if(depth > maxDepth)
                    {
                        depth--;
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    keys.add(dir);
                    return super.preVisitDirectory(dir, attrs);
                }
                catch(IOException e)
                {
                    MainWindow.logger.warning("Unable visit dir:"+dir + ":"+e.getMessage());
                    return FileVisitResult.SKIP_SUBTREE;
                }
            }


            /**
             *
             * Tar check due to 
             * SONGKONG-294:Ignore exceptions if file is not readable
             *
             * @param file
             * @param exc
             * @return
             * @throws IOException
             */
            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException
            {

                if (file.toString().endsWith(".tar")) {
                    //We dont log to reports as this is a bug in Java that we are handling not a problem in SongKong
                    MainWindow.logger.log(Level.SEVERE, exc.getMessage());
                    return FileVisitResult.CONTINUE;
                }

                try
                {
                    FileVisitResult result = super.visitFileFailed(file, exc);
                    return result;
                }
                catch(IOException e)
                {
                    MainWindow.logger.warning("Unable to visit file:"+file + ":"+e.getMessage());
                    return FileVisitResult.CONTINUE;
                }
            }

            /**
             * SONGKONG-294:Ignore exception if folder is not readable
             *
             * @param dir
             * @param exc
             * @return
             * @throws IOException
             */
            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                    throws IOException
            {
                depth--;
                try
                {
                    FileVisitResult result = super.postVisitDirectory(dir, exc);
                    return result;
                }
                catch(IOException e)
                {
                    MainWindow.logger.warning("Unable to count files in dir(2):"+dir);
                    return FileVisitResult.CONTINUE;
                }
            }
        }

        public CreateFolderTree(Path treeRoot)
        {
            this.treeRoot = treeRoot;
        }

        public String start(int depth)
        {
            VisitFolder visitFolder;
            try
            {

                if(treeRoot==null)
                {
                    for (Path path : FileSystems.getDefault().getRootDirectories())
                    {
                        visitFolder = new VisitFolder(keys, depth);
                        Files.walkFileTree(path, visitFolder);
                    }
                }
                else
                {
                    visitFolder = new VisitFolder(keys, depth);
                    Files.walkFileTree(treeRoot, visitFolder);
                }

                PathWalker2 pw = new PathWalker2();
                for (Path key : keys)
                {
                    //SONGKONG-505: Illegal character in Filepath problem prevented reportFile creation
                    try
                    {
                        pw.addPath(key);
                    }
                    catch (InvalidPathException ipe)
                    {
                        MainWindow.logger.log(Level.SEVERE, ipe.getMessage(), ipe);
                    }
                }
                Gson gson = new GsonBuilder().create();
                return gson.toJson(createJsonData(pw.getRoot()));
            }
            catch (Exception e)
            {
                handleException(e);
            }
            return "";
        }

        public void handleException(Exception e)
        {
            MainWindow.logger.log(Level.SEVERE, "Unable to count files:"+e.getMessage(), e);
            Errors.addError("Unable to count files:"+e.getMessage());
            MainWindow.logger.log(Level.SEVERE, e.getMessage());
            Counters.getErrors().getCounter().incrementAndGet();
            SongKong.refreshProgress(FixSongsCounters.SONGS_ERRORS);
        }

        /**
         * Add this node and recursively its children,  returning json data representing the tree
         *
         * @param node
         * @return
         */
        private Data createJsonData(PathWalker2.Node node)
        {
            Data data = new Data();
            if(node.getFullPath()!=null)
            {
                data.setId(node.getFullPath().toString());
                if(node.getFullPath().getFileName()!=null)
                {
                    MainWindow.logger.severe("AddingNode(1):"+node.getFullPath().getFileName().toString());
                    data.setText(node.getFullPath().getFileName().toString());
                }
                else
                {
                    MainWindow.logger.severe("AddingNode(2):"+node.getFullPath().toString());
                    data.setText(node.getFullPath().toString());
                }
            }
            else
            {
                try
                {
                    data.setText(java.net.InetAddress.getLocalHost().getHostName());
                    data.setId("#");
                    State state = new State();
                    state.setOpened(true);
                    data.setState(state);
                }
                catch(UnknownHostException uhe)
                {
                    data.setText("Server");
                }
            }

            //Recursively add each child folder of this node
            Map<String, PathWalker2.Node> children = node.getChildren();
            if(children.size()>0)
            {
                data.setChildren(new ArrayList<>());
                for (Map.Entry<String, PathWalker2.Node> next : children.entrySet())
                {
                    data.getChildren().add(createJsonData(next.getValue()));
                }
            }
            else
            {
                data.setBooleanchildren(true);
            }
            return data;
        }

        public static String createFolderJsonData(Request request, Response response)
        {
            if(Strings.nullToEmpty(request.queryParams("id")).equals("#"))
            {
                CreateFolderTree cft = new CreateFolderTree(null);
                String treeData = cft.start(1).replace("booleanchildren", "children");
                return treeData;
            }
            else
            {
                CreateFolderTree cft = new CreateFolderTree(Paths.get(request.queryParams("id")));
                String treeData = cft.start(2    ).replace("booleanchildren", "children");
                return treeData;
            }
        }

    }


    import java.nio.file.Path;
    import java.util.Collections;
    import java.util.Map;
    import java.util.TreeMap;

    /** Constructs a tree of folders based on a list of filepaths
     *
     * i.e a give it a list if all folder that  contain files that have been modified and it creates a hierachy
     * that can then be used to generate a data structure for use by jstree
     *
     */
    public class PathWalker2
    {
        private final Node root;


        public PathWalker2()
        {
            root = new Node();
        }

        public Node getRoot()
        {
            return root;
        }

        /**
         * Represent a node on the tree (may/not have children)
         */
        public static class Node
        {
            //Keyed on name and node
            private final Map<String, Node> children = new TreeMap<>();

            private Path fullPath;

            public Node addChild(String name)
            {

                if (children.containsKey(name))
                    return children.get(name);

                Node result = new Node();
                children.put(name, result);
                return result;
            }

            public Map<String, Node> getChildren()
            {
                return Collections.unmodifiableMap(children);
            }

            public void setFullPath(Path fullPath)
            {
                this.fullPath = fullPath;
            }

            public Path getFullPath()
            {
                return fullPath;
            }
        }

        /**
         * @param path
         */
        public void addPath(Path path)
        {
            Node node = root.addChild((path.getRoot().toString().substring(0, path.getRoot().toString().length() - 1)));

            //For each segment of the path add as child if not already added
            for (int i = 0; i < path.getNameCount(); i++)
            {
                node = node.addChild(path.getName(i).toString());
            }

            //Set full path of this node
            node.setFullPath(path);
        }


    }

对于 html,您要么需要设置符合您需要的适当字符集,要么最好坚持使用 ASCII 并对所有 non-ASCII 字符使用 html-encoding。 即使没有为您定义特定的字符集,这仍然有效 html 显示。

https://en.wikipedia.org/wiki/Unicode_and_HTML

您的调试输出似乎经过了字符集之间的多次转换。您发送到控制台的文本似乎使用 UTF-8 作为编码转换为字节,导致从 ôô 的转换。然后似乎使用系统的字符集从 byte-data 转换回字符。 Windows' 控制台使用 cp1252 作为字符集,而 Linux 在每个安装基础上有不同的设置。在您的情况下,似乎是 ASCII 导致 UTF-8 编码数据转换为两个 ?,因为这些字节具有未在 ASCII 中定义的值。

我不知道您使用的日志记录框架或您使用的 Logger 的具体设置是什么,所以我无法告诉您如何解决这个问题,但是 Linux-variant],您可以检查控制台的字符集并将其更改为 UTF-8 以查看是否具有所需的效果。

与往常一样,编码问题需要大量调试工作。不仅有很多不同的东西影响它,而且它们在不同的时间影响它,所以第一个任务总是检查哪里它首先出错了。

正如 � 的处理所示,一旦出错,它可能会出错 更多 如果您尝试从最终结果开始调试,就像从烂洋葱上剥层一样。


在这种情况下,问题的根源在于 OS 语言环境,它被设置为 POSIX。这个旧标准使您的 OS 表现得像 70 年代的东西,带有 ASCII 编码和其他过时的细节。 ASCII 编码将阻止 OS 理解文件名、文本或任何包含更多奇异字符的内容。这会导致奇怪的问题,因为 JVM 本身运行良好,但任何时候它与 OS 通信(打印到文本文件,要求打开具有特定名称的文件)都有可能损坏,因为OS 不明白 JVM 在说什么。

就好像有人在和你说话,每隔一段时间他就会在里面放一个中文单词。你用英文写下他说的话,但你用 "Didn't understand???".

替换每个中文单词

语言环境(在 /etc/default/locale 中)通常包含合理的默认值,但正如我们在这里看到的,您不能总是相信它。对于任何现代系统,您都需要像 en_EN.UTF-8 这样的语言环境值。在这个时代,你绝对不想看到 POSIX