Java的IO流解析

一、流的分类
按照流向分:输入流、输出流
输入流:只能从中读取数据,而不能向其写出数据
输出流:只能向其写出数据,而不能从中读取数据
这里的输出、输入都是从程序运行所在内存的角度来划分的
Java的输入流主要有InputStream和Reader作为基类,而输出流则主要有OutputStream和Writer作为基类
字节流和字符流
字节流和字符流的区别很简单,他们的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同:字节流操作的最小数据单元是8位的字节,而字符流操作的最小数据单元是16位的字符


字节流主要由InputStream和OutputStream作为基类
字符流主要由Reader和Writer作为基类


按照流的角色分,可以分为节点流和处理流
节点流:可以从/向一个特定的IO设备(如磁盘、网络)读\写数据的流,字节流也通常称为低级流。字节流来进行数据的输入\输出时,程序直接连接到实际的数据源,和实际的输入\输出节点连接
处理流:处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读\写功能,处理流也被称为高级流。当使用处理流来进行输入\输出时,程序并不会直接连接到实际的数据源,没有和实际的输入\输出节点连接。
使用处理流的好处:只要使用相同的处理流,程序就可以采用完全相同的输入\输出代码来访问不同的数据源,随着处理流锁包装的节点流的改变,程序实际所访问的数据源也相应的发生改变
备注:实际上Java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,即可消除不同节点流的实现差异,也可以提供更方便的方法来完成输入\输出功能,因此处理流也被称为包装流
二、流的概念模型
java的IO流的40多个类都是从4个抽象基类派生出来的
-->InputStream和Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
-->OutputStream和Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流


Java的处理流模型则体现了Java输入\输出流设计的灵活性。
处理流的功能主要体现在两个方面:
-->性能的提高:主要以增加缓冲流的方式来提高输入\输出的效率
-->操作的便捷:处理流可能提供了系列便捷的方法来一次输入\输出大批量的内容,而不是输入\输出一个或多个(字节\字符)
三、字节流和字符流
1、InputStream和Reader
InputStream和Reader是所有输入流的基类,它们都是两个抽象类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所有它们的方法是所有输入流都可以使用的方法
在InputStream里包含如下三个方法
-->int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)
-->int read(byte[] b):从输入流中读取最多b.length个字节的数据,并将其储存在字节数组b中,返回实际读取的字节数
-->int read(byte[] b,int off,int len):从输入流中读取最多len个字节的数据,并将其储存在数组b中,放入b数组中时,并不是从数组的起点开始,而是从offset位置开始,返回实际读取的字节数
在Reader里包含如下三个方法
-->int read():从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)
-->int read(char[] cbuf):从输入流中读取最多cbuf.length个字符的数据,并将其储存在字符数组cbuf中,返回实际读取的字符数
-->int read(char[] cbuf,int off,int len):从输入流中读取最多len个字符的数据,并将其储存在数组cbuf中,放入cbuf数组中时,并不是从数组的起点开始,而是从offset位置开始,返回实际读取的字符数
InputStream和Reader都是抽象类,本身不能创建实例,但他们分别有一个用于读取文件的输入流:FileInputStream和FileReader,他们都是节点流--会直接和指定文件关联。

eg:使用FileInputStream来读取自身的效果

