Java IO体系之强大的RandomAccessFile

来源:互联网 发布:个性化排序算法 编辑:程序博客网 时间:2024/03/29 06:39
现有如下的一个需求,向已存在1G数据的txt文本里末尾追加一行文字,内容如下“Lucene是一款非常优秀的全文检索库”。可能大多数朋友会觉得这个需求很easy,说实话,确实easy,然后XXX君开始实现了,直接使用Java中的流读取了txt文本里原来所有的数据转成字符串后,然后拼接了“Lucene是一款非常优秀的全文检索库”,又写回文本里了,至此,大功告成。后来需求改了,向5G数据的txt文本里追加了,结果XXX君傻了,他内存只有4G,如果强制读取所有的数据并追加,会报内存溢出的异常。 

其实上面的需求很简单,如果我们使用JAVA IO体系中的RandomAccessFile类来完成的话,可以实现零内存追加。其实这就是支持任意位置读写类的强大之处。 



在这之前,散仙还是喜欢先啰嗦的介绍下RandomAccessFile这个类,RandomAccessFile是Java中输入,输出流体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。 

如果我们只希望访问文件的部分内容,而不是把文件从头读到尾,使用RandomAccessFile将会带来更简洁的代码以及更好的性能。
 

下面来看下RandomAccessFile类中比较重要的2个方法,其他的和普通IO类似,在这里,就不详细说明了。 

方法名作用getFilePointer()返回文件记录指针的当前位置seek(long pos)将文件记录指针定位到pos的位置


下面散仙给出示例,分析下怎么使用RandomAccessFile 
首先,我们先看下散仙要操作的文本文件的内容截图。
 

 
功能one,读取任意位置的数据,代码如下 
Java代码  收藏代码
  1. /** 
  2.      * 读的方法 
  3.      * @param path 文件路径 
  4.      * @param pointe 指针位置 
  5.      * **/  
  6.     public static void randomRed(String path,int pointe){  
  7.         try{  
  8.             //RandomAccessFile raf=new RandomAccessFile(new File("D:\\3\\test.txt"), "r");  
  9.             /** 
  10.              * model各个参数详解 
  11.              * r 代表以只读方式打开指定文件 
  12.              * rw 以读写方式打开指定文件 
  13.              * rws 读写方式打开,并对内容或元数据都同步写入底层存储设备 
  14.              * rwd 读写方式打开,对文件内容的更新同步更新至底层存储设备 
  15.              *  
  16.              * **/  
  17.             RandomAccessFile raf=new RandomAccessFile(path, "r");  
  18.             //获取RandomAccessFile对象文件指针的位置,初始位置是0  
  19.             System.out.println("RandomAccessFile文件指针的初始位置:"+raf.getFilePointer());  
  20.             raf.seek(pointe);//移动文件指针位置  
  21.             byte[]  buff=new byte[1024];  
  22.             //用于保存实际读取的字节数  
  23.             int hasRead=0;  
  24.             //循环读取  
  25.             while((hasRead=raf.read(buff))>0){  
  26.                 //打印读取的内容,并将字节转为字符串输入  
  27.                 System.out.println(new String(buff,0,hasRead));  
  28.                   
  29.             }  
  30.               
  31.               
  32.               
  33.         }catch(Exception e){  
  34.             e.printStackTrace();  
  35.         }  
  36.           
  37.           
  38.           
  39.     }  


测试代码 
Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     String path="D:\\3\\test.txt";         
  3.      int seekPointer=20;  
  4.      randomRed(path,seekPointer);//读取的方法   
  5.     //randomWrite(path);//追加写的方法  
  6.     //insert(path, 33, "\nlucene是一个优秀的全文检索库");  
  7. }  

运行效果: 
Java代码  收藏代码
  1. RandomAccessFile文件指针的初始位置:0  
  2. is a teacher  
  3. hadoop is perfect  

功能two,追加数据,代码如下 
Java代码  收藏代码
  1. /** 
  2.  * 追加方式 
  3.  * 写的方法 
  4.  * @param path 文件路径 
  5.  * ***/  
  6. public static void randomWrite(String path){  
  7.     try{  
  8.         /**以读写的方式建立一个RandomAccessFile对象**/  
  9.         RandomAccessFile raf=new RandomAccessFile(path, "rw");  
  10.           
  11.         //将记录指针移动到文件最后  
  12.         raf.seek(raf.length());  
  13.         raf.write("我是追加的 \r\n".getBytes());  
  14.           
  15.     }catch(Exception e){  
  16.         e.printStackTrace();  
  17.     }  
  18.       
  19. }  

测试代码 
Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     String path="D:\\3\\test.txt";         
  3.      //int seekPointer=20;  
  4.     // randomRed(path,seekPointer);//读取的方法   
  5.      randomWrite(path);//追加写的方法  
  6.     //insert(path, 33, "\nlucene是一个优秀的全文检索库");  
  7. }  

运行效果: 

 
功能three,任意位置插入数据,代码如下 
Java代码  收藏代码
  1. /** 
  2.      * 实现向指定位置 
  3.      * 插入数据 
  4.      * @param fileName 文件名 
  5.      * @param points 指针位置 
  6.      * @param insertContent 插入内容 
  7.      * **/  
  8.     public static void insert(String fileName,long points,String insertContent){  
  9.         try{  
  10.         File tmp=File.createTempFile("tmp"null);  
  11.         tmp.deleteOnExit();//在JVM退出时删除  
  12.           
  13.         RandomAccessFile raf=new RandomAccessFile(fileName, "rw");  
  14.         //创建一个临时文件夹来保存插入点后的数据  
  15.         FileOutputStream tmpOut=new FileOutputStream(tmp);  
  16.         FileInputStream tmpIn=new FileInputStream(tmp);  
  17.         raf.seek(points);  
  18.         /**将插入点后的内容读入临时文件夹**/  
  19.           
  20.         byte [] buff=new byte[1024];  
  21.         //用于保存临时读取的字节数  
  22.         int hasRead=0;  
  23.         //循环读取插入点后的内容  
  24.         while((hasRead=raf.read(buff))>0){  
  25.             // 将读取的数据写入临时文件中  
  26.             tmpOut.write(buff, 0, hasRead);  
  27.         }  
  28.           
  29.         //插入需要指定添加的数据  
  30.         raf.seek(points);//返回原来的插入处  
  31.         //追加需要追加的内容  
  32.         raf.write(insertContent.getBytes());  
  33.         //最后追加临时文件中的内容  
  34.         while((hasRead=tmpIn.read(buff))>0){  
  35.             raf.write(buff,0,hasRead);  
  36.         }  
  37.         }catch(Exception e){  
  38.             e.printStackTrace();  
  39.         }  
  40.     }  

测试代码 
Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.         String path="D:\\3\\test.txt";         
  3.          //int seekPointer=20;  
  4.         // randomRed(path,seekPointer);//读取的方法   
  5.         // randomWrite(path);//追加写的方法  
  6.          insert(path, 33"\nlucene是一个优秀的全文检索库");  
  7.     }  

运行效果: 

 

至此,RandomAccessFile类的几个功能,散仙在代码中已给出实现了,现在回到本文开始前的提的那个需求,用RandomAccessFile类就可以轻而易举的完成了,另外需要注意的是,向指定位置插入数据,是散仙自己改造的功能,RandomAccessFile并不直接支持,需要新建一个缓冲区临时空间,存数据,然后在写,因为一旦数据量上了级别,在任意位置插入数据,是很耗内存的,这个也就是为什么hadoop的HDFS文件系统,只支持append的方式,而没有提供修改的操作。 


另外我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能,用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以自己实现下。