用java解析一个CSV文件,然后保存在二维数组

问题描述:

好了,所以我在游戏基于Java中的卡牌游戏的工作。我把所有游戏peices的“信息”都刮到了一个csv文件中,其中每一行都是一个游戏peice,每一列都是该peice的一种属性。我花了几个小时用Buffered reader等编写代码,试图从我的csv文件中提取信息到2d数组中,但无济于事。我的csv文件链接在这里:http://dl.dropbox.com/u/3625527/MonstersFinal.csv我有一年的计算机科学,但我仍然无法弄清楚如何做到这一点。用java解析一个CSV文件,然后保存在二维数组

所以我的主要问题是如何做我这样我可以保持行和列放入一个二维数组呢?

+0

发布一些你试过的代码 – blitzen

嘛,如前面提到的,你的一些字符串包含逗号,所以最初你从一个不好的地方开始,但我有一个解决方案,那就是:

  1. ----- ----如果可能的话,重新设置网站,但是当你这样做的时候执行一个简单的编码操作。你会想做一些事情,比如你会注意到在包含HTML的自动生成的XML文件中往往会做的事情;保留一个'控制字符'(一个可打印字符在这里最好,因为调试的原因,以及...理智),一旦编码,就不会直接作为自身的一个实例来读取。 &符号是我喜欢使用的,因为它很少见,但仍然可打印,但真正要使用的字符取决于您。我要做的就是编写程序,以便在每个“,”的实例中,在写入CSV之前,该逗号将替换为“& c”,并且在网站上实际的&符号的每个实例上,该“ “&”将被替换为“&a”。这样,您就不会有意外地将CSV中的单个值分成两部分的问题,并且可以在通过我将要概述的方法将它们分开之后,简单地解码每个值...

  2. --------假设你知道每行将有多少列,你可以使用StringTokenizer类(查看它 - 它很棒并且内置到Java中)。查找信息的好地方是总是Java教程)自动以数组的形式为您提供所需的值。

    • 它通过你传递一个字符串分隔符(在这种情况下,分隔符是“”),它吐出所有被那些逗号分隔的字符串。如果从一开始就知道总共有多少部分,则可以在开头初始化一个2D数组,然后插入StringTokenizer为您提供的每一行。如果你不这样做,它仍然可以,因为你可以使用ArrayList。 ArrayList是很好的,因为它是一个数组的更高级抽象,它会自动请求更多内存,以便您可以继续添加内存并知道检索时间将始终保持不变。但是,如果您计划动态添加棋子,并且比检索棋子更频繁,那么您可能希望使用LinkedList,因为它具有线性检索时间,但与添加 - 删除时间的ArrayList相比,关系更好。或者,如果你太棒了,你可以使用SkipList代替。我不知道它们是否在Java中默认实现,但它们很棒。尽管公平的警告;在检索,删除和放置时的速度成本随着内存的增加而增加。跳过列表保持了很多指针。

    • 如果你知道应该有相同数量的每一行中的值,并且希望他们在位置上有组织的,但不管是什么原因,你刮不办理行没有值的,只是没有提供这个值,你有一些坏消息......重写刮板代码中处理缺少值的部分比编写解释不同长度数组和实例化的方法更容易每个数组的一个Piece对象。我对此的建议将再次使用控制字符并填充空列(后面会用'null'来解释),但具体细节当然是将你的代码和编码风格个性化,所以它不适合我说。

编辑:我觉得你应该专注于主要的是学习Java中使用不同的标准库数据类型,也许学会执行一些自己的做法。我记得实现了一个二叉搜索树 - 而不是AVL树,但没问题。这很有趣,很好的编程练习,更重要的是,如果你想快速高效地完成任务,这是非常必要的。我不清楚Java是如何实现数组的,因为定义是“连续的内存段”,但是您可以在运行时使用变量在Java中为它们分配内存......但不管具体的Java实现如何,数组通常都不是最好的解决方案。此外,知道正则表达式使得所有的更容易。对于练习,我建议将它们加入到Java程序中,或者,如果你不想每次都编译和打包任何东西,你的bash脚本(如果你使用* nix)和/或批处理脚本(如果你'正在使用Windows)。

我工作在机器学习使用了类似的问题,所以让我分享一下我已经能够在话题做。 1)如果您在开始解析行之前知道 - 无论它是否被硬编码到您的程序中,或者您的文件中是否有一些标题可以提供此信息(强烈推荐) - 每行有多少个属性会有,你可以用逗号合理地分割它,例如第一个属性是RowString.substring(0,RowString.indexOf(',')),第二个属性是从第一个逗号到下一个逗号(编写一个函数来查找逗号的第n个实例,或者简单地在字符串中剔除字符串时,应该相当简单),最后一个属性是RowString.substring(RowString.lastIndexOf(',' ),RowString.length())。 String类的方法在这里是你的朋友。

2)如果您有麻烦这意味着可以单独值逗号,哪些是一个字符串格式的属性的一部分,那么(如果文件足够小手工重新逗号)之间的区别做的Java做什么 - 用'\'表示具有特殊含义的字符,而不是','。这样你可以搜索','的索引而不是'\',这样你就可以有一些区分你的角色的方法。

