【问题标题】:How to read formatted data from a text file in Java如何从 Java 中的文本文件中读取格式化数据
【发布时间】:2018-10-11 08:50:50
【问题描述】:

所以在过去的一周里,我完成了这个作业,我在这个作业中要做的一件事就是从文本文件中读取格式化数据。格式化我的意思是这样的:

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

(注意:这只是一个例子。不是我作业中的实际数据。)

现在我一直在尝试自己解决这个问题。我尝试将每一行读取为字符串并使用.substring() 获取所述字符串的某些部分并将其放入数组中,然后从数组中获取该字符串的索引并将其打印到屏幕上。现在我已经尝试了这个想法的一些不同的变体,但它只是不起作用。它要么以错误告终,要么以奇怪的方式输出数据。现在作业明天到期,我不知道该怎么办。如果有人可以在这件事上为我提供一些帮助,将不胜感激。

【问题讨论】:

  • 到目前为止你尝试过什么?每次都会准确的实际数据是什么?
  • 通常,您要查找分隔符(例如“;”或“,”,或者可能是空格或制表符),然后使用 string.split(",") 例如。我从您的示例中猜测它可能是一个选项卡,因此 \t 作为分隔符在理论上应该可以工作。

标签: java arrays file java.util.scanner


【解决方案1】:

对于您给出的示例,使用正则表达式模式 \s+ 分割行会起作用:

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

生成一个包含 4 个元素“Marsha”、“1234”、“Florida”和“1268”的数组。

我使用的模式匹配一​​个或多个空白字符 - 有关详细信息和其他选项,请参阅 The JavaDocs of 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);

此模式要求第二组和第四组仅包含数字。

但是请注意,如果您的数据也可以包含空格,那么这两种方法都会失效 - 在这种情况下,您需要不同的模式。

【讨论】:

    【解决方案2】:

    首先您必须知道文件的格式。如果它以 { 开头并以 } 结尾,就像您的示例一样。数据的分隔符是什么?例如分隔符可以是分号、空格等。知道了这一点,您就可以开始构建应用程序了。在您的示例中,我将编写如下内容:

    public class MainClass
    {
    
    public static void main(String[] args)
    {
        String s = "{\r\n"+
                   "Marsha      1234     Florida   1268\r\n" + 
                   "Jane        1523     Texas     4456\r\n" + 
                   "Mark        7253     Georgia   1234\r\n"+
                   "}\r\n";
    
        String[] rows = s.split("\r\n");
    
        //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));
        }
    
    }
    
    }
    

    这只是一个例子。您可以通过许多不同的方式制作您的应用程序。重要的部分是你要知道你想要处理的信息的初始状态是什么,你想要达到什么结果。

    祝你好运!

    【讨论】:

      【解决方案3】:

      您可以使用多种不同的方法来读取此格式化文件。我建议您首先从文本中提取相关数据作为字符串列表,然后将这些行分成字段。这是一个示例,说明如何使用您提供的数据样本来做到这一点:

      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\r\n" + 
                      "Jane        1523     Texas     4456\r\n" + 
                      "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("\r\n"))
                      //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,}(这就是我在上面的示例中所做的)。希望对你有所帮助!

      【讨论】:

        【解决方案4】:

        我真的相信@JoniVR 的建议会很有帮助,您应该考虑为每行的列使用分隔符。目前,您将无法解析像名字“Mary Ann”这样的复合数据。此外,由于您提供的示例数据已经有 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 +
                    '}';
            }
        }
        

        然后您希望在验证行后遇到错误,我猜想,因此最好考虑某种类型的 Error 类来存储这些错误(可能是一个设计合理的扩展 Exception 类的类?)。为此目的,一个非常简单的类是:

        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\n" +
                            "Jasmine     Texas    4456\n" +
                            "Jane        1523     Texas     4456\n" +
                            "Jasmine     Texas    2233      asd\n" +
                            "Mark        7253     Georgia   1234";
        
            int[] lineNumber = new int[1];
        
            List<InsertionError> errors = new ArrayList<>();
        
            List<MyPojo> insertedPojo = Arrays.stream(data.split("\n"))
                .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 行出错 -> “Texas”不是数值。
        第 4 行出错 -> “asd”不是数值。
        成功插入的 Pojo 数量为 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}

        【讨论】:

        • 这可能是 OP 所期待的,但完成他的任务并不会帮助他长久。给一个人一条鱼,你就喂他一天;教一个人钓鱼,你养他一辈子
        • 我同意上面的评论,因此为什么我发表评论而不是发布答案,他应该自己做功课,但是如果他自己没有到达那里,我们可以给他有用的指示。从长远来看,自己解决问题是一种更有效的学习方式。
        • 我同意@StephaneM 的声明,或者我同意这可能是一个“不情愿的人”。我在这里的真正意图仍然是给作者一个参考点,这样他就可以继续朝正确的方向挖掘,并逐步发展他在该领域的技能。一位优秀的教授也会对使用的代码进行研究(例如,谷歌搜索)并质疑学生对代码库的理解。差的分数可能会让某人退出,而差的分数可能会刺激获得更多关于该领域的知识。
        • 实际上我确实想学习代码,所以我真的只是在寻找一些指针。但是感谢您的帮助@dbl。
        猜你喜欢
        • 2017-08-06
        • 1970-01-01
        • 2023-03-31
        • 1970-01-01
        • 1970-01-01
        • 2013-08-03
        • 1970-01-01
        • 2016-07-17
        • 1970-01-01
        相关资源
        最近更新 更多