沒提供編碼格式,讀文件時要怎麼推測文件具體的編碼

引子

咱們知道從一個文件流中讀取內容時是要指定具體的編碼格式的,不然讀出來的內容會是亂碼。好比咱們的代碼寫成下面這個樣子:html

private static void m1(){
    try(FileInputStream fileInputStream = new FileInputStream("D:\\每日摘錄.txt")) {
        byte[] bytes = FileCopyUtils.copyToByteArray(fileInputStream);
        System.out.println(new String(bytes));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

執行上面的代碼,有時咱們能「僥倖」獲得正確的執行結果。由於new String(byte[])這個方法會指定默認的編碼格式,因此若是咱們讀取的文件的編碼格式正好是UTF8的話,那上面的代碼就一點問題沒有。可是若是咱們讀取的是一個編碼格式是GBK的文件,那麼獲得的內容將是一坨亂碼。java

上面的問題解決起來很簡單,只要指定下字符編碼就能夠了。正則表達式

new String(bytes,"GBK");

在告知文件編碼格式的條件下,解決上面的問題是很簡單。假如如今沒告知文件具體的編碼格式,咱們須要怎麼正確的讀取文件呢?一個可行的辦法是推測文件編碼方式。算法

推測文件編碼的方式

網上有多種方式能夠「推測」出一個文件的可用編碼,可是須要注意的是:全部的方法都不能保證推測出來的結果是絕對準確的,有的方法推測的準確率較高,而有的方法推測出來的準確率較低。主要的推測方法有如下幾種:apache

  • 經過文件的前三個字節來判斷:由於有些編碼格式會存在文件的前面3個字節中,好比UTF-8編碼格式的文本文件,其前3個字節的值就是-1七、-6九、-65。可是很明顯,這種方式的侷限性比較大,推測出來的準確率也比較低,所以不推薦這種方式。
  • 經過特殊字符來判斷:經過某些編碼格式編碼的文件中會出現一些特殊的字節值,所以能夠經過判斷文件中是否有這些特殊值來推測文件編碼格式。此方準確率也不高,不推薦使用。
  • 經過工具庫cpdetector來判斷:cpdector 是一款開源的文檔編碼檢測工具,能夠檢測 xml,html文檔編碼類型。是基於統計學原理來推測文件編碼的,可是也不保證推測結果的準確性。
  • 經過ICU4J庫來判斷:ICU的推測邏輯基於IBM過去幾十年收集的字符集數據,理論上也是基於統計學的。這種方式統計的結果準確性也較高推薦使用。

下面就來具體介紹下怎麼使用cpdectorICU4J推測文件編碼。工具

cpdector

使用Cpdetector jar包,提供兩種方式檢測文件編碼,至於選擇哪一種 須要根據我的需求,文檔有註釋。依賴antlr-2.7.4.jar,chardet-1.0.jar,jargs-1.0.jar三個jar包。 能夠再官網下載 http://cpdetector.sourceforge.net/。性能

import info.monitorenter.cpdetector.io.ASCIIDetector;
import info.monitorenter.cpdetector.io.ByteOrderMarkDetector;
import info.monitorenter.cpdetector.io.CodepageDetectorProxy;
import info.monitorenter.cpdetector.io.JChardetFacade;
import info.monitorenter.cpdetector.io.ParsingDetector;
import info.monitorenter.cpdetector.io.UnicodeDetector;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import org.apache.log4j.Logger;

/**
 * <p>
 *  獲取流編碼,不保證徹底正確,設置檢測策略 isFast爲true爲快速檢測策略,false爲正常檢測
 *  InputStream 支持mark,則會在檢測後調用reset,外部可從新使用。
 *  InputStream 流沒有關閉。
 * </p>
 * 
 * <p>
 *  若是採用快速檢測編碼方式,最多會掃描8個字節,依次採用的{@link UnicodeDetector},{@link byteOrderMarkDetector},
 *  {@link JChardetFacade}, {@link ASCIIDetector}檢測。對於一些標準的unicode編碼,適合這個方式或者對耗時敏感的。
 * </p>
 * 
 * <p>
 *  採用正常檢測,讀取指定字節數,若是沒有指定,默認讀取所有字節檢測,依次採用的{@link byteOrderMarkDetector},{@link parsingDetector},{@link JChardetFacade}, {@link ASCIIDetector}檢測。
 *  字節越多檢測時間越長,正確率較高。
 * </p>
 * @author WuKong
 *
 */
public class CpdetectorEncoding {
    
    private static final Logger logger = Logger.getLogger(CpdetectorEncoding.class);
    
    /**
     * <p>
     * 獲取流編碼,不保證徹底正確,設置檢測策略 isFast爲true爲快速檢測策略,false爲正常檢測
     * InputStream 支持mark,則會在檢測後調用reset,外部可從新使用。
     * InputStream 流沒有關閉。
     * </p>
     * 
     * <p>
     * 若是採用快速檢測編碼方式,最多會掃描8個字節,依次採用的{@link UnicodeDetector},{@link byteOrderMarkDetector},
     * {@link JChardetFacade}, {@link ASCIIDetector}檢測。對於一些標準的unicode編碼,適合這個方式或者對耗時敏感的。
     * </p>
     * 
     * <p>
     *  採用正常檢測,讀取指定字節數,若是沒有指定,默認讀取所有字節檢測,依次採用的{@link byteOrderMarkDetector},{@link parsingDetector},{@link JChardetFacade}, {@link ASCIIDetector}檢測。
     *  字節越多檢測時間越長,正確率較高。
     * </p>
     *
     * @param in 輸入流  isFast 是否採用快速檢測編碼方式
     * @return Charset The character are now - hopefully - correct。若是爲null,沒有檢測出來。
     * @throws IOException 
     */
    public Charset getEncoding(InputStream buffIn,boolean isFast) throws IOException{
        
        return getEncoding(buffIn,buffIn.available(),isFast);
    }
    
    public Charset getFastEncoding(InputStream buffIn) throws IOException{
        return getEncoding(buffIn,MAX_READBYTE_FAST,DEFALUT_DETECT_STRATEGY);
    }
    
    
    
    public Charset getEncoding(InputStream in, int size, boolean isFast) throws IOException {
        
        try {
            
            java.nio.charset.Charset charset = null;
            
            int tmpSize = in.available();
            size = size >tmpSize?tmpSize:size;
            //if in support mark method, 
            if(in.markSupported()){
                
                if(isFast){
                    
                    size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size;
                    in.mark(size++);
                    charset = getFastDetector().detectCodepage(in, size);
                }else{
                    
                    in.mark(size++);
                    charset = getDetector().detectCodepage(in, size);
                }
                in.reset();
                
            }else{
                
                if(isFast){
                    
                    size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size;
                    charset = getFastDetector().detectCodepage(in, size);
                }else{
                    charset = getDetector().detectCodepage(in, size);
                }
            }
            
            
            return charset;
        }catch(IllegalArgumentException e){
            
            logger.error(e.getMessage(),e);
            throw e;
        } catch (IOException e) {
            
            logger.error(e.getMessage(),e);
            throw e;
        }
        
    }
    
    
    public Charset getEncoding(byte[] byteArr,boolean isFast) throws IOException{
        
        return getEncoding(byteArr, byteArr.length, isFast);
    }
    
    
    public Charset getFastEncoding(byte[] byteArr) throws IOException{
        
        return getEncoding(byteArr, MAX_READBYTE_FAST, DEFALUT_DETECT_STRATEGY);
    }
    
    
    public Charset getEncoding(byte[] byteArr, int size,boolean isFast) throws IOException {
        
        size = byteArr.length>size?size:byteArr.length;
        if(isFast){
            size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size;
        }
        
        ByteArrayInputStream byteArrIn = new ByteArrayInputStream(byteArr,0,size);
        BufferedInputStream in = new BufferedInputStream(byteArrIn);
        
        try {
            
            Charset charset = null;
            if(isFast){
                
                charset = getFastDetector().detectCodepage(in, size);
            }else{
                
                charset = getDetector().detectCodepage(in, size);
            }
            
            return charset;
        } catch (IllegalArgumentException e) {
            
            logger.error(e.getMessage(),e);
            throw e;
        } catch (IOException e) {
            
            logger.error(e.getMessage(),e);
            throw e;
        }
       
    }
    
    private static CodepageDetectorProxy detector =null;
    private static CodepageDetectorProxy fastDtector =null;
    private static ParsingDetector parsingDetector =  new ParsingDetector(false);
    private static ByteOrderMarkDetector byteOrderMarkDetector = new ByteOrderMarkDetector();
    
    //default strategy use fastDtector
    private static final boolean DEFALUT_DETECT_STRATEGY = true;
    
    private static final int MAX_READBYTE_FAST = 8; 
    
    private static CodepageDetectorProxy getDetector(){
        
        if(detector==null){
            
            detector = CodepageDetectorProxy.getInstance();
             // Add the implementations of info.monitorenter.cpdetector.io.ICodepageDetector: 
            // This one is quick if we deal with unicode codepages:
            detector.add(byteOrderMarkDetector);
            // The first instance delegated to tries to detect the meta charset attribut in html pages.
            detector.add(parsingDetector);
            // This one does the tricks of exclusion and frequency detection, if first implementation is 
            // unsuccessful:
            detector.add(JChardetFacade.getInstance());
            detector.add(ASCIIDetector.getInstance());
        }
        
        return detector;
    }
    
    
    private static CodepageDetectorProxy getFastDetector(){
        
        if(fastDtector==null){
            
            fastDtector = CodepageDetectorProxy.getInstance();
            fastDtector.add(UnicodeDetector.getInstance());
            fastDtector.add(byteOrderMarkDetector); 
            fastDtector.add(JChardetFacade.getInstance());
            fastDtector.add(ASCIIDetector.getInstance());
        }
        
        return fastDtector;
    }
    
}

ICU4J

ICU (International Components for Unicode)是爲軟件應用提供Unicode和全球化支持的一套成熟、普遍使用的C/C++和Java類庫集,可在全部平臺的C/C++和Java軟件上得到一致的結果。ui

ICU首先是由Taligent公司開發的,Taligent公司被合併爲IBM公司全球化認證中心的Unicode研究組後,ICU由IBM和開源組織合做繼續開發。開始ICU只有Java平臺的版本,後來這個平臺下的ICU類被吸歸入SUN公司開發的JDK1.1,並在JDK之後的版本中不斷改進。C++和C平臺下的ICU是由JAVA平臺下的ICU移植過來的,移植過的版本被稱爲ICU4C,來支持這C/C++兩個平臺下的國際化應用。ICU4J和ICU4C區別不大,但因爲ICU4C是開源的,而且緊密跟進Unicode標準,ICU4C支持的Unicode標準老是最新的;同時,由於JAVA平臺的ICU4J的發佈須要和JDK綁定,ICU4C支持Unicode標準改變的速度要比ICU4J快的多。編碼

ICU的功能主要有:.net

  • 代碼頁轉換: 對文本數據進行Unicode、幾乎任何其餘字符集或編碼的相互轉換。ICU的轉化表基於IBM過去幾十年收集的字符集數據,在世界各地都是最完整的。
  • 排序規則(Collation): 根據特定語言、區域或國家的管理和標準比較字數串。ICU的排序規則基於Unicode排序規則算法加上來自公共區域性數據倉庫(Common locale data repository)的區域特定比較規則。
  • 格式化: 根據所選區域設置的慣例,實現對數字、貨幣、時間、日期、和利率的格式化。包括將月和日名稱轉換成所選語言、選擇適當縮寫、正確對字段進行排序等。這些數據也取自公共區域性數據倉庫。
  • 時間計算: 在傳統格里曆基礎上提供多種曆法。提供一整套時區計算API。
  • Unicode支持: ICU緊密跟進Unicode標準,經過它能夠很容易地訪問Unicode標準制定的不少Unicode字符屬性、Unicode規範化、大小寫轉換和其餘基礎操做。
  • 正則表達式: ICU的正則表達式全面支持Unicode而且性能極具競爭力。
  • Bidi: 支持不一樣文字書寫順序混合文字(例如從左到右書寫的英語,或者從右到左書寫的阿拉伯文和希伯來文)的處理。
  • 文本邊界: 在一段文本內定位詞、句或段落位置、或標識最適合顯示文本的自動換行位置。

代碼示例:

public class FileEncodingDetector {

    public static void main(String[] args) {
        File file = new File("D:\\xx1.log");
        System.out.println(getFileCharsetByICU4J(file));
    }

    public static String getFileCharsetByICU4J(File file) {
        String encoding = null;

        try {
            Path path = Paths.get(file.getPath());
            byte[] data = Files.readAllBytes(path);
            CharsetDetector detector = new CharsetDetector();
            detector.setText(data);
            //這個方法推測首選的文件編碼格式
            CharsetMatch match = detector.detect();
            //這個方法能夠推測出全部可能的編碼方式
            CharsetMatch[] charsetMatches = detector.detectAll();
            if (match == null) {
                return encoding;
            }
            encoding = match.getName();
        } catch (IOException var6) {
            System.out.println(var6.getStackTrace());
        }
        return encoding;
    }
}

注意點

  • ICU4J和cpdector推測出來的文件編碼都不能保證百分百準確,只能保證大機率準確;
  • ICU4J和cpdector推測出來的編碼不必定是文件原始的編碼。好比個人一個文本文件中只有簡單的英文字符,而後我將這個文件存爲GBK編碼格式。這時你使用這兩個工具推測出來的文件編碼多是ASCII編碼。可是使用ASCII編碼也能正確打開這個文件,由於GBK是兼容ASCII的。因此能看出,這兩個工具都是以能正確解碼文件爲原則來推測編碼的,不必定要推測出原始編碼。

參考

相關文章
相關標籤/搜索