之前一段时间排查一个应用在中文目录下无法启动的问题,查过一个 Equinox 的 Manifest 处理的坑。今天,一个同事在写 Equinox 插件的时候也遇到了类似的问题。这里记录一下 Equinox 里面对 Manifest 中的中文处理的坑。

问题描述

先来看一段代码:

package manifest;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Manifest;

import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.BundleException;

public class Test {
    private static String chineseString = "你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界你好,世界";

    public static void main(String[] args) throws IOException, BundleException {
        File manifestFile = createManifest();

        // 使用 ManifestElement 读取
        Map<String, String> header = new HashMap<>();
        ManifestElement.parseBundleManifest(new FileInputStream(manifestFile), header);
        System.out.println(header.get("Test-Headers"));
    }

    private static File createManifest() throws IOException {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
        // 创建一个带有中文的 Header,MANIFEST.MF 的一行的最大长度是 72 字节,中文在这里会被阶段
        manifest.getMainAttributes().putValue("Test-Headers", chineseString);

        File file = new File("MANIFEST.MF");

        if (!file.exists()) {
            file.createNewFile();
        }

        OutputStream outputStream = new FileOutputStream(file);

        manifest.write(outputStream);
        outputStream.flush();
        outputStream.close();

        return file;
    }
}

这一段代码虽然比较长,但是实际上的内容其实不多,主要做了这几步:

  1. 创建一个内容带有中文的 MANIFEST.MF 文件。
  2. 写入 MANIFEST.MF 文件到磁盘上。
  3. 用 Equinox 的 ManifestElement 来读取 MANFIEST.MF。
  4. 输出刚才的带有中文的 Header

上面的这段代码在我本地的执行结果如下:

你好,世界你好,世界你好,世界你好,��界你好,世界你好,世界你好,世界你好,世界你��,世界你好,世界你好,世界你好,世界

大家可以看到,中间有几个地方出现了乱码,同样的代码,如果用标准的 JDK 的方式来读取,是不会出现这种情况,为什么呢?

原因

首先,我们知道 UTF-8 的中文是占据了三个字节,而 MANIFEST.MF 文件一行的最大长度是 72 个字节,也就是说,如果你的 MANIFEST.MF 中含有中文,那么这个中文的三个字节可能会被截断,出现一部分在上面一行,一部分在下面一行的情况。上面的 MANIFEST.MF 文件就出现了这种情况,可以 cat 这个文件来看一下:

[~/Desktop/test]$ cat MANIFEST.MF
Manifest-Version: 1.0
Test-Headers: 你好,世界你好,世界你好,世界你好,�
 �界你好,世界你好,世界你好,世界你好,世界你�
 �,世界你好,世界你好,世界你好,世界

不过,即使这样写入了,如果读取的时候完全按照字节来读取的话,那也应该没有问题。但是,Equinox 比较特立独行,看下 Equinox 读取 Manifest 的关键代码:

图片描述

作死的 Equinox 将一行读取出来以后直接转成了一个 string,而 byte 在转 string 的时候,如果遇到无法转的字节的话,会用 来替代,于是就出现了上面的情况。(关于 ,可以看 http://en.wikipedia.org/wiki/Specials_(Unicode_block)

解决方法

这个问题除非 Equinox 修复了此 Bug,否则是无解的。只能说在用到 Equinox 的时候,尽量不要使用中文的 Header 吧。


khotyn
12 声望3 粉丝

蚂蚁金服技术专家,Java 容器和框架开发。