Cách dễ nhất để phân tích cú pháp tệp INI trong Java là gì?


104

Tôi đang viết một trình thay thế thả vào cho một ứng dụng cũ trong Java. Một trong những yêu cầu là các tệp ini mà ứng dụng cũ đã sử dụng phải được đọc nguyên trạng trong Ứng dụng Java mới. Định dạng của tệp ini này là kiểu cửa sổ phổ biến, với các phần tiêu đề và cặp key = value, sử dụng # làm ký tự để nhận xét.

Tôi đã thử sử dụng lớp Thuộc tính từ Java, nhưng tất nhiên điều đó sẽ không hoạt động nếu có xung đột tên giữa các tiêu đề khác nhau.

Vì vậy, câu hỏi là, cách dễ nhất để đọc tệp INI này và truy cập các khóa là gì?

Câu trả lời:


121

Thư viện tôi đã sử dụng là ini4j . Nó nhẹ và phân tích cú pháp các tệp ini một cách dễ dàng. Ngoài ra, nó không sử dụng phụ thuộc bí truyền cho 10.000 tệp jar khác, vì một trong những mục tiêu thiết kế là chỉ sử dụng API Java tiêu chuẩn

Đây là một ví dụ về cách sử dụng thư viện:

Ini ini = new Ini(new File(filename));
java.util.prefs.Preferences prefs = new IniPreferences(ini);
System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));

2
không hoạt động, lỗi cho biết "IniFile không thể được giải quyết thành một loại"
Caballero

@Caballero vâng dường như IniFilelớp đã được đưa ra, hãy thửIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly

2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html có thể sẽ luôn cập nhật ngay cả khi họ thay đổi lại tên lớp.
Lokathor

Điều này có còn hoạt động nữa không? Đã tải xuống mã nguồn 0.5.4 và nó thậm chí không được xây dựng, và nó không phải là phần phụ thuộc bị thiếu .. không đáng để bạn phải bận tâm đến nó nhiều hơn. Ngoài ra ini4j còn có rất nhiều thứ tào lao khác mà chúng ta không cần, chỉnh sửa registry Windoze ... thôi. #LinuxMasterRace ... Nhưng tôi đoán nếu nó phù hợp với bạn, hãy đánh gục chính mình.
Người dùng

Đối với tệp INI tôi đã viết, tôi phải sử dụng Winilớp, như được minh họa trong hướng dẫn "Một phút"; những prefs.node("something").get("val", null)không làm việc theo cách tôi mong đợi.
Agi Hammerthief

65

Như đã đề cập , ini4j có thể được sử dụng để đạt được điều này. Hãy để tôi đưa ra một ví dụ khác.

Nếu chúng ta có tệp INI như thế này:

[header]
key = value

Thông tin sau sẽ hiển thị valuethành STDOUT:

Ini ini = new Ini(new File("/path/to/file"));
System.out.println(ini.get("header", "key"));

Kiểm tra các hướng dẫn để biết thêm ví dụ.


2
Khéo léo! Tôi luôn chỉ sử dụng BufferedReader và một chút sao chép / dán mã phân tích cú pháp Chuỗi để không phải thêm phụ thuộc nào khác vào các ứng dụng của mình (điều đó có thể bị lệch tỷ lệ khi bạn bắt đầu thêm vào các API của bên thứ ba cho các tác vụ đơn giản nhất ). Nhưng tôi không thể bỏ qua kiểu đơn giản này.
Gimby

30

Đơn giản như 80 dòng:

package windows.prefs;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IniFile {

   private Pattern  _section  = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*" );
   private Pattern  _keyValue = Pattern.compile( "\\s*([^=]*)=(.*)" );
   private Map< String,
      Map< String,
         String >>  _entries  = new HashMap<>();

   public IniFile( String path ) throws IOException {
      load( path );
   }

   public void load( String path ) throws IOException {
      try( BufferedReader br = new BufferedReader( new FileReader( path ))) {
         String line;
         String section = null;
         while(( line = br.readLine()) != null ) {
            Matcher m = _section.matcher( line );
            if( m.matches()) {
               section = m.group( 1 ).trim();
            }
            else if( section != null ) {
               m = _keyValue.matcher( line );
               if( m.matches()) {
                  String key   = m.group( 1 ).trim();
                  String value = m.group( 2 ).trim();
                  Map< String, String > kv = _entries.get( section );
                  if( kv == null ) {
                     _entries.put( section, kv = new HashMap<>());   
                  }
                  kv.put( key, value );
               }
            }
         }
      }
   }

   public String getString( String section, String key, String defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return kv.get( key );
   }

   public int getInt( String section, String key, int defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Integer.parseInt( kv.get( key ));
   }

   public float getFloat( String section, String key, float defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Float.parseFloat( kv.get( key ));
   }

   public double getDouble( String section, String key, double defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Double.parseDouble( kv.get( key ));
   }
}

+1 Đơn giản chỉ để sử dụng Regex Pattern / Matcher. Công trình như một say mê
kalelien

Không phải là một giải pháp hoàn hảo nhưng một điểm khởi đầu tốt, ví dụ: thiếu getSection () và getString () chỉ trả về defaultValue nếu thiếu toàn bộ phần.
Jack Miller

Sự khác biệt về hiệu suất giữa regx như vậy so với làm việc với triển khai chuỗi là gì?
Ewoks

Hiệu suất khi đọc một tệp cấu hình nhỏ không phải là mối quan tâm. Tôi tin rằng việc mở và đóng một tập tin sẽ tốn nhiều công sức hơn.
Aerospace

Đúng, điều này cũng đơn giản như nó nên dành cho các trường hợp sử dụng đơn giản. Không chắc tại sao mọi người lại muốn làm phức tạp nó. Nếu bạn lo lắng về hiệu suất (hoặc các mối quan tâm khác như báo cáo lỗi), vâng, bạn có thể muốn sử dụng một cái gì đó khác (có thể là một định dạng hoàn toàn khác).
Người dùng

16

Dưới đây là một ví dụ đơn giản nhưng mạnh mẽ, sử dụng cấu hình HierarchicalINIConfiguration của lớp apache :

HierarchicalINIConfiguration iniConfObj = new HierarchicalINIConfiguration(iniFile); 

// Get Section names in ini file     
Set setOfSections = iniConfObj.getSections();
Iterator sectionNames = setOfSections.iterator();

while(sectionNames.hasNext()){

 String sectionName = sectionNames.next().toString();

 SubnodeConfiguration sObj = iniObj.getSection(sectionName);
 Iterator it1 =   sObj.getKeys();

    while (it1.hasNext()) {
    // Get element
    Object key = it1.next();
    System.out.print("Key " + key.toString() +  " Value " +  
                     sObj.getString(key.toString()) + "\n");
}

Cấu hình Commons có một số phụ thuộc thời gian chạy . Ở mức tối thiểu, ghi nhật ký commons-langcommons là bắt buộc. Tùy thuộc vào những gì bạn đang làm với nó, bạn có thể yêu cầu các thư viện bổ sung (xem liên kết trước để biết chi tiết).


1
Đây sẽ là câu trả lời chính xác của tôi. Rất đơn giản để sử dụng và linh hoạt.
marcolopes

cấu hình chung không phải là tập hợp.
jantox

13

Hoặc với API Java tiêu chuẩn, bạn có thể sử dụng java.util .

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}

12
Vấn đề là, với các tệp ini, cấu trúc có tiêu đề. Lớp Thuộc tính không biết cách xử lý các tiêu đề và có thể xảy ra xung đột tên
Mario Ortegón 13/10/08

2
Ngoài ra, Propertieslớp không nhận đúng các giá trị chứa \
rds

3
+1 cho giải pháp đơn giản nhưng chỉ phù hợp với các tệp cấu hình đơn giản, như Mario Ortegon và rds đã nhận thấy điều đó.
Benj

1
Tệp INI chứa [phần], tệp thuộc tính chứa các bài tập.
Hàng không vũ trụ


10

Trong 18 dòng, mở rộng java.util.Propertiesđể phân tích cú pháp thành nhiều phần:

public static Map<String, Properties> parseINI(Reader reader) throws IOException {
    Map<String, Properties> result = new HashMap();
    new Properties() {

        private Properties section;

        @Override
        public Object put(Object key, Object value) {
            String header = (((String) key) + " " + value).trim();
            if (header.startsWith("[") && header.endsWith("]"))
                return result.put(header.substring(1, header.length() - 1), 
                        section = new Properties());
            else
                return section.put(key, value);
        }

    }.load(reader);
    return result;
}

2

