Java学习—I/O概述

在学习Java的I/O操作时最好的方法就是通过一个例子进行演练,在开始篇中说过,这个系列的博客也是部分结合Lucene的,使用Lucene需要一个测试集,这个测试集需要对其进行I/O操作,所以把这个作为学习I/O的一个实例吧。

首先介绍一下LISA。LISA是一个测试的文档集,每篇文档由一个标题和一个摘要构成,我在这里拿它来作为学习Lucene的一个测试集。这个测试集的下载地址是http://ir.dcs.gla.ac.uk/resources/test_collections/

下载后对其进行解压,如下图所示:


总共包括6004篇文档,还有一个问题集。在这里先把其进行文档的切割,方便Lucene的读取。这篇博客中想要通过这个具体的需求来简单介绍一下Java的I/O系统。Java中提供了许多种I/O的类。



首先,Java处理数据是通过流。流按照处理数据的类型可以分为字节流和字符流,按照处理流的方向可以分为输入流和输出流。而所有处理字节流的抽象基类是InputStream和OutputStream;处理字符流的抽象基类是Reader和Writer。由这四类派生出来的子类名称都是其父类名作为子类名的后缀,而其子类的功能则作为子类名称的前缀。如FileInputStream,这个子类的功能是处理文件的,而且处理的是字节流。I/O中的类中,基本上输入流和输出流都有一对行对应的类(如SequenceInputStream则没有对应的OutputStream)。

通过上面的叙述差不多知道某个I/O的子类的功能和处理数据流对象的特点。下面给出如何根据需求判断出使用哪个I/O子类,而如果明白了需要使用哪个I/O子类,则可查阅该类的一些操作方法,至于具体的操作方法可以查看Java的API文档。

通过四步来判断使用哪个子类:

  1. 明确数据流的方向是源还是目的。如果数据流的方向是源则选择InputStream和Reader的子类。如果是方向是目的则选择OutputStream和Writer的子类。
  2. 明确操作的数据是否是纯文本。如果是的话选择字符流Reader或Writer的子类,如果不是则选择字节流InputStream或OutputStream的子类。
  3. 根据上面两条可以大概的确定用哪一个体系。接下来可以通过操作的对象来进一步明确需要使用的类,如对文件操作的话则选择File有关的子类。
  4. 最后一步要明确是否需要使用缓冲区判断是否需要用Buffered的子类。

通过上面的描述,下面来分析这个需求,首先需要从lisa目录中分别读取出存储文档集的14个文件。所以可以选择使用FileInputStream和FileReader两个类。这里这两个类都会进行介绍,由于知道文件存储的是字符,所以可以先来考虑使用FileReader对文件进行读操作(根据上述的原则应该选择FileReader这个类来实现,但是之所以要说一下FileInputStream是要讲解一下字符流和字节流之间的一个桥梁)。

需求的大致流程如下:


上图中给出了。每次读入一行是BufferedReader的方法,所以如果使用之前说的那个FileInputStream时,需要将字节流转换成字符流,这时就需要他们之间的一个桥梁InputStreamReader,该类的作用是可以将一个字节流转换成字符流来操作。同样输出的时候如果是输出流字节的话也是需要进行转换的。

再说一下怎么判断是否读到了一个文件的结束,如果是一个一个的读的话,当返回值为-1时表示已经读到了这个文件的结束。如果是一行一行的读到话,当返回字符串为null时表示已经读到了这个文件的结束。BufferedReader的readLine()方法的返回值需要注意一下,它是不会返回换行符的(Linux的换行符是\n,Windows下是\r\n),所以需要手动换行,可以使用BufferedReader的newLine()方法(这个会根据不同的操作系统平台选择不同的换行符)。I/O流的操作还要特别注意到一点就是打开的流一定要在使用后关闭,但是Buffered的流对象类有一些特殊,Buffered是对流动缓冲,所以在创建缓冲区前首先要指定它是对哪个流创建的缓冲区,在使用结束后只需关闭缓冲区的流对象同时会自动的关闭缓冲区缓冲的流对象。

下面附上程序的代码:

package lzl.splitcode.lisa;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;

