如何将Windows-1251编码的文本转换为可读格式?

3
我有一个字符串,由Jericho HTML解析器返回,并包含一些俄文文本。根据source.getEncoding()和相应HTML文件的头信息,编码是Windows-1251。
如何将此字符串转换为可读的格式?
我尝试了这个:
import java.io.UnsupportedEncodingException;

public class Program {
    public void run() throws UnsupportedEncodingException {
        final String windows1251String = getWindows1251String();
        System.out.println("String (Windows-1251): " + windows1251String);
        final String readableString = convertString(windows1251String);
        System.out.println("String (converted): " + readableString);
    }
    private String convertString(String windows1251String) throws UnsupportedEncodingException {
        return new String(windows1251String.getBytes(), "UTF-8");
    }
    private String getWindows1251String() {
        final byte[] bytes = new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
        return new String(bytes);
    }
    public static void main(final String[] args) throws UnsupportedEncodingException {
        final Program program = new Program();
        program.run();
    }
}

变量bytes包含在我的调试器中显示的数据,它是net.htmlparser.jericho.Element.getContent().toString().getBytes()的结果。我只是将该数组复制并粘贴到这里。
这不起作用 - readableString包含垃圾数据。
如何修复它,即确保正确解码Windows-1251字符串?
更新1(2015年7月30日12:45 MSK):当在convertString的调用中更改编码为Windows-1251时,没有任何变化。请参见下面的屏幕截图。

Screenshot

更新2:另一次尝试:

Second screenshot

更新3(2015年7月30日14:38):我需要解码的文本与下面显示的下拉列表中的文本相对应。

Expected result

更新4(2015年7月30日14: 41):编码检测器(见下文代码)显示编码不是Windows-1251,而是UTF-8。
public static String guessEncoding(byte[] bytes) {
    String DEFAULT_ENCODING = "UTF-8";
    org.mozilla.universalchardet.UniversalDetector detector =
        new org.mozilla.universalchardet.UniversalDetector(null);
    detector.handleData(bytes, 0, bytes.length);
    detector.dataEnd();
    String encoding = detector.getDetectedCharset();
    System.out.println("Detected encoding: " + encoding);
    detector.reset();
    if (encoding == null) {
        encoding = DEFAULT_ENCODING;
    }
    return encoding;
}

1
你试过 new String(bytes, "Windows-1251") 吗? - undefined
@FlorianSchaetz 是的,请查看更新1。 - undefined
1
不好意思,不是在那里,而是在getWindows1251String函数中。new String()可能已经尝试在那里生成一个UTF-8字符串,请参考http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#String%28byte[]%29。 - undefined
你期望看到什么?如果你手动查找字符的值,它们是正确的 - 在这里查看0xBD、0xBF和0xEF:https://en.wikipedia.org/wiki/Windows-1251 这三个字符就是你看到的,并且对应着十进制值-65、-67和-17,它们在你的字节数组中(初始空格之后)反复出现。 - undefined
更新对我来说意味着系统编码已经是 Windows-1512,这就是为什么在第 16 行指定它没有任何区别。因此,在第 12 行,getBytes() 返回另一个 Windows-1512 编码的字节数组 - 与你最开始的一样,所以徒劳无功。然后当你调用 new String 并指定为 UTF-8 时,解码失败,因为字节数组不是UTF-8。无论如何,从任何角度来看,convertString 方法都是多余的。 - undefined
显示剩余4条评论
3个回答

3

鉴于更新,我删除了原来的答案并重新开始

出现的文本

пїЅпїЅпїЅпїЅпїЅпїЅ

准确解码这些字节值。

-17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67

两端都用32填充,即空格。

所以可能是

1)文本错误或

2)文本应该是这样的,或者

3)编码不是Windows-1215。

这一行明显有误。

return new String(windows1251String.getBytes(), "UTF-8");

从字符串中提取字节并构建新的字符串并不是“转换”编码的方法。输入字符串和输出字符串都在内部使用UTF-16编码(通常你甚至不需要知道或关心这一点)。其他编码出现的唯一时机是当文本数据存储在字符串对象之外 - 即在初始字节数组中。当字符串被构造后,就进行了转换。没有从一个字符串类型到另一个字符串类型的转换 - 它们都是相同的。
事实上,这个
return new String(bytes);

