Как читать форматированные данные из текстового файла в Java

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

{
    Marsha      1234     Florida   1268
    Jane        1523     Texas     4456
    Mark        7253     Georgia   1234
}

(Примечание: это только пример. Не фактические данные из моего задания.)

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

Всего 4 ответа


Сначала вы должны знать формат своего файла. Как и ваш пример, если он начинается с {и заканчивается}. Что такое разделитель (ы) данных? Например, разделитель может быть точкой с запятой, пробелом и т. Д. Зная это, вы можете начать создавать приложение. В вашем примере я напишу что-то вроде этого:

public class MainClass
{

public static void main(String[] args)
{
    String s = "{
"+
               "Marsha      1234     Florida   1268
" + 
               "Jane        1523     Texas     4456
" + 
               "Mark        7253     Georgia   1234
"+
               "}
";

    String[] rows = s.split("
");

    //Here we will keep evertihing without the first and the last row
    List<String> importantRows = new ArrayList<>(rows.length-2);
    //lets assume that we do not need the first and the last row
    for(int i=0; i<rows.length; i++)
    {
        //String r = rows[i];
        //System.out.println(r);

        if(i>0 && i<rows.length)
        {
            importantRows.add(rows[i]);
        }

    }

    List<String> importantWords = new ArrayList<>(rows.length-2);
    //Now lets split every 'word' from row
    for(String rowImportantData : importantRows)
    {
        String[] oneRowData = rowImportantData.split(" ");

        //Here we will have one row like: [Marsha][ ][ ][ ][1234][ ][ ][ ][Florida][ ][ ][1268]
        // We need to remove the whitespace. This happen because there is more        
        //then one whitespace one after another. You can use some regex or another approach 
        // but I will show you this because you can have data that you do not need and you want to remove it.
        for(String data : oneRowData)
        {
            if(!data.trim().isEmpty())
            {
                importantWords.add(data);
            }
            //System.out.println(data);
        }

    }

    //Now we have the words.
    //You must know the rules that apply for this data. Let's assume from your example that you have (Name Number) group
    //If we want to print every group (Name Number) and we have in this state list with [Name][Number][Name][Number]....
    //Then we can print it this way
    for(int i=0; i<importantWords.size()-1; i=i+2)
    {
        System.out.println(importantWords.get(i) + " " + importantWords.get(i+1));
    }

}

}

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

Удачи!


В примере, который вы указали, разделение строк на regex-pattern s+ будет работать:

String s = "Marsha      1234     Florida   1268";
s.split("\s+");

приводит к массиву, содержащему 4 элемента «Марша», «1234», «Флорида» и «1268».

Образец, который я использовал, соответствует одному или нескольким символам пробела - см. «JavaDocs Pattern для получения подробной информации и других параметров.


Другой подход - определить шаблон, который должна соответствовать вашей линии в целом, и захватить интересующие вас группы:

String s = "Marsha      1234     Florida   1268";

Pattern pattern = Pattern.compile("(\w+)\s+(\d+)\s+(\w+)\s+(\d+)");
Matcher matcher = pattern.matcher(s);

if (!matcher.matches())
    throw new IllegalArgumentException("line does not match the expected pattern"); //or do whatever else is appropriate for your use case

String name = matcher.group(1);
String id = matcher.group(2);
String state = matcher.group(3);
String whatever = matcher.group(4);

Этот шаблон требует, чтобы вторая и четвертая группы состояли только из цифр.

Обратите внимание, однако, что оба этих подхода будут разрушены, если ваши данные также могут содержать пробелы - в этом случае вам нужны разные шаблоны.


Существует множество различных подходов, которые вы можете использовать для чтения этого форматированного файла. Я бы предложил сначала извлечь соответствующие данные из текста в виде списка строк, а затем разбить строки на поля. Это пример того, как вы можете это сделать, используя предоставленный вами образец данных:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CustomTextReader {

    public static void main(String[] args) {
        String text =
                "Marsha      1234     Florida   1268
" + 
                "Jane        1523     Texas     4456
" + 
                "Mark        7253     Georgia   1234";

        //Extract the relevant data from the text as a list of arrays
        //  in which each array is a line, and each element is a field. 
        List<String[]> data = getData(text);
        //Just printing the results
        print(data);
    }

    private static List<String[]> getData(String text) {
        //1. Separate content into lines.
        return Arrays.stream(text.split("
"))
                //2. Separate lines into fields.
                .map(s -> s.split("\s{2,}"))
                .collect(Collectors.toList());
    }

    private static void print(List<String[]> data) {
        data.forEach(line -> {
            for(String field : line) {
                System.out.print(field + " | ");
            }
            System.out.println();
        });

    }
}

Важно знать, чего ожидать от данных с точки зрения формата. Если вы знаете, что поля не содержат пробелы, вы можете использовать " " или \s{2,} как шаблон для разделения строки на шаге 2. Но если вы считаете, что данные могут содержать поля с пробелами (например, North Carolina "), лучше использовать другое регулярное выражение, подобное \s{2,} (вот что я сделал в примере выше). Надеюсь, я помог тебе!


Я действительно верю, что советы @JoniVR будут очень полезны, и вам следует рассмотреть возможность использования разделителя для столбцов в строке. В настоящее время вы не сможете анализировать составные данные, например, имя «Мэри Энн». Кроме того, поскольку данные примера, которые вы предоставили, уже имеют 4 строки, у вас должно быть POJO, которое будет представлять анализируемые данные формы. Концептуальный выглядит так:

class MyPojo {

    private String name;
    private int postCode;
    private String state;
    private int cityId;

    public MyPojo(String name, int postCode, String state, int cityId) {
        this.name = name;
        this.postCode = postCode;
        this.state = state;
        this.cityId = cityId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPostCode() {
        return postCode;
    }

    public void setPostCode(int postCode) {
        this.postCode = postCode;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }

    @Override
    public String toString() {
        return "MyPojo{" +
            "name='" + name + ''' +
            ", postCode=" + postCode +
            ", state='" + state + ''' +
            ", cityId=" + cityId +
            '}'
    }
}

Затем вы хотели бы, чтобы ошибки встречались после проверки строк, которые я предполагаю, поэтому неплохо подумать о каком-то классе ошибок, хранящем эти (правильно спроектированный, который расширяет класс исключений, может быть?). Для этого был очень простой класс:

class InsertionError {
    private String message;
    private int lineNumber;

    public InsertionError(String message, int lineNumber) {
        this.message = message;
        this.lineNumber = lineNumber;
    }

    @Override
    public String toString() {
        return "Error at line " + lineNumber + " -> " + message;
    }
}

И тогда само решение должно:
1. Разделите линии.
2. Обозначьте столбцы на каждую строку и проанализируйте / подтвердите их.
3. Соберите данные столбцов в полезном представлении java.

Может быть, что-то вроде:

private static final int HEADERS_COUNT = 4;
private static final int LINE_NUMBER_CURSOR = 0;

public static void main(String[] args) {
    String data =   "Marsha      1234     Florida   1268
" +
                    "Jasmine     Texas    4456
" +
                    "Jane        1523     Texas     4456
" +
                    "Jasmine     Texas    2233      asd
" +
                    "Mark        7253     Georgia   1234";

    int[] lineNumber = new int[1];

    List<InsertionError> errors = new ArrayList<>();

    List<MyPojo> insertedPojo = Arrays.stream(data.split("
"))
        .map(x -> x.split("\p{Blank}+"))
        .map(x -> {
            lineNumber[LINE_NUMBER_CURSOR]++;

            if (x.length == HEADERS_COUNT) {
                Integer postCode = null;
                Integer cityId = null;

                try {
                    postCode = Integer.valueOf(x[1]);
                } catch (NumberFormatException ignored) {
                    errors.add(new InsertionError(""" + x[1] + "" is not a numeric value.", lineNumber[LINE_NUMBER_CURSOR]));
                }

                try {
                    cityId = Integer.valueOf(x[3]);
                } catch (NumberFormatException ignored) {
                    errors.add(new InsertionError(""" + x[3] + "" is not a numeric value.", lineNumber[LINE_NUMBER_CURSOR]));
                }

                if (postCode != null && cityId != null) {
                    return new MyPojo(x[0], postCode, x[2], cityId);
                }
            } else {
                errors.add(new InsertionError("Columns count does not match headers count.", lineNumber[LINE_NUMBER_CURSOR]));
            }
            return null;
        })
        .filter(Objects::nonNull)
        .collect(Collectors.toList());

    errors.forEach(System.out::println);

    System.out.println("Number of successfully inserted Pojos is " + insertedPojo.size() + ". Respectively they are: ");

    insertedPojo.forEach(System.out::println);
}

, который печатает:

Ошибка в строке 2 -> Количество столбцов не соответствует количеству заголовков.
Ошибка в строке 4 -> «Техас» не является числовым значением.
Ошибка в строке 4 -> «asd» не является числовым значением.
Количество успешно вставленных Pojos равно 3. Соответственно они:
MyPojo {name = 'Marsha', postCode = 1234, state = 'Florida', cityId = 1268}
MyPojo {name = 'Jane', postCode = 1523, state = 'Texas', cityId = 4456}
MyPojo {name = 'Mark', postCode = 7253, state = 'Georgia', cityId = 1234}


Есть идеи?

10000