天天看点

黑马程序员----java中的IO流基础

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、IO流概述

设备之间要进行数据传输的时候,java给出了实现方法,那就是IO流。java对数据就是通过"流"的方式来操纵,具体实现依靠IO包中的类。

流(stream)的概念源于UNIX中管道(pipe)的概念。在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是Internet上的某个URL。

流的方向是重要的,根据流的方向,流可分为两类:输入流和输出流。用户可以从输入流中读取信息,但不能写它。相反,对输出流,只能往输入流写,而不能读它。

 实际上,流的源端和目的端可简单地看成是字节的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。 形象的比喻——水流 ,文件======程序 ,文件和程序之间连接一个管道,水流就在之间形成了,自然也就出现了方向:可以流进,也可以流出.便于理解,这么定义流: 流就是一个管道里面有流水,这个管道连接了文件和程序。

 java.io包中的stream类根据它们操作对象的类型是字符还是字节可分为两大类: 字符流和字节流。按流向又可以分为输入流和输出流。  

                      字节流                                字符流
输入流       InputStream                        Reader
输出流       OutputStream                     Writer
这是字节流和字符流的四个抽象基类,由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀,例如InputStream的子类FileInputStream,Writer的子类FileWriter.

二、字符流

顾名思义,字符流的操作是以字符为对象,这样一来,操纵文本类型数据更加方便和有效率。而字符需要编码表来编码,所以字符流中融合了编码表,且自动查找使用编码表。字符流是基于字节流的,专门对文字文本处理。

1.字符流的两个基类:Reader和Writer

Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。
2.FileReader和FileWriter:分别是这两个的子类,专门用来操作文本类型文件的字符流,继承体系如下
java.lang.Object
 ->java.io.Writer
      ->java.io.OutputStreamWriter
          ->java.io.FileWriter   
java.lang.Object
  ->java.io.Reader
      ->java.io.InputStreamReader
          ->java.io.FileReader
3.字符流的文件操作
流是用来处理IO操作的,一般而言,最常见的是对文件进行读写操作。如何创建文件并写入内容:
(1)创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(“Test.txt”);
(2)调用流对象的写入方法,将数据写入流
fw.write(“text”);
(3)关闭流资源,并将流中的数据清空到文件中。
fw.close();
记得IO操作完成后,要关闭流,这样可以释放内存资源。
读取文件内容:
(1)创建流对象,选中源文件,要保证文件存在,否则发生异常
FileReader fr = new FileReader("demo.txt");
(2)调用read方法,读取原文件中的数据
FileReader的read()方法:read()
 读取单个字符。 
int read(char[] cbuf) 
将字符读入数组。 
abstract  int read(char[] cbuf, int off, int len)  
将字符读入数组的某一部分。 
(3)关闭流资源

三、字符流代码示例:

1.创建文件并写入数据

这里要用到字符流

注意:
定义文件路径时,可以用“/”或者“\\”。
在创建一个文件时,如果目录下有同名文件将被覆盖。
在读取文件时,必须保证该文件已存在,否则出异常。
<span style="font-family:Microsoft YaHei;">import java.io.*;
class  FileWriterDemo
{
	public static void main(String[] args) throws IOException
	{
		//创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。
		//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。
		//其实该步就是在明确数据要存放的目的地。
		FileWriter fw = new FileWriter("demo.txt");//创建对象并初始化,相当于调用构造函数并初始化赋值

		//调用write方法,将字符串写入到流中。
		fw.write("abcde");

		//刷新流对象中的缓冲中的数据。
		//将数据刷到目的地中。
		//fw.flush();

		//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。
		//将数据刷到目的地中。
		//和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
		fw.close();
	}
}</span>
           
注意:IO操作因为涉及到文件等原因,所以可能产生异常IOExcepiton,这一点要注意,具体处理方式这里不赘述。
2.对已有文件的数据进行续写
<span style="font-family:Microsoft YaHei;">import java.io.*;
class  FileWriterDemo3
{
	public static void main(String[] args) throws IOException
	{
		//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
		FileWriter fw = new FileWriter("demo.txt",true);
		
		fw.write("nihao\r\nxiexie");

		fw.close();
	}
}</span>
           
3.文本文件的第一种读取方式:
用read方法,特点是每次调用会自动往下读,一次读一个字符
<span style="font-family:Microsoft YaHei;">import java.io.*;

class  FileReaderDemo
{
	public static void main(String[] args) throws IOException
	{
		//创建一个文件读取流对象,和指定名称的文件相关联。
		//要保证该文件是已经存在的
		FileReader fr = new FileReader("demo.txt");

		//调用读取流对象的read方法。
		//read():一次读一个字符。而且会自动往下读。
		
		int ch = 0;

		while((ch=fr.read())!=-1)//read方法会从上次调用时的位置继续往下读,未读完,返回值是Unicode编码,若读完了,就返回-1
		{
			System.out.println((char)ch);//转换成字符并打印
		}
		fr.close();
	}
}</span>
           
4.文本文件的第二种读取方式
定义一个字符数组作为缓冲,用来存储读到的数据
<span style="font-family:Microsoft YaHei;">import java.io.*;