执行与此相同的操作。

return new String(bytes, "Windows-1251");

建议 Windows-1251 是该平台的默认编码。(这也得到了你所在时区是 MSK 的支持)

+1; 我可以确认 byte[] 被正确显示了。我在 Windows-1251 编码页面中进行了检查。(byte -17 = int 239 = 0xEF = char 'п')https://en.wikipedia.org/wiki/Windows-1251 - undefined

3
我通过修改从网站读取文本的代码部分来解决了这个问题。
private String readContent(final String urlAsString) {
    final StringBuilder content = new StringBuilder();
    BufferedReader reader = null;
    InputStream inputStream = null;
    try {
        final URL url = new URL(urlAsString);
        inputStream = url.openStream();
        reader =
            new BufferedReader(new InputStreamReader(inputStream);

        String inputLine;
        while ((inputLine = reader.readLine()) != null) {
            content.append(inputLine);
        }
    } catch (final IOException exception) {
        exception.printStackTrace();
    } finally {
        IOUtils.closeQuietly(reader);
        IOUtils.closeQuietly(inputStream);
    }
    return content.toString();
}

我修改了这行代码

new BufferedReader(new InputStreamReader(inputStream);

为了

new BufferedReader(new InputStreamReader(inputStream, "Windows-1251"));

然后它就工作了。


问题在于读取器(reader)已经在内部执行了从bytechar的转换。这也是读取器和流(stream)之间的主要区别。在这一点上,你的数据已经损坏。好的解决方案。 - undefined

1

为了确保您完全理解Java如何处理charbyte

byte[] input = new byte[1];

// values > 127 become negative when you put them in an array.
input[0] = (byte)239; // the array contains value -17 now.

// but all 255 values are preserved. 
// But if you cast them to integers, you should use their unsigned value.
// (casting alone isn't enough).
int output = input[0] & 0xFF; // output is 239 again

// you shouldn't cast directly from a single-byte to a char.
// because: char is 16-bit ; but you only want to use 1 byte ; unfortunately your negative values will be applied in the 2nd byte, and break it.
char corrupted = (char) input[0]; // char-code: 65519 (2 bytes are used)
char corrupted = (char) ((int)input[0]); // char-code: 66519 (2 bytes are used)

// just casting to an integer/character is ok for values < 0x7F though
// values < 0x7F are always positive, even when casted to byte
// AND the first 7-bits in any ascii-encodings (e.g. windows-1251) are identical.
byte simple = (byte) 'a';
char chr = (char) ascii_LT_7F; // will result in 'a' again

// But it's still more reliable to use the & 0xFF conversion.
// Because it ensures that your character can never be greater than char code 255 (a single byte), even when the byte is unexpectedly negative (> 0x7F).
char chr = (char) ((byte)simple & 0xFF); // also results in 'a'

// for value 239 (which is 0xEF) it's impossible though.
// a java char is 16-bit encoded internally, following the unicode character set.
// characters 0x00 to 0x7F are identical in most encodings.
// but e.g. 0xEF in windows-1251 does not match 0xEF in UTF-16.
// so, this is a bad idea.
char corrupted = (char) (input[0] & 0xFF);

// And that's something you can only fix by using encodings.
// It's good practice to use encodings really just ALWAYS.
// the encoding indicates what your bytes[] are encoded in NOW.
// your bytes will be converted to 16-bit characters.
String text = new String(bytes, "from-encoding");

// if you want to change that text back to bytes, use an encoding !!
// this time the encoding specifies is the TARGET-ENCODING.
byte[] bytes = text.getBytes("to-encoding");

我希望这能有所帮助。
至于显示的值: 我可以确认byte[]已正确显示。我在Windows-1251代码页中检查了它们。(byte -17 = int 239 = 0xEF = char 'п')
换句话说,你的byte值是不正确的,或者它是一个不同的源编码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接