Как мне разобрать этот простой текстовый файл на Java? - PullRequest
2 голосов
/ 02 апреля 2010

У меня есть текстовый файл, который выглядит так:

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

Как лучше всего анализировать этот файл с использованием Java, если я хочу создать HashMap с первым столбцом в качестве ключа и вторым столбцом в качестве значения.

Должен ли я использовать класс сканера? Попробуйте прочитать весь файл как строку и разбить ее?

Какой самый лучший способ?

Ответы [ 7 ]

4 голосов
/ 02 апреля 2010

Вот как бы я это сделал! Я почти исключительно программист на Java с 2000 года, так что это может быть немного старомодно. В частности, есть одна строка, которой я немного горжусь:

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}

Да, мой компьютер зовут Цветок. Назван в честь скунса от Бэмби.

Последнее замечание: поскольку close () может генерировать IOException, я действительно должен закрыть потоки:

} finally {
  try {
    if (br != null) br.close();
  } finally {
    try {
      if (isr != null) isr.close();
    } finally {
      if (fin != null) fin.close();
    }
  }
}
3 голосов
/ 02 апреля 2010

Основано на @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); 
  } 
}
2 голосов
/ 02 апреля 2010

Я не знаю, как лучше, но я подозреваю, что наиболее эффективным способом было бы читать по одной строке за раз (используя BufferedReader ), а затем разбивать каждую строку, находя первую символ пробела, расщепление там, а затем обрезка обеих сторон. Тем не менее, все, что вам больше нравится, хорошо, если только оно не должно быть очень быстрым.

Я лично склонен против загрузки всего файла сразу ... кроме того факта, что предполагается, что памяти достаточно для хранения всего файла, он не допускает параллельных вычислений (например, если ввод приходит из трубы). Имеет смысл иметь возможность обрабатывать ввод, пока он еще генерируется.

1 голос
/ 02 апреля 2010

Использование сканера или обычного FileReader + String.split () должно работать нормально. Я думаю, что разница в скорости минимальна, и если вы не планируете читать очень большой файл снова и снова, это не имеет значения.

РЕДАКТИРОВАТЬ: На самом деле, для второго метода, используйте BufferedReader . У него есть метод getLine (), который немного упрощает процесс.

0 голосов
/ 02 апреля 2010

Как насчет кэширования регулярного выражения? (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 + ")");
            }
        }
    }
}
0 голосов
/ 02 апреля 2010

Ответ Джулиуса Дэвиса в порядке.

Однако я боюсь, что вам придется определить формат вашего текстового файла, который должен быть проанализирован. Например, каков отдельный символ между вашим первым столбцом и вторым столбцом, если он не зафиксирован, это вызовет дополнительные трудности.

0 голосов
/ 02 апреля 2010

Если вы хотите следовать учебному решению, используйте StringTokenizer. Это просто, легко учиться и довольно просто. Он может преодолеть простые отклонения в структуре (переменное количество пробелов, неравные строки и т. Д.)

Но если известно, что ваш текст на 100% хорошо отформатирован и предсказуем, тогда просто прочитайте несколько строк в буфер, извлеките их по одной за раз и выньте части строк в ключ HashMap значение. Это быстрее, чем StringTokenizer, но ему не хватает гибкости.

...