3)作为替代2),CSV的(在我看来)是不是很大的字符串,通常包括逗号。 CSVs没有真正的通用格式,为什么不让它们成为冒号分隔值或破折号分隔值,甚至是三连字和分隔值呢?用逗号分隔数值的目的是让他们很容易区分开来,如果逗号没有做这项工作,就没有理由保留它们。同样,这只适用于您的文件足够小以便手动编辑的情况。

4)看你的文件的不仅仅是格式的更多,但显而易见的是,你不能用手做。此外,它会显示某些字符串被三重双引号(“”“string”“”)包围,有些被单引号(“字符串”)包围。如果我不得不猜测,我会说包含在引号中的任何内容都是单个属性 - 例如,没有一对引号是以一个属性开始并以另一个属性结尾的。所以我会说你可以: 用一种方法将一个字符串分解成每个逗号分隔的字段。 编写方法,使得它忽略由奇数个双引号的前面逗号(这种方式,如果报价配对尚未关闭时,它知道它是一个字符串内并且该逗号不是值分隔符)。然而,如果文件的创建者做了一些类似双倍双引号(“”string“”)的字符串,那么这种策略会失败,因此您可能需要更全面的方法。

我想你已经刮了数据的方式,使这个问题比它需要更加困难。由于大多数值不一致地包含引号,一些数据中已经包含逗号,而不是每个卡都在自己的行上,所以您的刮擦似乎不一致并且难以处理。

尝试重新刮在一个更加一致的格式的数据,如:

R1C1|R1C2|R1C3|R1C4|R1C5|R1C6|R1C7|R1C8 
R2C1|R2C2|R2C3|R2C4|R2C5|R2C6|R2C7|R3C8 
R3C1|R3C2|R3C3|R3C4|R3C5|R3C6|R3C7|R3C8 
R4C1|R4C2|R4C3|R4C4|R4C5|R4C6|R4C7|R4C8 
A/D Changer|DREV-EN005|Effect Monster|Light|Warrior|100|100|You can remove from play this card in your Graveyard to select 1 monster on the field. Change its battle position. 

每一行肯定是自己的卡(而不是CSV你贴在奇数地方新线的示例)并且分隔符在数据字段中不会用作分隔符以外的其他分隔符。

一旦你得到的输入到持续读取状态,就变得非常简单,通过它来解析:

BufferedReader br = new BufferedReader(new FileReader(new File("MonstersFinal.csv"))); 
    String line = ""; 

    ArrayList<String[]> cardList = new ArrayList<String[]>(); // Use an arraylist because we might not know how many cards we need to parse. 

    while((line = br.readLine()) != null) { // Read a single line from the file until there are no more lines to read 
     StringTokenizer st = new StringTokenizer(line, "|"); // "|" is the delimiter of our input file. 
     String[] card = new String[8]; // Each card has 8 fields, so we need room for the 8 tokens. 
     for(int i = 0; i < 8; i++) { // For each token in the line that we've read: 
      String value = st.nextToken(); // Read the token 
      card[i] = value; // Place the token into the ith "column" 
     } 
     cardList.add(card); // Add the card's info to the list of cards. 
    } 

    for(int i = 0; i < cardList.size(); i++) { 
     for(int x = 0; x < cardList.get(i).length; x++) { 
      System.out.printf("card[%d][%d]: ", i, x); 
      System.out.println(cardList.get(i)[x]); 
     } 
    } 

这将产生下面的输出为我给出的例子输入:

card[0][0]: R1C1 
card[0][1]: R1C2 
card[0][2]: R1C3 
card[0][3]: R1C4 
card[0][4]: R1C5 
card[0][5]: R1C6 
card[0][6]: R1C7 
card[0][7]: R1C8 
card[1][0]: R2C1 
card[1][1]: R2C2 
card[1][2]: R2C3 
card[1][3]: R2C4 
card[1][4]: R2C5 
card[1][5]: R2C6 
card[1][6]: R2C7 
card[1][7]: R3C8 
card[2][0]: R3C1 
card[2][1]: R3C2 
card[2][2]: R3C3 
card[2][3]: R3C4 
card[2][4]: R3C5 
card[2][5]: R3C6 
card[2][6]: R3C7 
card[2][7]: R4C8 
card[3][0]: R4C1 
card[3][1]: R4C2 
card[3][2]: R4C3 
card[3][3]: R4C4 
card[3][4]: R4C5 
card[3][5]: R4C6 
card[3][6]: R4C7 
card[3][7]: R4C8 
card[4][0]: A/D Changer 
card[4][1]: DREV-EN005 
card[4][2]: Effect Monster 
card[4][3]: Light 
card[4][4]: Warrior 
card[4][5]: 100 
card[4][6]: 100 
card[4][7]: You can remove from play this card in your Graveyard to select 1 monster on the field. Change its battle position. 

我希望重新抓取信息是一个选择,我希望我没有误解任何东西;祝你好运!

关于最后一点,不要忘了利用面向对象的,一旦你已经得到的东西制定。一个Card类可以使数据更简单。