天天看點

Win和Linux下JAVA預設編碼問題導緻的亂碼解決

項目和起因

項目

  • 一個類似于Server醬的消息推送應用,隻需通過一條URL即可給指定通道發送資訊,可以用來發送告警、伺服器狀态、腳本運作狀态等資訊,約等于以前很多人用的郵件通知。
  • 目前隻寫了企業微信應用的通道,因為企業微信應用能在微信顯示,而微信最常用基本不會關。
  • 以後應該會增加公衆号、釘釘等通道,再看看要不要支援多人的。

遇到的問題

  • 版本:Java 1.8.0_333
  • 在Linux下使用正常,在Windows下發送中文會不顯示或者亂碼。

問題原因和解決

原因

  • Windows和Linux下Java預設編碼不同的問題。
  • Windows下預設編碼是GBK,Linux下預設編碼是UTF-8。
  • 這個對新手來說挺坑的,以前一直聽說Java跨平台好,沒想到能遇到這種問題。
  • 在這之前用Python寫過一個Dome就沒遇到這種問題。
  • 可以用這段代碼測試目前環境Java的預設編碼
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
 
class Test {
    public static void main(String[] args) {
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.out.println("file.encoding=" + System.getProperty("file.encoding"));
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.out.println("Default Charset in Use=" + getDefaultCharSet());
    }
 
    private static String getDefaultCharSet() {
        OutputStreamWriter writer = new OutputStreamWriter(new ByteArrayOutputStream());
        String enc = writer.getEncoding();
        return enc;
    }
 
}           

複制

探究和解決

探索粗記錄

項目基本邏輯

  1. 用SpringBoot寫一個API,用來接收請求,例如:

    http://127.0.0.1:8080/qw?msg=你好&token=123

  2. 對比token,如果token與預設的不同傳回錯誤資訊,不給使用API。
  3. token相同,調用企業微信API把

    msg

    的資訊推送到手機。
其中發送POST、GET請求的類如下:
package hello;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by chengxia on 2018/12/4.
 */
public class HttpURLConnectionWX {
    public String doPost(String URL,String jsonStr){
        OutputStreamWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        HttpURLConnection conn = null;
        try{
            URL url = new URL(URL);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            //發送POST請求必須設定為true
            conn.setDoOutput(true);
            conn.setDoInput(true);
            //設定連接配接逾時時間和讀取逾時時間
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(10000);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Accept", "application/json");
            //擷取輸出流
            out = new OutputStreamWriter(conn.getOutputStream());
            out.write(jsonStr);
            out.flush();
            out.close();
            //取得輸入流,并使用Reader讀取
            if (200 == conn.getResponseCode()){
                in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
                String line;
                while ((line = in.readLine()) != null){
                    result.append(line);
                    System.out.println(line);
                }
            }else{
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                if(out != null){
                    out.close();
                }
                if(in != null){
                    in.close();
                }
            }catch (IOException ioe){
                ioe.printStackTrace();
            }
        }
        return result.toString();
    }

    public String doGet(String URL){
        HttpURLConnection conn = null;
        InputStream is = null;
        BufferedReader br = null;
        StringBuilder result = new StringBuilder();
        try{
            //建立遠端url連接配接對象
            URL url = new URL(URL);
            //通過遠端url連接配接對象打開一個連接配接,強轉成HTTPURLConnection類
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            //設定連接配接逾時時間和讀取逾時時間
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            conn.setRequestProperty("Accept", "application/json");
            //發送請求
            conn.connect();
            //通過conn取得輸入流,并使用Reader讀取
            if (200 == conn.getResponseCode()){
                is = conn.getInputStream();
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String line;
                while ((line = br.readLine()) != null){
                    result.append(line);
                    System.out.println(line);
                }
            }else{
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        }catch (MalformedURLException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                if(br != null){
                    br.close();
                }
                if(is != null){
                    is.close();
                }
            }catch (IOException ioe){
                ioe.printStackTrace();
            }
            conn.disconnect();
        }
        return result.toString();
    }


    public static void main(String[] args) throws Exception {
        // 測試用
        new HttpURLConnectionWX().doPost("http://127.0.0.1:5000/qwtx", new String("{\"name\":\"你好\"}".getBytes("UTF-8")));

    }

}           

複制

嘗試

  • 使用

    new String(msg.getBytes("UTF-8"))

    進行轉碼,無效。
  • 使用

    new String(msg.getBytes("GBK"))

    進行轉碼,無效。
  • 更改POST請求函數中的

    in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));

    為GBK,無效。
上面的

無效

指的是以下效果:直接是空的(一般是純文字資訊會遇到)、前部分文字能顯示最後一個是亂碼(一般是文字+數字/英文)、全是亂碼(瞎改代碼裡面的編碼轉換後遇到的)

更多奇怪的嘗試就不說了,當時已經知道通過加參數運作可以指定編碼,但是感覺那樣還得按照系統改指令不夠人性化,就一直在嘗試。

最後還是放棄了,沒找到方法,等以後真正系統學了Java再說吧。

  • 最後放個輔助測試的Python腳本
import charset_normalizer
from flask import Flask, request
import json

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'hello world'

@app.route('/qw', methods=['POST'])
def register():
    byu = request.stream.read()
    # print(request.headers)
    print(charset_normalizer.detect(byu))
    try:
        print(str(byu ,encoding='GBK'))
    except:
        print('GBK ERR')
    try:
        print(str(byu,encoding='UTF-8'))
    except:
        print('UTF-8 ERR')
    return 'welcome'

if __name__ == '__main__':
    app.run(port=5000, debug=True)           

複制

解決
參考:設定Java JDK的預設編碼為UTF-8_lc11535的部落格-CSDN部落格_java設定utf-8
  1. 添加一個名為

    JAVA_TOOL_OPTIONS

    的系統環境變量,變量值為

    -Dfile.encoding=UTF-8

    ,參考官網說明。
  2. 每次運作時都加一個

    -Dfile.encoding=UTF-8

    的參數。

    如果是添加系統環境變量,添加完後需要重新開機CMD視窗才生效,可以用開頭的檢測預設編碼的代碼測試看看是否生效

未嘗試:看B站有回複說Java 18把Win和Linux的預設編碼都改成UTF-8了