2

我有一个如下所示的文本文件:

grn129          agri-
ac-214          ahss
hud114          ahss
lov1150         ahss
lov1160         ahss
lov1170         ahss
lov1210         ahss

如果我想创建一个以第一列为键、第二列为值的 HashMap,使用 Java 解析此文件的最佳方法是什么。

我应该使用 Scanner 类吗?尝试将整个文件作为字符串读取并拆分?

什么是最好的方法?

4

7 回答 7

4

这就是我将如何做到的!自 2000 年以来,我几乎完全是一名 Java 程序员,所以它可能有点过时。有一句话让我特别自豪:

new InputStreamReader(fin, "UTF-8");

http://www.joelonsoftware.com/articles/Unicode.html

享受!

import java.io.*;
import java.util.*;

public class StackOverflow2565230 {

  public static void main(String[] args) throws Exception {
    Map<String, String> m = new LinkedHashMap<String, String>();
    FileInputStream fin = null;
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      fin = new FileInputStream(args[0]);
      isr = new InputStreamReader(fin, "UTF-8");
      br = new BufferedReader(isr);
      String line = br.readLine();
      while (line != null) {
        // Regex to scan for 1 or more whitespace characters
        String[] toks = line.split("\\s+");
        m.put(toks[0], toks[1]);
        line = br.readLine();
      }
    } finally {
      if (br != null)  { br.close();  }
      if (isr != null) { isr.close(); }
      if (fin != null) { fin.close(); }
    }

    System.out.println(m);
  }

}

这是输出:

julius@flower:~$ javac StackOverflow2565230.java 
julius@flower:~$ java -cp .  StackOverflow2565230  file.txt 
{grn129=agri-, ac-214=ahss, hud114=ahss, lov1150=ahss, lov1160=ahss, lov1170=ahss, lov1210=ahss}

是的,我的电脑的名字是Flower。以小鹿斑比的臭鼬命名。

最后一点:因为 close() 可以抛出 IOException,这就是我真正关闭流的方式:

} finally {
  try {
    if (br != null) br.close();
  } finally {
    try {
      if (isr != null) isr.close();
    } finally {
      if (fin != null) fin.close();
    }
  }
}
于 2010-04-02T06:21:51.767 回答
3

基于@Julius Davies,这是一个较短的版本。

import java.io.*; 
import java.util.*; 

public class StackOverflow2565230b { 
  public static void main(String... args) throws IOException { 
    Map<String, String> m = new LinkedHashMap<String, String>(); 
    BufferedReader br = null; 
    try { 
      br = new BufferedReader(new FileReader(args[0])); 
      String line;
      while ((line = br.readLine()) != null) { 
        // Regex to scan for 1 or more whitespace characters 
        String[] toks = line.split("\\s+"); 
        m.put(toks[0], toks[1]); 
      } 
    } finally { 
      if (br != null) br.close(); // dont throw an NPE because the file wasn't found.
    } 

    System.out.println(m); 
  } 
}
于 2010-04-02T08:27:53.987 回答
2

我不知道最好的方法,但我怀疑最有效的方法是一次读取一行(使用BufferedReader),然后通过找到第一个空白字符来分割每一行,在那里分割,然后修剪双方。但是,除非它需要超快,否则无论您最喜欢什么都可以。

我个人倾向于一次加载整个文件......除了它假设有足够的内存来保存整个文件之外,它不允许任何并行计算(例如,如果输入进入从管道)。能够在输入仍在生成时对其进行处理是有意义的。

于 2010-04-02T06:10:09.317 回答
1

使用 Scanner 或普通 FileReader + String.split() 应该都可以正常工作。我认为速度差异很小,除非您打算一遍又一遍地读取一个非常大的文件,否则没关系。

编辑:实际上,对于第二种方法,使用BufferedReader。它有一个 getLine() 方法,这使事情变得稍微容易一些。

于 2010-04-02T06:07:59.133 回答
0

缓存一个正则表达式怎么样?(String.split() 将在每次调用时编译正则表达式)

如果您在几个大文件(100、1k、100k、1m、10m 条目)上对每种方法进行性能测试,并查看性能比较,我会很好奇。

import java.io.*;
import java.util.*;
import java.util.regex.*;

public class So2565230 {

    private static final Pattern rgx = Pattern.compile("^([^ ]+)[ ]+(.*)$");

    private static InputStream getTestData(String charEncoding) throws UnsupportedEncodingException {
        String nl = System.getProperty("line.separator");
        StringBuilder data = new StringBuilder();
        data.append(" bad data " + nl);
        data.append("grn129          agri-" + nl);
        data.append("grn129          agri-" + nl);
        data.append("ac-214          ahss" + nl);
        data.append("hud114          ahss" + nl);
        data.append("lov1150         ahss" + nl);
        data.append("lov1160         ahss" + nl);
        data.append("lov1170         ahss" + nl);
        data.append("lov1210         ahss" + nl);
        byte[] dataBytes = data.toString().getBytes(charEncoding);
        return new ByteArrayInputStream(dataBytes);
    }

    public static void main(final String[] args) throws IOException {
        String encoding = "UTF-8";

        Map<String, String> valuesMap = new LinkedHashMap<String, String>();

        InputStream is = getTestData(encoding);
        new So2565230().fill(valuesMap, is, encoding);

        for (Map.Entry<String, String> entry : valuesMap.entrySet()) {
            System.out.format("K=[%s] V=[%s]%n", entry.getKey(), entry.getValue());
        }
    }

    private void fill(Map<String, String> map, InputStream is, String charEncoding) throws IOException {
        BufferedReader bufReader = new BufferedReader(new InputStreamReader(is, charEncoding));
        for (String line = bufReader.readLine(); line != null; line = bufReader.readLine()) {
            Matcher m = rgx.matcher(line);
            if (!m.matches()) {
                System.err.println("Line has improper format (" + line + ")");
                continue;
            }
            String key = m.group(1);
            String value = m.group(2);
            if (map.put(key, value) != null) {
                System.err.println("Duplicate key detected: (" + line + ")");
            }
        }
    }
}
于 2010-04-02T19:04:13.990 回答
0

如果您想遵循教科书的解决方案,请使用 StringTokenizer。它直截了当,易于学习且非常简单。它可以克服简单的结构偏差(可变数量的空白字符、不均匀的格式行等)

但是,如果您的文本被认为是 100% 格式良好且可预测的,那么只需将一堆行读入缓冲区,一次取一个,然后将部分字符串取出到您的 HashMap 键和值中。它比 StringTokenizer 快,但缺乏灵活性。

于 2010-04-02T06:08:52.090 回答
-1

朱利叶斯戴维斯的回答很好。

但是,恐怕您必须定义要解析的文本文件的格式。例如你的第一列和第二列之间的分隔符是什么,如果不固定,会造成更多的困难。

于 2010-04-02T10:41:11.483 回答