Лучший подход для разбора текстовых файлов, которые содержат несколько типов разделителей? - PullRequest
2 голосов
/ 12 ноября 2009

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

Существует также различный порядок элементов в зависимости от того, что является разделителем, например:

comma: A, B, C, D, E
caret: B, C, A, E, D
tilde: C, A, B, D, E 

Разделитель внутри файла одинаков, но отличается от одного файла к другому. Из того, что я могу сказать, в элементах данных нет разделителей.

Какой хороший способ сделать это на обычной Java?

Ответы [ 10 ]

3 голосов
/ 12 ноября 2009

Мне нравится читать первые две строки файла, а затем проверять разделители. Если вы разделяете разделитель, и обе строки возвращают одно и то же ненулевое количество частей, то вы, вероятно, догадались, правильный. Вот пример программы, которая проверяет файл names.txt.

public static void main(String[] args) throws IOException {
    File file = new File("etc/names.txt");

    String delim = getDelimiter(file);
    System.out.println("Delim is " + delim + " (" + (int) delim.charAt(0) + ")");
}

private static final String[] DELIMS = new String[] { "\t", ",", " " };

private static String getDelimiter(File file) throws IOException {
    for (String delim : DELIMS) {

        BufferedReader br = new BufferedReader(new FileReader(file));
        String[] line0 = br.readLine().split(delim);
        String[] line1 = br.readLine().split(delim);
        br.close();
        if (line0.length == line1.length && line0.length > 1) {
            return delim;
        }
    }
    throw new IllegalStateException("Failed to find delimiter for file " + file);
}
2 голосов
/ 12 ноября 2009

Я мог бы начать играть с Java StringTokenizer . Это берет строку и позволяет найти каждый токен, разделенный разделителем.

Вот один пример из сети.

Но вы хотите токенизировать вещи из файла. В этом случае вы можете поиграть с Java StreamTokenizer , который позволяет вам анализировать входные данные из файлового потока.

редактировать

Если вы не знаете разделителей заранее, вы можете сделать несколько вещей:

  1. Разделитель на основе всех возможных разделителей. Если ваши данные не имеют разделителей, это сработает. (т. е. ищите «,» и «;» - при условии, что ваши данные не содержат ни одного из этих символов)
  2. Если у вас есть представление о том, как должны выглядеть ваши данные (должны быть целыми числами или одиночными символами), тогда ваш код может использовать разные разделители (try "," first, затем try ";", и т. д.) до тех пор, пока он не проанализирует строку текста «правильно».
1 голос
/ 12 ноября 2009

Один из способов найти разделитель в файле - это какое-то регулярное выражение. Простой случай - найти любой символ, который не является алфавитным или числовым: [^ A-Za-z0-9]

static String getDelimiter(String str) {
  Pattern p = Pattern.compile("([^A-Za-z0-9])");
  Matcher m = p.matcher(str.trim()); //remove whitespace as first char(s)
  if(m.find())
   return m.group(0);
  else 
   return null;
 }




public static void main(String[] args) {
  String[] str = {" A, B, C, D", "A B C D", "A;B;C;D"};
  for(String s : str){   
   String[] data = s.split(getDelimiter(s));
   //do clever stuff with the array
  }
 }

В этом случае я загружал данные из массива вместо чтения из файла. При чтении из файла feed первая строка метода getDelimiter.

1 голос
/ 12 ноября 2009

Вы можете использовать StringTokenizer, как упоминалось ранее. Да, вам нужно будет указать строку для всех возможных разделителей. Не забудьте установить свойство returnDelims токенизатора. Таким образом, вы узнаете, какой токен используется в файле, и сможете соответствующим образом проанализировать данные.

1 голос
/ 12 ноября 2009

Если точный порядок записей известен при использовании определенного разделителя, я бы просто создал парсер, который бы возвращал объект Record для каждой строки ... что-то вроде ниже.

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

 class MyParser

    {
        public static Record parseLine(String line, char delimiter)
        {
            StringTokenizer st1 = new StringTokenizer(line, delimiter);
            //You could easily use an array instead of these dumb variables
            String temp1,temp2,temp3,temp4,temp5;

            temp1 = st1.getNextToken();
            .. etc..

            Record ret = new Record();
            switch (delimiter)
            {
                case '^':
                ret.A = temp2;
                ret.B = temp3;
                ...etc...
                break;
                case '~':
                ...etc...
                break;
            }
        }
    }

    class Record
    {
        String A;
        String B;
        String C;
        String D;
        String E:
    }
1 голос
/ 12 ноября 2009

Один из возможных подходов - использовать компилятор Java (https://javacc.dev.java.net/).). С его помощью вы можете написать набор правил для того, что вы примете, и какие разделители могут появиться в любой момент. Движку можно дать правила чтобы обойти проблемы порядка в зависимости от используемого разделителя. И файл может, если необходимо, переключать разделители по пути.

1 голос
/ 12 ноября 2009

Вы можете написать класс, который анализирует файл, примерно так:

interface MyParser {
  public MyParser(char delimiter, List<String> fields);

  Map<String,String> ParseFile(InputStream file);
}

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

Реализация ParseFile, вероятно, будет использовать split с разделителем, а затем выполнять итерацию по массиву, возвращенному split, и списку полей одновременно, создавая карту по ходу.

1 голос
/ 12 ноября 2009

если в файле есть один и тот же разделитель, то, вероятно, при загрузке файла для анализа вы можете ввести разделитель.

Скажи для бывшего ..

    void someFunction(char delimiter){
--- do wateva you want to do with the file --- // you can use stringTokenizer for this purpose
}

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

Надеюсь, это поможет ..: -)

1 голос
/ 12 ноября 2009

Если в файле используется один и тот же разделитель, напишите функцию для одного разделителя, назовите ее d , а при обработке других файлов замените их разделитель на d . Полоскание. Повторение. :)

Другой подход: пусть ваша функция синтаксического анализа принимает имя файла и разделитель в качестве параметров. Предполагается, что логика синтаксического анализа одинакова для всех файлов.

Если ваши файлы выглядят совершенно иначе - разделители представляют собой наименьшую проблему.

0 голосов
/ 12 ноября 2009

Большинство библиотек синтаксического анализа CSV с открытым исходным кодом позволяют изменять символы разделителя, а также имеют встроенное поведение для обработки экранирования. Opencsv кажется популярным в настоящее время, но я еще не использовал его. Я был очень доволен библиотекой Ostermiller в прошлый раз, когда мне пришлось много раз анализировать CSV.

...