public class SplitLisa 
{
	//源目录集的路径
	private static final String SOURCEPATH = ".\\src\\source\\files\\lisa\\";
	//源目录集名称前面的名字,这部分是相同的名字
	private static final String SOURCENAME = "LISA";
	//每个源目录集后面的名称
	private static final String[] SOURCEFILE = new String[]{"0.001", "0.501", "1.001", "1.501", "2.001", "2.501", "3.001",
															"3.501", "4.001", "4.501", "5.001", "5.501", "5.627", "5.850"};
	//每篇源文档后面的分隔符	 													
	private static final String ENDSTRING = "********************************************";
	//分割后目标文档的路径位置
	private static final String OUTPUTPATH = ".\\src\\output\\files\\lisa\\";
	//分割后文档名称的公共部分
	private static final String OUTPUTNAME = "LISA";
	
	
	public static void main(String[] args)
	{	
		int count = 1;									//记录分割的文档数目
		int sourceFileCount = 0;
		long startTime = System.currentTimeMillis();
		long endTime = 0;
		
		File sourceFile = new File(SOURCEPATH + SOURCENAME + SOURCEFILE[0]);																		
		File outputFile = new File(OUTPUTPATH + OUTPUTNAME + Integer.toString(count));
		BufferedReader readIn = null;
		BufferedWriter readOut = null;
		
		try
		{
			//readIn = new BufferedReader(
			//			new InputStreamReader(
			//				new FileInputStream(sourceFile)));
			readIn = new BufferedReader(
						new FileReader(sourceFile));
			//readOut = new BufferedWriter(
			//			new OutputStreamWriter(
			//				new FileOutputStream(outputFile)));
			readOut = new BufferedWriter(
						new FileWriter(outputFile));
			
			String oneLineString = null;
			for(; sourceFileCount < SOURCEFILE.length; sourceFileCount++)
			{
				while(null != (oneLineString = readIn.readLine()))
				{
					if(!(ENDSTRING.equals(oneLineString)))
					{
						readOut.write(oneLineString);
						readOut.newLine();
					}
					else
					{
						count ++;
						readOut.close();
						outputFile = new File(OUTPUTPATH + OUTPUTNAME + Integer.toString(count));
						//readOut = new BufferedWriter(
									//new OutputStreamWriter(
										//new FileOutputStream(outputFile)));
						readOut = new BufferedWriter(
									new FileWriter(outputFile));
					}
				}
				readIn.close();
				readOut.close();

				if(SOURCEFILE.length == sourceFileCount + 1)
					break;
				
				sourceFile = new File(SOURCEPATH + SOURCENAME + SOURCEFILE[sourceFileCount + 1]);
				//readIn = new BufferedReader(
				//		new InputStreamReader(
				//			new FileInputStream(sourceFile)));
				readIn = new BufferedReader(
							new FileReader(sourceFile));
				outputFile = new File(OUTPUTPATH + OUTPUTNAME + Integer.toString(count));
				//readOut = new BufferedWriter(
				//		new OutputStreamWriter(
				//				new FileOutputStream(outputFile)));
				readOut = new BufferedWriter(
							new FileWriter(outputFile));
			}
			endTime = System.currentTimeMillis();
			
			outputFile.deleteOnExit();
			count --;
			
			System.out.println("Create " + count + " files took " + (endTime - startTime) + " milliseconds");
			
		}catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}
最后还想提及一下参考4中的一个处理I/O异常的方式(上述程序中没有按照这种方式),先给出一个程序例子:

package lzl.splitcode.lisa;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class DealWithIOException 
{
	public static void main(String[] args)
	{
		FileWriter fw = null;
		FileReader fr = null;
		try
		{
			fr = new FileReader("a.txt");
			fw = new FileWriter("b.txt");
			int temp;
			
			while(-1 != (temp = fr.read()))
			{
				fw.write(temp);
			}
			System.out.println("Copy completed!");
		}catch(IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				if(null != fr)
					fr.close();
			}catch(IOException e1)
			{
				e1.printStackTrace();
			}
			
			try
			{
				if(null != fw)
					fw.close();
			}
			catch(IOException e2)
			{
				e2.printStackTrace();
			}
		}
	}
}
首先需要注意到是变量的作用范围为{},所以如果在try{}中直接定义FileReader   fr = new FileReader("a.txt")的话,在catch代码块中将不能访问变量fr,所以应该在全局范围内定义一个引用。其次,流的关闭也有异常抛出(NullPointerException),所以也需要处理,所以需要对关闭的流进行是否为空的判断。如果有多个流对象需要关闭,则对每个流对象进行处理。

这篇关于Java I/O的博客内容就讲这多,后面还有后续的一些讲解。


参考资料:

1. 博客中两张I/O的类图来源于互联网

2. JDK1.7 API:http://docs.oracle.com/javase/7/docs/api/

3. Java编程思想(第四版)

4. Java核心技术 卷1/卷2

5. 传智播客毕向东Java基础视频教程


说明:

如有错误还请各位指正,欢迎大家一起讨论给出指导。

上述程序完整代码的下载链接:
https://github.com/zeliliu/BlogPrograms/tree/master/Java%E5%AD%A6%E4%B9%A0/split_file_demo

最后更新时间:2013-04-17


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值