使用java.util.Scanner读取具有不同字符编码的文件

5

我使用Java读取文件列表。其中一些文件的编码不同,使用的是ANSI而不是UTF-8java.util.Scanner无法读取这些文件并获得空输出字符串。我尝试了另一种方法:

                FileInputStream fis = new FileInputStream(my_file);
                BufferedReader br = new BufferedReader(new InputStreamReader(fis));
                InputStreamReader isr = new InputStreamReader(fis);
                isr.getEncoding();

我不确定如何在ANSI编码的情况下更改字符编码。UTF-8和ANSI文件混合在同一个文件夹中。我尝试使用Apache Tika来解决这个问题。获取文件的编码后,我使用Scanner,但是输出为空。

Scanner scanner = new Scanner(my_file, detector.getCharset().toString());
line = scanner.nextLine();

1
可能是重复的问题:Java:如何确定流的正确字符集编码 - locus2k
@locus2k:我已经添加了它。 - plaidshirt
确保你的文件顶部不为空,然后将你的扫描器放入一个while循环中 while(scanner.hasNextLine()) line = scanner.nextLine(); 如果这样做不起作用,那么如果你只是在读取行,可以尝试使用普通的缓冲读取器方式。 - locus2k
@locus2k:我明白了,但据我所知,Scanner也应该可以这样工作。 - plaidshirt
@Slaw:不,这些文件来自外部来源。 - plaidshirt
显示剩余8条评论
3个回答

