首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Properties 配置檔完整操作指南:讀寫、路徑解析、編碼坑與實戰工具類

     編輯:Java 相關知識

前言

.properties 是 Java 生態最經典的配置檔格式,採「鍵=值」純文字結構,支援註解(# 或 !),廣泛用於資料庫連線、系統參數、國際化資源等場景。本文系統整理 Properties 核心 API、多種載入路徑策略、讀寫完整代碼、Classpath 機制深度解析,並提供開箱即用的工具類。

一、Properties 檔案結構範例

#################################
# 工商報表應用 IcisReport 配置文件
# 日期:2006年11月21日
#################################
# 說明:業務系統與報表系統分離部署
# IcisReport 的 IP
IcisReport.server.ip=192.168.3.143
# IcisReport 的埠號
IcisReport.server.port=8080
# 上下文路徑
IcisReport.contextPath=/IcisReport

二、Properties 核心方法速查

方法說明
getProperty(String key)依鍵取值,找不到回傳 null
getProperty(String key, String defaultValue)指定預設值
load(InputStream inStream)從輸入流載入鍵值對(ISO-8859-1 編碼)
load(Reader reader)從字元流載入(可指定 UTF-8 等編碼)Java 1.6+
setProperty(String key, String value)設定鍵值(同步至內部 Hashtable)
store(OutputStream out, String comments)寫入輸出流,標頭寫入 comments
store(Writer writer, String comments)字元流寫入(可指定編碼)Java 1.6+
clear()清空所有鍵值
keySet() / propertyNames()取得所有鍵集合

三、四種載入路徑策略與適用場景

  1. Class.getResourceAsStream("/pkg/file.properties")
    相對於 Classpath 根目錄(/ 開頭),適用於 JAR 內部資源、Maven src/main/resources。
  2. ClassLoader.getResourceAsStream("pkg/file.properties")
    相對於 Classpath 根目錄(不加 /),通用性最強,推薦首選。
  3. new FileInputStream("相對/絕對路徑")
    直接讀取檔案系統路徑,適用於外部化配置(如 /etc/app/config.properties),不受 Classpath 限制。
  4. ResourceBundle.getBundle("pkg.file")
    專為國際化設計,自動依 Locale 尋找 file_zh_TW.properties 等變體,不支援寫入,且快取機制導致修改不即時生效。

四、完整讀寫工具類(支援 UTF-8、自動建立檔案、執行緒安全)

package com.example.util;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public final class PropertiesUtil {
    private PropertiesUtil() {}

    /** 讀取單一鍵值 */
    public static String get(String path, String key) {
        try (InputStream in = resolveInputStream(path)) {
            Properties props = new Properties();
            props.load(in);
            return props.getProperty(key);
        } catch (IOException e) {
            throw new RuntimeException("讀取配置失敗: " + path, e);
        }
    }

    /** 讀取全部鍵值 */
    public static Map<String, String> getAll(String path) {
        try (InputStream in = resolveInputStream(path)) {
            Properties props = new Properties();
            props.load(in);
            Map<String, String> result = new ConcurrentHashMap<>();
            for (String key : props.stringPropertyNames()) {
                result.put(key, props.getProperty(key));
            }
            return result;
        } catch (IOException e) {
            throw new RuntimeException("讀取配置失敗: " + path, e);
        }
    }

    /** 寫入/更新鍵值(自動建立檔案與父目錄) */
    public static void set(String path, String key, String value, String comment) {
        File file = new File(path);
        Properties props = new Properties();
        
        // 1. 讀取現有內容
        if (file.exists()) {
            try (InputStream in = new FileInputStream(file)) {
                props.load(in);
            } catch (IOException e) {
                throw new RuntimeException("讀取現有配置失敗", e);
            }
        } else {
            file.getParentFile().mkdirs(); // 自動建立目錄
        }

        // 2. 更新值
        props.setProperty(key, value);

        // 3. 寫回檔案(UTF-8)
        try (OutputStream out = new FileOutputStream(file);
             Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
            props.store(writer, comment != null ? comment : "Updated by PropertiesUtil at " + new Date());
        } catch (IOException e) {
            throw new RuntimeException("寫入配置失敗: " + path, e);
        }
    }

    /** 解析輸入流:優先 Classpath,再嘗試檔案系統 */
    private static InputStream resolveInputStream(String path) throws IOException {
        // 1. ClassLoader 根目錄
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
        if (in != null) return in;
        // 2. 檔案系統絕對/相對路徑
        File file = new File(path);
        if (file.exists()) return new FileInputStream(file);
        throw new FileNotFoundException("找不到配置檔: " + path + " (Classpath 或檔案系統均無)");
    }
}

五、Classpath 深度解析:為什麼讀不到檔案?

  • Java 專案:.properties 放在 src/main/resources(Maven)或 src 根目錄,編譯後進入 target/classes,屬於 Classpath。
  • Web 應用 (WAR):放入 WEB-INF/classes 或 WEB-INF/lib 內的 JAR 中。
  • 外部化配置:啟動參數加入 -Dspring.config.location=file:/opt/app/config/ 或手動 new FileInputStream("/opt/app/config/app.properties")。
  • 常見錯誤:
    - 路徑寫成 config.properties 卻放在 src/main/resources/com/example/ 下 → 應用 com/example/config.properties 或移動檔案。
    - 使用 Class.getResourceAsStream("config.properties")(無 /)→ 相對於類所在包,非 Classpath 根。
    - 修改 src/main/resources 下檔案但未重新編譯/打包 → 運行時仍讀舊版本。

六、編碼陷阱與解決方案

  • Properties.load(InputStream) 強制使用 ISO-8859-1,中文會變亂碼(如 \u4e2d\u6587)。
  • 解法:Java 1.6+ 使用 load(Reader) / store(Writer) 搭配 StandardCharsets.UTF_8。
    舊版本需自行用 native2ascii 工具轉義或改用 XML 格式 loadFromXML/storeToXML(天然 UTF-8)。

七、實戰建議

  1. 配置檔外部化,啟動時指定路徑,避免重新打包。
  2. 讀取失敗拋出 受檢異常 或包裝為 RuntimeException,避免靜默失敗。
  3. 高併發場景下,Properties 非執行緒安全,建議快取至 ConcurrentHashMap 或使用 Apache Commons Configuration / Spring @ConfigurationProperties。
  4. 敏感資訊(密碼、金鑰)勿明文存放,應加密或用 Vault/K8s Secret 注入。

結語

掌握 Properties 的載入機制、編碼處理與路徑策略,能解決 90% 的配置讀取疑難雜症。配合上述工具類,即可在專案中統一、安全、高效地管理配置。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved