วิธีที่ง่ายที่สุดในการแยกวิเคราะห์ไฟล์ INI ใน Java คืออะไร?


104

ฉันกำลังเขียนการแทนที่ดร็อปอินสำหรับแอปพลิเคชันเดิมใน Java ข้อกำหนดประการหนึ่งคือไฟล์ ini ที่แอปพลิเคชันรุ่นเก่าใช้จะต้องถูกอ่านใน Java Application ใหม่ รูปแบบของไฟล์ ini นี้เป็นรูปแบบ windows ทั่วไปโดยมีส่วนหัวและคู่คีย์ = ค่าโดยใช้ # เป็นอักขระในการแสดงความคิดเห็น

ฉันลองใช้คลาส Properties จาก Java แต่แน่นอนว่าจะใช้ไม่ได้หากมีการขัดแย้งของชื่อระหว่างส่วนหัวที่แตกต่างกัน

คำถามคืออะไรคือวิธีที่ง่ายที่สุดในการอ่านไฟล์ INI นี้และเข้าถึงคีย์

คำตอบ:


121

ห้องสมุดที่ผมเคยใช้เป็นini4j มีน้ำหนักเบาและแยกวิเคราะห์ไฟล์ ini ได้อย่างง่ายดาย นอกจากนี้ยังไม่ใช้การอ้างอิงที่เป็นความลับกับไฟล์ jar อื่น ๆ อีก 10,000 ไฟล์เนื่องจากหนึ่งในเป้าหมายการออกแบบคือการใช้ Java API มาตรฐานเท่านั้น

นี่คือตัวอย่างวิธีใช้ไลบรารี:

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
ไม่ทำงานข้อผิดพลาดแจ้งว่า "IniFile ไม่สามารถแก้ไขเป็นประเภทได้"
Caballero

@Caballero ใช่ดูเหมือนว่าIniFileคลาสจะถูกนำออกไปลองIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly

2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.htmlอาจจะอัปเดตอยู่เสมอแม้ว่าจะเปลี่ยนชื่อคลาสอีกครั้งก็ตาม
โลก ธ อร์

สิ่งนี้ยังใช้งานได้อีกต่อไปหรือไม่? ดาวน์โหลดแหล่งที่มา 0.5.4 แล้วและมันไม่ได้สร้างและมันก็ไม่ใช่การพึ่งพาที่ขาดหายไป .. ไม่คุ้มกับเวลาที่ต้องกังวลกับมันมากขึ้น นอกจากนี้ ini4j ยังมีอึอื่น ๆ อีกมากมายที่เราไม่ต้องการการแก้ไขรีจิสทรีของ Windoze ... มาเลย #LinuxMasterRace ... แต่ฉันเดาว่ามันเหมาะกับคุณหรือเปล่า
ผู้ใช้

สำหรับไฟล์ INI ที่ฉันเขียนฉันต้องใช้Winiคลาสดังที่แสดงในบทแนะนำ "หนึ่งนาที" prefs.node("something").get("val", null)ไม่ได้ทำงานในแบบที่ผมคาดไว้
Agi Hammerthief

65

ในฐานะที่เป็นที่กล่าวถึง , ini4jสามารถนำมาใช้เพื่อให้บรรลุนี้ ให้ฉันดูอีกตัวอย่างหนึ่ง

หากเรามีไฟล์ INI เช่นนี้:

[header]
key = value

สิ่งต่อไปนี้ควรแสดงvalueเป็น STDOUT:

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

ตรวจสอบบทเรียนสำหรับตัวอย่างเพิ่มเติม


2
เรียบร้อย! ฉันใช้ BufferedReader มาโดยตลอดและคัดลอก / วางโค้ดแยกวิเคราะห์ String เพื่อไม่ต้องเพิ่มการอ้างอิงอื่นให้กับแอปพลิเคชันของฉัน (ซึ่งสามารถเพิ่มสัดส่วนได้เมื่อคุณเริ่มเพิ่มใน API ของบุคคลที่สามสำหรับงานที่ง่ายที่สุด ). แต่ฉันไม่สามารถละเลยความเรียบง่ายแบบนี้ได้
Gimby

30

ง่ายๆเพียง 80 บรรทัด:

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 เพียงแค่ใช้ regex Pattern / Matcher ใช้งานได้เหมือนมีเสน่ห์
kalelien

ไม่ใช่โซลูชันที่สมบูรณ์แบบ แต่เป็นจุดเริ่มต้นที่ดีเช่นไม่มี getSection () และ getString () จะคืนค่า defaultValue เท่านั้นหากไม่มีทั้งส่วน
Jack Miller

ความแตกต่างของประสิทธิภาพระหว่าง regx กับการทำงานกับการใช้งานสตริงคืออะไร?
Ewoks

ประสิทธิภาพเมื่ออ่านไฟล์คอนฟิกูเรชันขนาดเล็กไม่น่ากังวล ฉันเชื่อว่าการเปิดและปิดไฟล์นั้นใช้เวลานานกว่ามาก
Aerospace

ใช่นี่เป็นเรื่องง่ายอย่างที่ควรจะเป็นสำหรับกรณีการใช้งานทั่วไป ไม่แน่ใจว่าทำไมผู้คนถึงต้องการทำให้มันซับซ้อน หากคุณกังวลเกี่ยวกับประสิทธิภาพ (หรือข้อกังวลอื่น ๆ เช่นการรายงานข้อผิดพลาด) ใช่คุณอาจต้องการใช้อย่างอื่น (อาจเป็นรูปแบบอื่นทั้งหมด)
ผู้ใช้

16

นี่คือการที่ง่ายและมีประสิทธิภาพตัวอย่างเช่นยังใช้คลาส Apache HierarchicalINIConfiguration :

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");
}

Commons Configuration มีการอ้างอิงรันไทม์จำนวนหนึ่ง อย่างน้อยที่สุดจำเป็นต้องมีการบันทึกแบบcommons-langและcommons-logging คุณอาจต้องใช้ไลบรารีเพิ่มเติม (ดูลิงค์ก่อนหน้าสำหรับรายละเอียด) ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณทำ


1
นี่คงเป็นคำตอบที่ถูกต้องของฉัน ใช้งานง่ายและหลากหลาย
marcolopes

การกำหนดค่าคอมมอนส์ไม่ใช่คอลเลกชัน
jantox

13

หรือด้วย Java API มาตรฐานคุณสามารถใช้java.util.Properties :

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

12
ปัญหาคือด้วยไฟล์ ini โครงสร้างมีส่วนหัว ชั้นคุณสมบัติไม่ทราบวิธีจัดการส่วนหัวและอาจมีการปะทะกันของชื่อ
Mario Ortegón

2
นอกจากนี้Propertiesคลาสยังไม่ได้รับค่าที่ประกอบด้วย \
rds

3
+1 สำหรับวิธีแก้ปัญหาง่ายๆ แต่เหมาะกับไฟล์กำหนดค่าธรรมดาเท่านั้นดังที่ Mario Ortegon และ rds สังเกตเห็น
เบญจ

1
ไฟล์ INI มี [ส่วน] ไฟล์คุณสมบัติมีการกำหนด
Aerospace

1
รูปแบบไฟล์: 1 / a simple line-orientedหรือ 2 / a simple XML formatหรือ 3 / a simple line-oriented โดยใช้ ISO 8859-1 (พร้อมUnicodenative2ascii
Escape

10

ใน 18 บรรทัดการขยายjava.util.Propertiesเพื่อแยกวิเคราะห์เป็นหลายส่วน:

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

ตัวเลือกหนึ่งคือApache Commons Configยังมีชั้นเรียนสำหรับการโหลดจากไฟล์ INI มันมีการอ้างอิงรันไทม์บางอย่างแต่สำหรับไฟล์ INI ควรต้องการเฉพาะคอลเลกชัน Commons, lang และ logging เท่านั้น

ฉันใช้ Commons Config กับโปรเจ็กต์ที่มีคุณสมบัติและการกำหนดค่า XML มันใช้งานง่ายมากและรองรับคุณสมบัติที่ทรงพลังบางอย่าง



2

ผมเองชอบขงจื้อ

เป็นสิ่งที่ดีเนื่องจากไม่ต้องการการอ้างอิงภายนอกใด ๆ มันมีขนาดเล็กเพียง 16K และโหลดไฟล์ ini ของคุณโดยอัตโนมัติเมื่อเริ่มต้น เช่น

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

3 ปีต่อมามาร์คและ OP อาจเสียชีวิตด้วยวัยชรา ... แต่นี่เป็นการค้นพบที่ดีจริงๆ
ผู้ใช้

6
ฉันใช้ไม้เท้าเพื่อไปไหนมาไหน แต่ยังมีชีวิตอยู่และ kickin '
Mario Ortegón

@ MarioOrtegón: ดีใจมากที่ได้ทราบ!
ישואוהבאותך

0

โซลูชันของhoat4นั้นสวยงามและเรียบง่ายมาก ใช้งานได้กับไฟล์ ini ที่มีเหตุผลทั้งหมด แต่ผมได้เห็นหลายที่มีอักขระช่องว่างยกเลิกการหลบหนีในที่สำคัญ
เพื่อแก้ปัญหานี้ฉันได้ดาวน์โหลดและแก้ไขสำเนาของjava.util.Propertiesไฟล์. แม้ว่านี่จะเป็นเรื่องนอกรีตเล็กน้อยและในระยะสั้นม็อดที่แท้จริงนั้นมีเพียงไม่กี่บรรทัดและค่อนข้างเรียบง่าย ฉันจะยื่นข้อเสนอต่อชุมชน JDK เพื่อรวมการเปลี่ยนแปลง

โดยการเพิ่มตัวแปรคลาสภายใน:

private boolean _spaceCharOn = false;

ฉันควบคุมการประมวลผลที่เกี่ยวข้องกับการสแกนหาจุดแยกคีย์ / ค่า ฉันแทนที่โค้ดการค้นหาอักขระเว้นวรรคด้วยเมธอดส่วนตัวขนาดเล็กที่ส่งคืนบูลีนขึ้นอยู่กับสถานะของตัวแปรข้างต้น

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

load0(...)วิธีนี้จะใช้ในสองสถานที่ภายในวิธีการส่วนตัว
นอกจากนี้ยังมีวิธีการสาธารณะในการเปิดใช้งาน แต่จะเป็นการดีกว่าถ้าใช้เวอร์ชันดั้งเดิมPropertiesหากตัวคั่นช่องว่างไม่ใช่ปัญหาสำหรับแอปพลิเคชันของคุณ

หากมีความสนใจฉันยินดีที่จะโพสต์รหัสลงในIniFile.javaไฟล์ของฉัน ใช้งานได้กับProperties.


0

เมื่อใช้คำตอบโดย @Aerospace ฉันรู้ว่ามันถูกต้องสำหรับไฟล์ INI ที่จะมีส่วนต่างๆโดยไม่มีคีย์ - ค่าใด ๆ ในกรณีนี้นอกจากแผนที่ระดับบนสุดควรเกิดขึ้นก่อนที่จะพบคีย์ - ค่าใด ๆ เช่น (อัปเดตน้อยที่สุดสำหรับ 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);
            }

-1

ง่ายๆเพียงเท่านี้ .....

//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
สิ่งนี้ไม่ได้จัดการกับส่วน INI
Igor Melnichenko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.