public class FileInputStreamTest
{
	public static void main(String[] args) throws IOException
	{
		//创建字节输入流
		FileInputStream fis = null;
		try
		{
		
		fis = new FileInputStream("FileInputStreamTest.java");
		//创建一个长度为1024的字节数组
		byte[] bbuf = new byte[1024];
		//用于保存实际读取的字节数
		int hasRead = 0;
		while((hasRead = fis.read(bbuf)) > 0)
		{
		//取出字节数组内的字节,并将字节数组转换成字符串输入!
		System.out.print(new String(bbuf,0,hasRead));
		}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//关闭文件输入流
		finally
		{
			fis.close();
		}

	}
}

eg:使用FileReader来读取自身的效果
public class FileReaderTest
{
	public static void main(String[] args) throws IOException
	{
		//创建字符输入流
		FileReader fr = null;
		try
		{
		
		fr = new FileReader("FileReaderTest.java");
		//创建一个长度为32的字符数组
		char[] cbuf = new char[32];
		//用于保存实际读取的字符数
		int hasRead = 0;
		while((hasRead = fr.read(cbuf)) > 0)
		{
		//取出字节数组内的字节,并将字节数组转换成字符串输入!
		System.out.print(new String(cbuf,0,hasRead));
		}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//关闭文件输入流
		finally
		{
		//使用finally块来关闭文件输入流
			if(fr !=null)
			{
			fr.close();
			}
		}

	}
}
除此之外,InputStream和Reader还支持如下几个方法来移动记录指针
-->void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)
-->boolean markSupported():判断此输入流是否支持mark()操作,即是否支持记录标记
-->long skip(long n):记录指针向前移动n个字节/字符
2、OutputStream和Writer
OutputStream和Writer提供了如下三种方法
-->void writer(int c):讲指定的字节/字符输出到输出流中,其中c即可代表字节,也可以代表字符
-->void writer(byte[]/char[] buf):讲字节数组/字符数组中的数据输入到指定输出流中
-->void writer(byte[]/char[],int off,int len):讲字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中
因为字符流字节以字符为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。
Writer里还包含如下两个方法
-->void write(String str):将str字符串里包含的字符输出到指定输出流中
-->void write(String str,int off,int len):将str字符串里从off位置开始,长度为len的字符输出到指定输出流中

eg:使用FileInputStream来执行输入,并用FileOutputStream来执行输出,用以实现复制FileOutputStream.java文件功能
public void FileOutputStreamTest
{
	public static void main(String[] args) throw IOException
	{
		FileOutputStream fos = null;
		FileInputStream fis = null;
		try
		{
		//创建字节输入流
			fis = new FileInputStream("FileOutputStreamTest.java");
			//创建字节输出流
			fos = new FileOutputStream("newFile.txt");
			byte[] bbuf = new byte[1024];
			int hasRead = 0;
			while((hasRead = fis.read(bbuf)) > 0)
			{
			//没读取一次,即写入文件输出流,读了多少,就写多少
			fos.write(bbuf,0,hasRead);
			}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//关闭文件输入流
		finally
		{
			//使用finally块来关闭文件输入流
			if(fis !=null)
			{
			   fis.close();
			}
			//使用finally块来关闭文件输出流
			if(fos !=null)
			{
			   fos.close();
			}
		}

	}
}

eg:如果我们希望直接输出字符串内容,则使用Writer会有更好的效果

public void FileWriterTest
{
	public static void main(String[] args) throw IOException
	{
		FileWriter fw = null;
		try
		{
			//创建字符输出流
			fw = new FileWriter("poem.txt");
			fw.write("服务器在偷懒\r\n");
			fw.write("偷懒还这么任性\r\n");
			fw.write("服务器在偷懒,大家没有火车票\r\n");
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		finally
		{
			if(fw != null)
			{
			fw.close();
			}
		}
	}

}

上面程序在输出字符串内容时,字符串内容的最后是\r\n,这是Windows平台的换行符,通过这种方式就可以让输出的内容换行;如果是Unix/Linux/BSD等平台,只要使用\n就可以作为换行符


四、处理流的用法
处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入\输出方法,让程序员需要关系高级流的操作
处理流的典型思路:使用处理流来包装节点流,程序通过处理流来执行输入\输出功能,让节点流与底层的I\O设备、文件交互
处理流的识别:只要流的构造器不是一个物理节点流,而是已经存在的流,那么这种流一定是处理流;而所有节点流都是直接以物理IO节点作为构造器的

eg:下面程序使用PrintStream处理流来包装InputStream,使用处理流后的输出流在输出时将更加方便
public class PrintStreamTest{
	public static void main(String[] args) throws IOException {
		PrintStream ps = null;
		try
		{
			//创建一个节点输出流:FileOutputStream
			FileOutputStream fos = new FileOutputStream("text.txt");
			//以PrintStream来包装FileOutputStream输出流
			ps = new PrintStream(fos);
			//使用PrintStream执行输出
			ps.println("普通子字符串");
			ps.println(new PrintStreamTest());
		}
		catch(IOException ioe)
		{
			ioe.printStackTrace(ps);
		}
		finally
		{
			
			ps.close();
		}
	}

}
由于PrintStream类的输出功能非常强大,通常如果我们需要输出文本内容,都应该讲输出流包装成PrintStram后进行输出。
处理流的使用非常简单,通常只需要在创建处理流时传入一个节点流构造参数即可,这样创建的处理流就是包装了该节点流的处理流
当我们使用处理流来包装底层节点流之后,关闭输入\输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭该处理流包装的节点流
五、输入/输出流体系
以数组为物理节点的节点流除了在创建节点流时需要传入一个字节数组或者字符数组之外,用法上与文件节点流相似。
字符流还可以使用字符串作为物理节点,用于实现从字符串内读取内容,或将内容写入字符串(实际上用StringBuffer充当了字符串)的功能

eg:下面程序示范了使用字符串作为物理节点的字符输入\输出流的用法
public class StringNodeTest{
	public static void main(String[] args) throws IOException {
		String src ="从明天起,做一个幸福的人\r\n"
				+"喂马,劈柴,周由世界\r\n"
				+"告诉他们我是幸福的\r\n";
		StringReader sr = new StringReader(src);
		char[] buffer = new char[32];
		int hasRead = 0;
		try
		{
			while((hasRead = sr.read(buffer)) > 0){
				System.out.print(new String(buffer,0,hasRead));
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		finally
		{
			sr.close();
		}
		//创建StringWrite时,实际上以一个StringBuffer作为输出节点
		//下面指定的20就是StringBuffer的初始长度
		StringWriter sw = new StringWriter(20);
		//调用StringWriter的方法执行输出
		sw.write("我远离了大海,\r\n");
		sw.write("我远离了小溪,\r\n");
		sw.write("我远离了海流,\r\n");
		sw.write("我远离了湖泊,\r\n");
		System.out.println("-----下面是sw的字符串节点里的内容");
		//使用toString方法返回StringWriter的字符串节点的内容
		System.out.println(sw.toString());
	}

}
上面程序与前面使用FileReader和FileWriter的程序基本类似,只是在创建StringReader和StringWriter对象时传入的是字符串节点,而不是文件节点。由于String是不可变的字符串对象,所以StringWriter使用StringBuffer作为输出节点
六、转换流
输入\输出流体系里提供了两个转换流,这两个转换流用于实现将字节流转换成字符流,
其中InputStreamReader将字节流转换成字符输入流
其中OutputStreamWriter将字节输出流转换成字符输出流
没有字符流转换成字节流的原因:字节流比字符流使用范围更广,但字符流比字节流操作更方便。我们知道已有的字节流内容都是文本,那么把它转换成字符流来处理就会更方便一些。所以Java只提供了将字节流转换成字符流的转换流,没有提供将字符流转换成字节流的转换流。
下面以获取键盘输入为例来介绍转换流的用法,Java使用System.in代表标准输入,即键盘输入,但这个标准输入流就是InputStream类的实例,使用不太方便而且我们知道键盘输入内容都是文本内容,所以我们可以使用InputStreamReader将其转换成字符输入流,普通Reader读取输入内容时依然不太方便,我们可以将普通Reader再次包装成BuffedReader,利用BuffedReader的readLine方法可以一次读取一行内容。

eg:
public class PrintStreamTest{
	public static void main(String[] args) throws IOException {
		
		BufferedReader br = null;
		try
		{
			//将System.in对象转换成Reader对象
			InputStreamReader reader = new InputStreamReader(System.in);
			//将普通的Reader包装成BufferedReader
			br = new BufferedReader(reader);
			String buffer = null;
			//采用循环一行一行的读取数据
			while((buffer = br.readLine()) !=null)
			{
				//如果读取的字符串为“exit”,程序退出
				if(buffer.equals("exit"))
				{
					System.exit(1);
				}
				//打印读取的内容
				System.out.println("读取内容为:"+buffer);
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		finally
		{
			br.close();
		}
	}
}

上面程序中将System.in包装成BufferedReader,BufferedReader流具有一个缓冲的功能,它可以一次读取一行文本--以换行符为标志,如果它没有读到换行符,则程序阻塞,等到读到换行符为止。
由于bufferedReader具有一个readLine方法,可以非常方便地一次读取一行内容,所有经常把读取文本内容的输入流包装成BufferedReader,用以方便地读取输入流的文本内容。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值