2
有一个名为juniversalchardet的库可以帮助你猜测正确编码。它最近已更新,目前位于GitHub上:
https://github.com/albfernandez/juniversalchardet
然而,并没有绝对可靠的工具来检测编码,因为有很多未知的东西:
1. 这个文件是文本还是PNG? 2. 它存储在(1,...,k,...,n)位编码中吗? 3. 使用了哪种k位编码?
通过计算不常用控制字符的数量,可以进行一些猜测。当一个文件包含许多控制符号时,你可能选择了错误的编码。(然后尝试下一个)
Juniversalchardet尝试多种并且更成功的方式来确定编码(甚至包括中文)。它还提供了方便的方法来打开一个阅读器,从文件中选择正确的编码:
(代码片段取自https://github.com/albfernandez/juniversalchardet#creating-a-reader-with-correct-encoding并进行了修改)
import org.mozilla.universalchardet.ReaderFactory;
import java.io.File;
import java.io.IOException;
import java.io.Reader;

public class TestCreateReaderFromFile {

    public static void main (String[] args) throws IOException {
        if (args.length != 1) {
            System.err.println("Usage: java TestCreateReaderFromFile FILENAME");
            System.exit(1);
        }

        Reader reader = null;
        try {
            File file = new File(args[0]);
            reader = ReaderFactory.createBufferedReader(file);

            String line;
            while((line=reader.readLine())!=null){
                System.out.println(line); //Print each line to console
            }
        }
        finally {
            if (reader != null) {
                reader.close();
            }
        }

    }

}

编辑:添加了ScannerFactory

/*
(C) Copyright 2016-2017 Alberto Fernández <infjaf@gmail.com>
Adapted by Fritz Windisch 2018-11-15
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Scanner;
import org.mozilla.universalchardet.UniversalDetector;
import org.mozilla.universalchardet.UnicodeBOMInputStream;

/**
 * Create a scanner from a file with correct encoding
 */
public final class ScannerFactory {

    private ScannerFactory() {
        throw new AssertionError("No instances allowed");
    }
    /**
     * Create a scanner from a file with correct encoding
     * @param file The file to read from
     * @param defaultCharset defaultCharset to use if can't be determined
     * @return Scanner for the file with the correct encoding
     * @throws java.io.IOException if some I/O error ocurrs
     */

    public static Scanner createScanner(File file, Charset defaultCharset) throws IOException {
        Charset cs = Objects.requireNonNull(defaultCharset, "defaultCharset must be not null");
        String detectedEncoding = UniversalDetector.detectCharset(file);
        if (detectedEncoding != null) {
            cs = Charset.forName(detectedEncoding);
        }
        if (!cs.toString().contains("UTF")) {
            return new Scanner(file, cs.name());
        }
        Path path = file.toPath();
        return new Scanner(new UnicodeBOMInputStream(new BufferedInputStream(Files.newInputStream(path))), cs.name());
    }
    /**
     * Create a scanner from a file with correct encoding. If charset cannot be determined,
     * it uses the system default charset.
     * @param file The file to read from
     * @return Scanner for the file with the correct encoding
     * @throws java.io.IOException if some I/O error ocurrs
     */
    public static Scanner createScanner(File file) throws IOException {
        return createScanner(file, Charset.defaultCharset());
    }
}

我找到了juniversalchardet,但我不确定如何在Scanner中使用它。 - plaidshirt
1
@plaidshirt 我刚刚添加了一个ScannerFactory,可以为您生成扫描器。 - Friwi
请问您能否给我一个使用示例?首先,我无法解决 UniversalDetector.detectCharset() - plaidshirt

1
你的方法无法获得正确的编码。
 FileInputStream fis = new FileInputStream(my_file);
 BufferedReader br = new BufferedReader(new InputStreamReader(fis));
 InputStreamReader isr = new InputStreamReader(fis);
 isr.getEncoding();

这将返回该InputStream使用的编码(请参阅javadoc),而不是文件中所写字符(在您的情况下为my_file)的编码。如果编码错误,Scanner将无法正确读取文件。
事实上,请纠正我如果我错了,没有一种方法可以100%准确地获取特定文件使用的编码。有一些项目在猜测编码方面具有更好的成功率,但并非100%准确。另一方面,如果您知道使用的编码,则可以使用以下方式读取文件:
Scanner scanner = new Scanner(my_file, "charset");
scanner.nextLine();

同时,找出在Java中用于ANSI的正确字符集名称。它可以是US-ASCII或Cp1251。

无论你选择哪个路径,请注意任何可能指向正确方向的IOException


我尝试了Cp1252和Cp1251这两种编码,但输出结果中没有找到该字符串。 - plaidshirt
@plaidshirt,你能分享一下你想读取的样本文本以及相关代码吗? - Vicky Singh
这并不取决于文本,因为每次格式都是相同的(键:值)。唯一的区别在于这些文件之间的编码类型。 - plaidshirt
如答案所述,您无法仅通过查看文件来获取编码。如果您知道结果,因为您说它以相同的方式格式化,您可以尝试一种编码并查看是否符合您的模式,并对其他编码执行此操作。这很昂贵,而且会极其糟糕地扩展,但可以用于少量文件。但这是正确的方法吗?也许尝试从不同的角度看问题,并找出如何以另一种编码获取数据。 - sbstnzmr
@sezi80:文件的编码是预定义的,因此没有其他解决方案。 - plaidshirt

0
为了使Scanner能够与不同的编码一起使用,您必须向扫描仪的构造函数提供正确的编码。
要定义文件编码,最好使用外部库(例如https://github.com/albfernandez/juniversalchardet)。但是,如果您确切地知道可能的编码,可以根据Wikipedia手动检查它。
public static void main(String... args) throws IOException {
    List<String> lines = readLinesFromFile(new File("d:/utf8.txt"));
}

public static List<String> readLinesFromFile(File file) throws IOException {
    try (Scanner scan = new Scanner(file, getCharsetName(file))) {
        List<String> lines = new LinkedList<>();

        while (scan.hasNext())
            lines.add(scan.nextLine());

        return lines;
    }
}

private static String getCharsetName(File file) throws IOException {
    try (InputStream in = new FileInputStream(file)) {
        if (in.read() == 0xEF && in.read() == 0xBB && in.read() == 0xBF)
            return StandardCharsets.UTF_8.name();
        return StandardCharsets.US_ASCII.name();
    }
}

它返回所有文件的"US_ASCII",但输出不正确。 - plaidshirt
你的UTF文件开头没有标记。 - oleg.cherednik

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