class FileReaderDemo2 
{
	public static void main(String[] args) throws IOException
	{
		FileReader fr = new FileReader("demo.txt");
		
		//定义一个字符数组。用于存储读到字符。
		//该read(char[])返回的是读到字符个数。
		char[] buf = new char[1024];

		int num = 0;
		while((num=fr.read(buf))!=-1)
		{
			System.out.println(new String(buf,0,num));//打印数组buf组成的字符串
		}
		fr.close();
	}
}</span>
           
5.复制文本文件
复制文件其实就是通过IO流将数据读出,然后写入到新建的文件中。
步骤:
1,在D盘创建一个文件。用于存储C盘文件中的数据。
2,定义读取流和C盘文件关联。
3,通过不断的读写完成数据存储。
4,关闭资源。
<span style="font-family:Microsoft YaHei;">import java.io.*;

class CopyText 
{
	public static void main(String[] args) throws IOException
	{
		copy_2();
	}
	public static void copy_2()
	{
		FileWriter fw = null;
		FileReader fr = null;
		try
		{
			fw = new FileWriter("SystemDemo_copy.txt");
			fr = new FileReader("SystemDemo.java");

			char[] buf = new char[1024];

			int len = 0;
			while((len=fr.read(buf))!=-1)
			{
				fw.write(buf,0,len);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读写失败");
		}
		finally
		{
			if(fr!=null)
				try
				{
					fr.close();
				}
				catch (IOException e)
				{
				}
			if(fw!=null)
				try
				{
					fw.close();
				}
				catch (IOException e)
				{
				}
		}
	}
	//从C盘读一个字符,就往D盘写一个字符。
	public static void copy_1()throws IOException
	{
		//创建目的地。
		FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");

		//与已有文件关联。
		FileReader fr = new FileReader("RuntimeDemo.java");
		int ch = 0;

		while((ch=fr.read())!=-1)
		{
			fw.write(ch);
		}		
		fw.close();
		fr.close();
	}
}</span>
           

四、字符流缓冲区

BufferedReader和BufferedWriter:是字符流的缓冲流

这两个类对应的流使用了缓冲,是在流的基础上对流的功能进行增强,能大大提高输入输出的效率。因为是基于字符流,所以要现有字符流才能建立相应的缓冲流。

针对不同平台中换行符不同的情况,缓冲流中提供了封装当前平台换行符的方法:BufferedWriter :newLine()。

1.BufferedWriter
<span style="font-family:Microsoft YaHei;">import java.io.*;

class  BufferedWriterDemo
{
	public static void main(String[] args) throws IOException
	{
		//创建一个字符写入流对象。
		FileWriter fw = new FileWriter("buf.txt");

		//为了提高字符写入流效率。加入了缓冲技术。
		//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
		BufferedWriter bufw = new BufferedWriter(fw);

		for(int x=1; x<5; x++)
		{
			bufw.write("abcd"+x);
			bufw.newLine();
			bufw.flush();//必须要刷入数据
		}
		//记住,只要用到缓冲区,就要记得刷新。
		//bufw.flush();
		//其实关闭缓冲区,就是在关闭缓冲区中的流对象。
		bufw.close();
	}
}</span>
           

2.BufferedReader

该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。

当返回null时,表示读到文件末尾。注意,该方法会忽略回车符。

<span style="font-family:Microsoft YaHei;">import java.io.*;

class  BufferedReaderDemo
{
	public static void main(String[] args) throws IOException
	{
		//创建一个读取流对象和文件相关联。
		FileReader fr = new FileReader("buf.txt");

		//为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。
		BufferedReader bufr = new BufferedReader(fr);
		
		String line = null;

		while((line=bufr.readLine())!=null)//readLine方法一次读一行,返回读到的该行字符串,读到末尾则返回空
		{
			System.out.print(line);
		}
		bufr.close();
	}
}</span>
           

五、装饰设计模式

1.什么是装饰类

当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。

那么自定义的该类称为装饰类。装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。

例如:
Person有吃饭的功能,如果要对这个功能进行增强,增加一些其他的功能,可以将Person对象传入SuperPerson中,在后者的方法中调用前者的方法,并增加功能
<span style="font-family:Microsoft YaHei;">class Person
{
	public void chifan()
	{
		System.out.println("吃饭");
	}
}
class SuperPerson 
{
	private Person p ;
	SuperPerson(Person p)
	{
		this.p = p;
	}
	public void superChifan()
	{
		System.out.println("开胃酒");
		p.chifan();
		System.out.println("甜点");
		System.out.println("来一根");
	}
}
class  PersonDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		//p.chifan();

		SuperPerson sp = new SuperPerson(p);
		sp.superChifan();
	}
}</span>
           
2.装饰与继承的区别
装饰设计模式有者继承的一些特点,但二者是不同的:
继承体系因为子类继承父类所有的特征,所以导致体系变得臃肿,而装饰设计模式通过传递对象参数的方法,只增强特定功能,提高了灵活性,并且降低了类与类之间的关联,优化了体系结构。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常是都属于一个体系中的。
应用装饰设计模式,可以用新方法模拟已有的类中的方法

