用Java读文件实现这么简单的要求都不行:
我要读一大文件,300万行,我想将文件指针移动到指定行,读取指定内容。一句话,就是读取指定行的内容。
看了很多API,都没有该功能,RandomAccessFile ,LineNumberReader等类都没用,最多能实现的是,跳过N个字符。
特别是LineNumberReader类,不知道有什么用,骗人的setLineNumber方法,根本无法改变文件指针。大家有什么方案吗
我要读一大文件,300万行,我想将文件指针移动到指定行,读取指定内容。一句话,就是读取指定行的内容。
看了很多API,都没有该功能,RandomAccessFile ,LineNumberReader等类都没用,最多能实现的是,跳过N个字符。
特别是LineNumberReader类,不知道有什么用,骗人的setLineNumber方法,根本无法改变文件指针。大家有什么方案吗
把一个大文件拆分成若干小文件,比如1000行一个,文件名后缀代表开始位置。
然后读某一特定行的时候,先打开对应的文件,然后跳过相应行数(用readLine())
你这个方案很不好,我想速度能达到1S就不错了。
jAVA里的readLine()只是读取某行的数据,没法实现跳过多少行????只能跳过多少字节。
只不过是按换行符分隔而已.读第几行本质上和读第几个'a'也没有区别.能跳过byte或char难道不是提供了跳过行?java没有直接API,其他语言有?除了ed那种行处理程序,好像都不会有吧?
要不,lz用shell吧,shell干这个事情比java搞效得多.
然后,再外部做一个行索引,保留每1000行的起始位置。还有一种方案,固定数据行长度,也就是VARCHAR->CHAR,以空间换速度(MySQL里面也可以试试此招,不知在读取数据时,是否会更快)
我主要是想借鉴数据库读文件的方法,数据库读文件那么高效,JAVA要可以达到那样的效率就好了。
shell 不可能高效的。程序(不管java/c)可以保持输入流。
前者需要顺序读,效率不会高,后者能提高效率,但是需要自己建索引。
我是说用ed或awk之类的行编辑软件.
HSQLDB, Derby(Java DB) 这些都是使用纯 Java 实现的关系型数据库。
它基于的是sql语句,虽然号称可以支持8G的存储,可实际上连基本的事务控制都没有。
拿hsqldb来说事情,可以说明java确实可以写小小数据库,但是和这里的需求没太大关系哦,有点跑题了。
就算谁写了一个可以跳过去的方法,里边的实现也一样是循环过去。何苦呢,与其要求底层提供不可能的功能,不如从本源上考虑一下解决方案,现在不是又兴起一种叫nosql的日志分析技术么?
完全可以自己实现一套这类的底层库。单是考虑\r\n, \n就够麻烦了,
首先遍历整个文件,动态生成index索引,记录每一行的偏移,这样就简单多了。想要哪一行直接从索引里找就行。
数据库也是索引,全文检索也是索引。它们都是从文件存储格式上下工夫。首先水平分区存储数据,别300w行进一个文件。
然后建立索引文件,加快搜索速度。update操作就要靠文件算法了。上头已经有人提到过,顺序存储的结构,在中间进行插入更新,删除,都会造成后面所有部分的内容发生变化。想提高更新速度,最佳选择就是实现链表。但是链表又会导致搜索的难度加大。这些东西都整合在一起,可以说实现一个通用的工具难度有点儿大。但是一般数据本身都有会自己的规则,善加利用可以提高不少效率。
呵呵~加油。
public class Test { // 数据流
static RandomAccessFile data;
// 索引流
static RandomAccessFile index; public static void main(String[] args) throws Exception {
try {
open();
// 第一次初始化数据
// init();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
System.out.println(read(100));
System.out.println(read(1000));
System.out.println(read(10000));
}
long end = System.currentTimeMillis();
System.out.println(end - start);
} finally {
close();
}
} static String read(int idx) throws Exception {
long indexPos = idx << 3;
index.seek(indexPos);
long dataPos = index.readLong();
long nextDataPos = index.readLong();
data.seek(dataPos);
int length = (int) (nextDataPos - dataPos) >> 1; // pos: bytes -> length: chars
char[] buff = new char[length];
for (int i = 0; i < buff.length; i++) {
buff[i] = data.readChar();
}
return new String(buff);
} static void init() throws Exception {
for (int i = 0; i < 100000; i++) {
index.writeLong(data.getFilePointer());
data.writeChars("No. " + i);
}
} static void open() throws Exception {
data = new RandomAccessFile("D:/test/test.dat", "rw");
index = new RandomAccessFile("D:/test/test.idx", "rw");
} static void close() throws Exception {
if (data != null) {
try {
data.close();
data = null;
} catch (Exception ex) {
}
}
if (index != null) {
try {
index.close();
index = null;
} catch (Exception ex) {
}
}
}
}
如果非要加入行的概念,你就得自己扩展File类了。
分别读取第100行、1,000、10,000、1,000,000行,循环1000遍,平均每行处理时间8ms而且,用文件的话,似乎必须使用固定长度,否则update操作涉及到长度变化时将很麻烦。本来lucene也是一个方案。但是据LZ原帖讲,读写比很低。怕帮不了什么忙。
于事无补,反而添乱
index可以是动态的,随实际dat自动增长。假设该方法为readLine(intLineId)
0、查看index是否存在?不在转10,存在转20
10、若index不存在,遍历dat,建立index,转步骤999读取
20、检查index中标识(data,size)是否和dat同步?同步转999读取,不在转步骤30
30、获取index的最后一行,再次基础上遍历data,重建index,转步骤999
999、读取index.getLine(intLineId),根据line.Offset,Line,Length用RandomAccessFile读取文件这里Index可以用数组,Serialize保存
如果多线程,考虑到同步,需要确保只有一个Index实例
class CLine
public int Offset,Length;
end class
class CIndex
private static HashMap<File,CIndex> m_hmOpenIndex=new HashMap<..>();
private int ma_intOffset[],ma_intLength[];
private CIndex(){}
public CIndex open(File fle){
if (m_hmOpenIndex.containsKey(fle)) return m_hmOpenIndex.get(fle);
//初始化一个新类
ma_intOffset=new int[3000000]; //效率原因,使用数组而不是vector,如果行非常多,就不用serialize,直接把index用io随机读写,但这样同步问题就很难处理了。
ma_intLength=new int[3000000];
scan(fle);
.......
}
public getLine(int intId){
return newLine(ma_intOffset[intId],ma_intLength[intId]);
}
end class
现在想想,确实没有必要记录这个数据行,因为方法调用者也不可能知道这个行号。考虑这个方案:在写文件的时候,把当前的位置(第几个字节),和该条数据的KEY,记录到索引文件,下次读的时候,知道了KEY,根据KEY从索引文件找到位置信息,再根据位置信息从数据文件直接定位到要读的数据。其实索引文件也要300万行数据,虽然只记录2个字段,还是慢,还是没有解决性能问题。因此这个方案不可行。
我用MySQL SELECT 1000条 1000ms左右就搞定了。(如果有查询缓存只要600多ms,这里不适合用查询缓存,更新频繁),刚用文件读1000条,基本上要1500-2000ms.
只要Offset和Length大小固定,索引可以用RandomAccessFile解决啊。
这样你的开销只要IO就可以了,一般Os都有文件缓存,实际只会更快。
但是同步更新之类比纯载入内存麻烦。
class CPosition
public int Offset,Length
public final static Size=8;
end class class CIndex
public CPostion get(int intLine){
raf.seek(intLine * CPosition.Size);
return new CPosition(raf.readInt(),raf.readInt());
}
end class
替你更正一下:高性能数据库(如ORACLE等)是绕过操作系统和文件系统直接读写硬盘的!
你所用的JAVA I/O流还是基于封装的,而且还得过操作系统和文件系统(取决于操作平台),不懂JAVA,搞C/C++的飘过...
按照搂主描述,既然还要对文件插入和删除,那规则型的数据可能性比较大,如果能使用数据库,那自然是应该使用数据库的了
如果每一行的长度都一样的话,自己算一下就知道从什么地方开始读。
如果每一行的长度不一样的话,就算是神仙也不可能一下子就知道第5000行从什么地方开始。除非一行行地读,别无他法。你可以建一个数组,存放每一行的起始位置,当然要遍历一遍整个文件。LineNumberReader类本来就不是给你用的,是jdk自用的一个类,可以在发生异常的时候报告错误发生在源文件的第几行。