我有一个包含数据的大文件(~8Gb / ~80万条记录)。每条记录都有6-8个属性,这些属性由单个选项卡分割。我想让初学者在另一个文件中复制一些给定的属性。所以我想要一个比上面更优雅的代码,例如,如果我只想要总共4个中的第二个和最后一个令牌:
StringTokenizer st = new StringTokenizer(line, "\t");
st.nextToken(); //get rid of the first token
System.out.println(st.nextToken()); //show me the second token
st.nextToken(); //get rid of the third token
System.out.println(st.nextToken()); //show me the fourth token
我提醒说这是一个巨大的文件,所以我必须避免任何多余的检查。
答案 0 :(得分:3)
你的问题让我想知道表现。最近我一直在使用Guava的Splitter,因为我挖掘了语法。我从未测量过性能,因此我对四种解析样式进行了快速测试。我把这些放在一起非常快,所以请原谅错误的风格和边缘情况的正确性。他们的理解是我们只对第二和第四项感兴趣。
我发现有趣的是,在解析350MB制表符分隔的文本文件(包含四列)时,“homeGrown”(非常粗略的代码)解决方案是最快的,例如:
head test.txt
0 0 0 0
1 2 3 4
2 4 6 8
3 6 9 12
在我的笔记本电脑上运行超过350MB的数据时,我得到了以下结果:
考虑到这一点,我认为我会坚持使用Guava的分配器进行大多数工作,并考虑使用更大数据集的自定义代码。
public static List<String> tokenize(String line){
List<String> result = Lists.newArrayList();
StringTokenizer st = new StringTokenizer(line, "\t");
st.nextToken(); //get rid of the first token
result.add(st.nextToken()); //show me the second token
st.nextToken(); //get rid of the third token
result.add(st.nextToken()); //show me the fourth token
return result;
}
static final Splitter splitter = Splitter.on('\t');
public static List<String> guavaSplit(String line){
List<String> result = Lists.newArrayList();
int i=0;
for(String str : splitter.split(line)){
if(i==1 || i==3){
result.add(str);
}
i++;
}
return result;
}
static final Pattern p = Pattern.compile("^(.*?)\\t(.*?)\\t(.*?)\\t(.*)$");
public static List<String> regex(String line){
List<String> result = null;
Matcher m = p.matcher(line);
if(m.find()){
if(m.groupCount()>=4){
result= Lists.newArrayList(m.group(2),m.group(4));
}
}
return result;
}
public static List<String> homeGrown(String line){
List<String> result = Lists.newArrayList();
String subStr = line;
int cnt = -1;
int indx = subStr.indexOf('\t');
while(++cnt < 4 && indx != -1){
if(cnt==1||cnt==3){
result.add(subStr.substring(0,indx));
}
subStr = subStr.substring(indx+1);
indx = subStr.indexOf('\t');
}
if(cnt==1||cnt==3){
result.add(subStr);
}
return result;
}
请注意,通过正确的绑定检查和更优雅的实现,所有这些都可能会更慢。
答案 1 :(得分:0)
正如Paul Tomblin所说,您应该使用unix cut
实用程序。
但是,在Java中你也可以尝试:
String[] fields = line.split("\t");
System.out.println(fields[1]+" "+fields[3]);
这是否更“优雅”是一个意见问题。对于大文件是否更快,我不知道 - 你需要在你的系统上对它进行基准测试。
相对性能还取决于每行有多少字段,以及您想要的字段; split()
会立即处理整行,但StringTokenizer
将逐步处理该行(如果您只想要20个字段中的字段2和4,则很好)。
答案 2 :(得分:0)
虽然您的数据文件很大,但听起来您的问题更多的是如何方便地访问文本行中的项目,其中项目由制表符分隔。我认为StringTokenizer对于这种简单的格式来说太过分了。
我会使用某种类型的“split”将该行转换为一个标记数组。我更喜欢在String.split上的公共语句StringUtils split,特别是当不需要正则表达式时。由于选项卡是“空格”,因此您可以使用默认拆分方法而不指定分隔符:
String [] items = StringUtils.split(line);
if (items != null && items.length > 6)
{
System.out.println("Second: " + items[1] + "; Fourth: " + items[3]);
}
答案 3 :(得分:0)
如果您正在执行readLines,那么您实际上是在扫描文件两次: 1)您一次搜索文件1个字符以查找行尾字符 2)然后你扫描每一行的标签。
您可以查看其中一个Csv库。从内存中,flatpack只进行一次扫描。 这些库可以提供更好的性能(我从未测试过它)。
一些java库: - Java Csv library - flatpack
答案 4 :(得分:0)
如果您的文件除了速度之外还很大,您还将面临内存消耗问题,因为您必须将文件加载到内存中才能操作它。
我有一个想法,但请注意,这是特定于平台的,并且违反了Java移动性。
您可以从java运行unix命令以获得大量的速度和内存消耗。例如:
public static void main ( final String[] args)throws Exception {
Runtime.getRuntime().exec("cat <file> | awk {print $1} >> myNewFile.txt");
}