Một tùy chọn khác là Apache Commons Config cũng có một lớp để tải từ các tệp INI . Nó có một số phụ thuộc thời gian chạy , nhưng đối với các tệp INI, nó chỉ yêu cầu bộ sưu tập Commons, lang và ghi nhật ký.

Tôi đã sử dụng Commons Config trên các dự án có thuộc tính và cấu hình XML của chúng. Nó rất dễ sử dụng và hỗ trợ một số tính năng khá mạnh mẽ.



2

Cá nhân tôi thích Nho giáo hơn .

Nó rất hay, vì nó không yêu cầu bất kỳ phụ thuộc bên ngoài nào, nó rất nhỏ - chỉ 16K và tự động tải tệp ini của bạn khi khởi tạo. Ví dụ

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);

3 năm sau, Mark và OP có lẽ đã chết vì tuổi già ... nhưng đây là một phát hiện thực sự tốt.
Người dùng

6
Tôi sử dụng một cây gậy để đi lại, nhưng vẫn còn sống và kickin'
Mario Ortegón

@ MarioOrtegón: Thật tuyệt khi nghe điều đó!
ישו אוהב אותך

0

Giải pháp của hoat4 rất thanh lịch và đơn giản. Nó hoạt động cho tất cả các tệp ini lành mạnh . Tuy nhiên, tôi đã thấy nhiều có ký tự khoảng trắng chưa thoát trong khóa .
Để giải quyết vấn đề này, tôi đã tải xuống và sửa đổi một bản sao của java.util.Properties. Mặc dù đây là một chút không chính thống và ngắn hạn, các bản mod thực tế chỉ có một vài dòng và khá đơn giản. Tôi sẽ đưa ra một đề xuất cho cộng đồng JDK để bao gồm các thay đổi.

Bằng cách thêm một biến lớp bên trong:

private boolean _spaceCharOn = false;

Tôi kiểm soát quá trình xử lý liên quan đến việc quét tìm điểm phân tách khóa / giá trị. Tôi đã thay thế mã tìm kiếm ký tự khoảng trắng bằng một phương thức private nhỏ trả về boolean tùy thuộc vào trạng thái của biến trên.

private boolean isSpaceSeparator(char c) {
    if (_spaceCharOn) {
        return (c == ' ' || c == '\t' || c == '\f');
    } else {
        return (c == '\t' || c == '\f');
    }
}

Phương pháp này được sử dụng ở hai nơi trong phương thức private load0(...).
Cũng có một phương pháp công khai để bật tính năng này, nhưng sẽ tốt hơn nếu sử dụng phiên bản gốc của Propertiesnếu dấu phân cách không gian không phải là vấn đề đối với ứng dụng của bạn.

Nếu có hứng thú, tôi sẵn sàng đăng mã vào IniFile.javatệp của mình . Nó hoạt động với một trong hai phiên bản của Properties.


0

Sử dụng câu trả lời của @Aerospace, tôi nhận ra rằng các tệp INI có các phần không có bất kỳ khóa-giá trị nào là hợp pháp. Trong trường hợp này, việc bổ sung vào bản đồ cấp cao nhất sẽ xảy ra trước khi tìm thấy bất kỳ khóa-giá trị nào, ví dụ: (được cập nhật tối thiểu cho Java 8):

            Path location = ...;
            try (BufferedReader br = new BufferedReader(new FileReader(location.toFile()))) {
                String line;
                String section = null;
                while ((line = br.readLine()) != null) {
                    Matcher m = this.section.matcher(line);
                    if (m.matches()) {
                        section = m.group(1).trim();
                        entries.computeIfAbsent(section, k -> new HashMap<>());
                    } else if (section != null) {
                        m = keyValue.matcher(line);
                        if (m.matches()) {
                            String key = m.group(1).trim();
                            String value = m.group(2).trim();
                            entries.get(section).put(key, value);
                        }
                    }
                }
            } catch (IOException ex) {
                System.err.println("Failed to read and parse INI file '" + location + "', " + ex.getMessage());
                ex.printStackTrace(System.err);
            }

-2

Nó chỉ đơn giản như thế này .....

//import java.io.FileInputStream;
//import java.io.FileInputStream;

Properties prop = new Properties();
//c:\\myapp\\config.ini is the location of the ini file
//ini file should look like host=localhost
prop.load(new FileInputStream("c:\\myapp\\config.ini"));
String host = prop.getProperty("host");

1
Điều này không xử lý các phần INI
Igor Melnichenko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.