六、字节流 

Java的字节流
InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
字节流是比字符流更加通用的IO方式,可以用来对非文本文件如图片,音乐,视屏等进行复制等操作。
操作文件的字节流FileOutputStream和FileInputStream:
父类OutputStream                   InputStream
子类FileOutputStream            FileInputStream
字节流示例:

1.读取文件信息到控制台

写入信息到文件

<span style="font-family:Microsoft YaHei;">import java.io.*;
class  FileStream
{
	public static void main(String[] args) throws IOException
	{
		readFile();
		writeFile();
	}
	public static void readFile()throws IOException
	{
		FileInputStream fis = new FileInputStream("fos.txt");
		
		byte[] buf = new byte[fis.available()];//available()方法返回文件的总大小,可以定义一个刚刚好的缓冲区。不用在循环了。

		fis.read(buf);

		System.out.println(new String(buf));

		fis.close();
	}
	public static void writeFile()throws IOException
	{
		FileOutputStream fos = new FileOutputStream("fos.txt");
		
		fos.write("abcde".getBytes());

		fos.close();
	}
}</span>
           

2.拷贝图片

复制一个图片

思路:

1,用字节读取流对象和图片关联。
2,用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
3,通过循环读写,完成数据的存储。
4,关闭资源。
<span style="font-family:Microsoft YaHei;">import java.io.*;
class  CopyPic
{
	public static void main(String[] args) 
	{
		FileOutputStream fos = null;
		FileInputStream fis = null;
		try
		{
			fos = new FileOutputStream("c:\\2.bmp");
			fis = new FileInputStream("c:\\1.bmp");

			byte[] buf = new byte[1024];

			int len = 0;

			while((len=fis.read(buf))!=-1)
			{
				fos.write(buf,0,len);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("复制文件失败");
		}
		finally
		{
			try
			{
				if(fis!=null)
					fis.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("读取关闭失败");
			}
			try
			{
				if(fos!=null)
					fos.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("写入关闭失败");
			}
		}
	}
}</span>
           

七、字节流的缓冲区

BufferedOutputStream

BufferedInputStream

用法与字符流的缓冲区相似

示例:

复制mp3文件,使用缓冲区
<span style="font-family:Microsoft YaHei;">import java.io.*;
class  CopyMp3
{
	public static void main(String[] args) throws IOException
	{
		long start = System.currentTimeMillis();
		copy_2();
		long end = System.currentTimeMillis();
		System.out.println((end-start)+"毫秒");
	}
	public static void copy_2()throws IOException
	{
		MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
		BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
		
		int by = 0;
		//System.out.println("第一个字节:"+bufis.myRead());

		while((by=bufis.myRead())!=-1)
		{
			bufos.write(by);
		}
		bufos.close();
		bufis.myClose();
	}
	//通过字节流的缓冲区完成复制。
	public static void copy_1()throws IOException
	{
		BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
		BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
		
		int by = 0;
		while((by=bufis.read())!=-1)
		{
			bufos.write(by);
		}
		bufos.close();
		bufis.close();		
	}
}</span>
           

八、字符流和字节流的转换流

InputStreamReader字节流转换成字符流
本身是字符流,通过构造方法传入一个字节流
new InputStreamReader(InputStream in)
OutputStreamWriter字符流转换成字节流
本身是字节流,通过构造方法传入一个字符流
new OutputStreamWriter(OutputStream)
转换流的由来:
转化流是字符流与字节流之间的桥梁,使用转换流方便了字符流与字节流之间的操作。当字节流中的数据都是字符时,转成字符流操作更高效。
例如:标准输入输出。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in))键盘录入常见代码
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out))控制台输出常见代码
代码示例
1.通过键盘录入数据。
当录入一行数据后,就将该行数据进行打印。
如果录入的数据是over,那么停止录入
<span style="font-family:Microsoft YaHei;">import java.io.*;

class  TransStreamDemo
{
	public static void main(String[] args) throws IOException
	{
		//获取键盘录入对象。
		//InputStream in = System.in;

		//将字节流对象转成字符流对象,使用转换流。InputStreamReader
		//InputStreamReader isr = new InputStreamReader(in);

		//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
		//BufferedReader bufr = new BufferedReader(isr);

		//键盘录入的最常见写法。
		BufferedReader bufr = 
				new BufferedReader(new InputStreamReader(System.in));

		String line = null;

		while((line=bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			System.out.println(line.toUpperCase());
		}
		burf.close();
		
		BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

		String line = null;

		while((line=bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			bufw.write(line.toUpperCase());
			bufw.newLine();
			bufw.flush();
		}
		bufr.close();
	}
}</span>
           

九、流操作的基本规律

流对象有很多,到底什么时候该用哪一个呢?通过三个明确来完成。
1,明确源和目的。
源:输入流。InputStream/Reader
目的:输出流。OutputStream/Writer。
2,操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘。键盘
目的设备:内存,硬盘,控制台。
4,是否需要高效IO操作
使用对应的缓冲流。
掌握以上这些,就能熟练使用IO流了。

更多方法请参见java的API文档。