黑马程序员-JAVA高级(IO输入与输出)PART2

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------


四个大知识点:

1.字符缓冲流以及装饰设计模式;

2.字节流和字节缓冲流;

3.转换流,包括读取转换流和写入转换流;

4.流操作规律。

三个小知识点:

1.改变标准输入输出设备;

2.将异常信息输出到日志文件中;

3.将系统信息输出到文本文件中。


一、字符缓冲流和装饰设计模式

缓冲区的出现是为了提高数据的读写效率。一个简单的小例子就是喝水,一滴一滴的喝不如用杯子接满再喝,杯子就是缓冲区。

1.BufferedWriter

将文本写入字符输出流,缓冲各个字符,从而提供字符、数组和字符串的高效写入。

import java.io.*;

class BufferedWriterDemo 
{
	public static void main(String[] args) 
	{
		//声明文件写入流变量,缓冲区的出现是为了提高流的效率,所以在创建缓冲区
		//之前必须先有流对象
		FileWriter fw = null;
		//声明缓冲流变量
		BufferedWriter bufw = null;
		//处理异常
		try
		{
			//初始化文件写入流对象
			fw = new FileWriter("buf.txt");
			//初始化缓冲流对象
			bufw = new BufferedWriter(fw);

			//缓冲流的写方法实际上调用的还是文件写入流的写方法	
			//bufw.write("naxienian");
			//只要用到缓冲区,就必须刷新
			//bufw.flush();

			for (int x=0; x<5; x++)
			{
				bufw.write("wqnmlgb"+x);
				bufw.newLine();//换行,跨平台,仅缓冲区有此方法
				bufw.flush();//防止断电,你懂的
			}

		}
		catch (IOException e)
		{
			throw new RuntimeException("写入失败!");
		}
		finally
		{
			if(bufw!=null)
				try
				{
					bufw.close();//关闭缓冲流,实际上就是关闭文件写入流
				}
				catch (IOException e)
				{
					throw new RuntimeException("流关闭失败!");
				}
		}
	}
}
以上代码中的几个小点:

(1)所谓“防止断电”,是指bufw对象每写一行数据就必须刷新一次,把数据写入到相应的目的地中,如果不这样,那所有数据都被暂时放在缓冲区中,如果在最后关闭流(关闭流前会刷新一次,将所有数据写入目的地)前,发生某种意外(类似于电脑断电时,如果未保存工作,那么内存中的数据都会被清空),那么所有数据就会全部消失。而如果写一次数据刷新一次,写过的数据将会得到保存。

(2)关闭缓冲流,实际上就是关闭缓冲流所操作的流,所以不必再写被操作的流关闭的代码。

(3)BufferedWriter类提供了一个跨平台的换行方法newLine()。在windows中换行符为\r\n,在linux中为\n

2.BufferedReader

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行(注意这里是行,BufferedWriter中是字符串)的高效读取。

/*
文件读取流缓冲区。
*/
import java.io.*;

class BufferedReaderDemo 
{
	public static void main(String[] args) 
	{
		BufferedReader bufr = null;
		try
		{
			FileReader fr = new FileReader("buf.txt");
			bufr = new BufferedReader(fr);

			String line = null;
			//readLine()方法一次读一行,读到文件末尾返回null
			while((line=bufr.readLine())!=null)
				System.out.println(line);
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败!");
		}
		finally
		{
			if(bufr!=null)
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("流关闭失败!");
				}
		}
	}
}
以上代码中的小点:

BufferedReader类提供了一个一次读一行的方法readLine(),方便对文本数据的获取。当返回null时,表示文件读到末尾。

3.通过缓冲区复制文本

/*
通过缓冲区复制一个.java文件。
*/
import java.io.*;

class CopyTextByBuf 
{
	public static void main(String[] args) 
	{
		BufferedReader bufr = null;
		BufferedWriter bufw = null;

		try
		{
			/*
			使用匿名对象创建读取流和写入流分别与原文件和目的文件关联,
			并作为参数传递给对应缓冲流的构造函数。
			*/

			//FileReader fr = new FileReader("BufferedWriterDemo.java");
			bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));

			//FileWriter fw = new FileWriter("BufferedWriterDemo_copy.java");
			bufw = new BufferedWriter(new FileWriter("BufferedWriterDemo_copy.java"));

			String line = null;
			while((line=bufr.readLine())!=null)//用readLine()方法一次读取一行
			{
				bufw.write(line);//每读一行就写一行
				bufw.newLine();//readLine()方法读取的数据不包括换行符,所以要写入换行
				bufw.flush();//刷新,保存数据
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读写失败");
		}
		finally
		{
			if(bufr!=null)
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			if(bufw!=null)
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
		}
	}
}

4.自定义缓冲流

/*
自己写一个缓冲区类
*/
import java.io.*;

class MyBufferedReader extends Reader//继承Reader,要实现抽象方法
{
	private Reader r;
	MyBufferedReader(Reader r)
	{
		this.r = r;
	}
	public String myReadLine() throws IOException//抛出异常
	{
		StringBuilder sb = new StringBuilder();//容器,替代数组(原缓冲类中用的就是数组)

		int ch = 0;
		while((ch=r.read())!=-1)
		{
			if((char)ch == '\r')//这里可以不用转换ch
				continue;
			if((char)ch == '\n')//这种写法如果文件末尾没有换行符就会导致无法输出最后一行
				//return sb.toString();
				break;//我自认为可以这么写
			else
				sb.append((char)ch);
		}

		//增加以下代码防止最后一行无法输出
		if(sb.length()!=0)
			return sb.toString();
		
		return null;//如果什么都没有就返回null
	}

	public void myClose() throws IOException
	{
		r.close();
	}

	//实现父类的抽象方法
	public int read(char[] cbuf, int off, int len) throws IOException
	{
		return r.read(cbuf,off,len);
	}

	public void close() throws IOException
	{
		r.close();
	}
}

class MyBufferedReaderDemo
{
	public static void main(String[] args) 
	{
		MyBufferedReader bufr = null;
		try
		{
			bufr = new MyBufferedReader(new FileReader("buf.txt"));

			String line = null;
			while((line=bufr.myReadLine())!=null)
				System.out.println(line);
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败!");
		}
		finally
		{
			if(bufr!=null)
				try
				{
					bufr.myClose();
				}
				catch (IOException e)
				{
					throw new RuntimeException("流关闭失败!");
				}
		}
	}
}
以上代码中的小点:

主要是理解readLine()方法的原理,读一行归根到底还是被缓冲的流对象一个字符一个字符的读取,每读到一个字符就放入缓冲区中(在这里使用StringBuilder,实际上应使用数组),当读到换行符的时候说明达到了一行的末尾,将之前读取到的所有字符输出就是一行数据。

5.装饰设计模式

当想要给已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能提供增强的功能。该自定义的类就是装饰类。

装饰类通常会通过构造函数接收被装饰的类。

上面的缓冲类就是对应字符流的装饰类。

小例子

/*
装饰设计模式
*/
class Person
{
	public void eat()
	{
		System.out.println("吃饭");
	}
}

class SuperPerson
{
	Person p;
	SuperPerson(Person p)
	{
		this.p = p;
	}

	public void superEat()
	{
		System.out.println("吃开胃小菜");
		p.eat();
		System.out.println("吃饭后甜点");
	}
}

class DecorateDemo 
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		//p.eat();

		SuperPerson sp = new SuperPerson(p);
		sp.superEat();
	}
}


装饰和继承

假设MyReader是一个读取数据的类,它有两个读取不同数据类型的子类MyTexReaderMyMediaReader,体系如下

MyReader

        |--MyTextReader

        |--MyMediaReader

为了提高效率,想加入缓冲区,但如果每个子类都创建一个装饰类,整个体系就会显示臃肿且扩展性差,如下

MyReader

        |--MyTextReader

            |--MyBufferTextReader

        |--MyMediaReader

            |--MyBufferMediaReader

因为所有缓冲的功能都类似,改为如下代码

class MyBufferReader
{
    MyBufferReader(MyTextReader text){}
    
    MyBufferReader(MyMediaReader media){}
}

上面代码同样扩展性很差,如果需要缓冲的类很多的话。所以找到其参数的共同类型,通过多态的形式提高扩展性。
class MyBufferReader
{
    MyBufferReader(MyReader r){}    
}
装饰类用于增强已有对象,具备的功能和已有对象是相同的,只不过提供了更强的功能,所以装饰类和被装饰类通常都是属于同一个体系。
class MyBufferReader extends MyReader
{
    MyBufferReader(MyReader r){}
}

最后整个体系如下

MyReader

        |--MyTextReader

        |--MyMediaReader

        |-- MyBufferReader
装饰设计模式比继承要灵活,避免了继承体系的臃肿,也降低了类与类之间的关系。
6.LineNumberReader
BufferedReader的子类,跟踪行号的字符缓冲读取流,可以设置行号和读取行号。
import java.io.*;

class LineNumberReaderDemo 
{
	public static void main(String[] args) 
	{
		LineNumberReader lnr = null;
		try
		{
			lnr = new LineNumberReader(new FileReader("DecorateDemo.java"));

			String line = null;
			lnr.setLineNumber(100);//设置行号
			while((line=lnr.readLine())!=null)
			{
				System.out.println(lnr.getLineNumber()+":"+line);//获取每行行号
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		finally
		{
			if(lnr!=null)
				try
				{
					lnr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("关闭失败");
				}
		}
	}
}
模拟一个带行号的缓冲类
/*
模拟一个带行号的缓冲类
*/

import java.io.*;

//继承BufferedReader
class MyLineNumberReader extends BufferedReader
{
	private int lineNumber;
	MyLineNumberReader(Reader r)
	{
		super(r);
	}

	public String readLine() throws IOException
	{
		/*
		String line = super.readLine();
		if(line!=null)//不用判断
			lineNumber++;

		return line;
		*/
		lineNumber++;
		return super.readLine();
	}

	public int getLineNumber()
	{
		return lineNumber;
	}

	public void setLineNumber(int lineNumber)
	{
		this.lineNumber = lineNumber;
	}
}

//不继承BufferedReader
/*
class MyLineNumberReader
{
	private Reader r;
	private int lineNumber;
	MyLineNumberReader(Reader r)
	{
		this.r = r;
	}

	public String readLine() throws IOException
	{
		StringBuilder sb = new StringBuilder();

		int ch = 0;
		while((ch=r.read())!=-1)
		{
			if(ch=='\r')
				continue;
			if(ch=='\n')
			{
				lineNumber++;
				return sb.toString();
			}
			else
				sb.append((char)ch);
		}

		if(sb.length()!=0)
		{
			lineNumber++;
			return sb.toString();
		}
		return null;
	}

	public int getLineNumber()
	{
		return lineNumber;
	}

	public void setLineNumber(int lineNumber)
	{
		this.lineNumber = lineNumber;
	}

	public void myClose() throws IOException
	{
		r.close();
	}
}
*/

class MyLineNumberReaderDemo 
{
	public static void main(String[] args) 
	{
		MyLineNumberReader mylnr = null;
		try
		{
			mylnr = new MyLineNumberReader(new FileReader("LineNumberReaderDemo.java"));

			String line = null;
			mylnr.setLineNumber(44);
			while((line=mylnr.readLine())!=null)
			{
				System.out.println(mylnr.getLineNumber()+":"+line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		finally
		{
			if(mylnr!=null)
				try
				{
					mylnr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("关闭失败");
				}
		}
	}
}

二、字节流和字节缓冲流

1.字节流读写操作,FileInputStream和FileOutputStream

import java.io.*;

class FileStream 
{
	public static void main(String[] args) throws IOException
	{
		readFile_3();
	}

	public static void readFile_3() throws IOException
	{
		FileInputStream fis = new FileInputStream("fos.txt");

		//int len = fis.available();
		byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区,不用再循环了
		fis.read(buf);
		System.out.println(new String(buf));
	}

	public static void readFile_2() throws IOException
	{
		FileInputStream fis = new FileInputStream("fos.txt");

		byte[] buf = new byte[1024];
		int len = 0;
		while((len=fis.read(buf))!=-1)
			System.out.println(new String(buf,0,len));

		fis.close();
	}

	public static void readFile_1() throws IOException
	{
		FileInputStream fis = new FileInputStream("fos.txt");

		int ch = 0;
		while((ch=fis.read())!=-1)
			System.out.println((char)ch);

		fis.close();
	}

	public static void writeFile() throws IOException
	{
		FileOutputStream fos = new FileOutputStream("fos.txt");

		fos.write("haoren".getBytes());//不用刷新也能输出

		fos.close();//流最后必须关闭
	}
}
以上代码中的小点:

(1)字节输出流不用刷新方法也能将数据打印到目的地,但最后流还是必须关闭;

(2)字节输入流的available()方法须慎用,防止爆内存;

(3)字符流用的是字符数组,字节流用的是字节数组。

2.使用字节流拷贝图片

思路:

(1)用字节读取流和原图片文件相关联;

(2)用字节写入流创建一个图片文件,用来存储读取过来的数据;

(3)循环读取原文件数据,写入目标文件;

(4)关闭流,释放资源。

/*
复制一个图片
*/
import java.io.*;

class CopyPic 
{
	public static void main(String[] args) 
	{
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try
		{
			fis = new FileInputStream("pic.jpg");
			fos = new FileOutputStream("pic_copy.jpg");

			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("duxie fail");
		}
		finally
		{
			if(fis!=null)
				try
				{
					fis.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			if(fos!=null)
				try
				{
					fos.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
		}
	}
}
需要注意不要用字符流拷贝媒体文件。

3.拷贝MP3文件,使用字节流的缓冲区BufferedInputStream和BufferedOutputStream

/*
复制Mp3文件,通过字节流的缓冲区
*/
import java.io.*;

class CopyMp3 
{
	public static void main(String[] args) throws IOException 
	{
		long start = System.currentTimeMillis();
		copy();
		long end = System.currentTimeMillis();

		System.out.println("拷贝费时:"+(end-start)+"毫秒");
	}

	public static void copy() throws IOException 
	{
	    //创建缓冲字节读取流
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Adele-Rolling In The Deep.mp3"));
		//创建缓冲字节写入流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("music_copy.mp3"));

		/*
		每读一个字节就写一个字节
		*/
		int by = 0;
		while((by=bis.read())!=-1)
			bos.write(by);

		//关闭流,释放资源
		bos.close();
		bis.close();
	}
}
4.自定义字节流 的缓冲区
/*
自定义缓冲字节流
*/
import java.io.*;

class MyBufferedInputStream 
{
	//创建字节数组作为缓冲区
	private byte[] buf = new byte[1024];
	/*
	定义计数器和指针,计数器的作用是记录缓冲区中字节的数量,
	指针的作用是记录当前读到的缓冲区中字节的位置。
	*/
	private int count,pos;
	private InputStream in;
	MyBufferedInputStream(InputStream in)
	{
		this.in = in;
	}

	//一次读一个字节,从缓冲区(字节数组)获取
	public int myRead() throws IOException
	{
		/*
		如果计数器等于0,就通过in对象从硬盘上读取数据到缓冲区中,
		计数器记录读取到的字节数量,指针归零。并从缓冲区中读出一个字节,
		同时计数器减1,指针加1,最后返回读出的字节。
		如果计数器大于0,直接从缓冲区中读出一个字节,同时计数器减1,
		指针加1,最后返回读出的字节。
		*/
		if(count==0)
		{
			pos = 0;
			count = in.read(buf);
			if(count<0)
				return -1;
			
			byte b = buf[pos];//这里b的类型应该定义为byte而不是int
			count--;
			pos++;
			return b&255;//防止出现-1
		}
		else if(count>0)
		{
			byte b = buf[pos];
			count--;
			pos++;
			return b&0xff;//防止出现-1
		}

		return -1;
	}

	public void myClose() throws IOException
	{
		in.close();
	}
}
以上代码中的小点:
为什么返回读取的字节时要“与”255?因为读到的是字节,而返回值类型是整型(4个字节),如果读到的字节是1111 1111,在字节转整型时高24位均为1,即11111111 11111111 11111111 11111111,十进制数值为-1,但此时其实并没有读到文件末尾。为了防止这种情况的发生,可以在返回值时,把得到的字节和255(即二进制00000000 00000000 00000000 11111111)做“与”运算,这样得到的整型数高24位均为0,低8位仍为原字节。

三、转换流
1.读取键盘录入
System.out:对应标准输出设备,控制台;
System.in:对应标准输入设备,键盘。
需求:通过键盘录入数据,录入一行数据后,将改行数据进行打印,如果录入的数据是over则停止录入。
/*
读取键盘录入
*/
import java.io.*;

class ReadIn 
{
	public static void main(String[] args) throws IOException 
	{
		InputStream in = System.in;
		StringBuilder sb = new StringBuilder();

		while(true)
		{
			int ch = in.read();
			if(ch==13)//即字符'\r'
				continue;
			if(ch==10)//即字符'\n'
			{
				String s = sb.toString();
				if(s.equals("over"))
					break;
				
				System.out.println(s.toUpperCase());
				sb.delete(0,sb.length());//输出一行之后就把之前的数据清空
			}
			else
				sb.append((char)ch);
		}

		//不用关闭流
	}
}
发现以上代码中通过键盘录入一行数据并打印的过程就是读一行数据的原理。
2.读取转换流InputStreamReader:字节流通向字符流的桥梁
    写入转换流OutputStreamWriter:字符流通向字节流的桥梁
/*
转换流
*/
import java.io.*;

class TransStreamDemo 
{
	public static void main(String[] args) 
	{
		BufferedReader bufr = null;
		BufferedWriter bufw = null;
		try
		{
			bufr = new BufferedReader(new InputStreamReader(System.in));

			bufw = new BufferedWriter(new OutputStreamWriter(System.out));

			String line = null;
			while((line=bufr.readLine())!=null)
			{
				if(line.equals("over"))
					break;
				//System.out.println(line.toUpperCase());
				bufw.write(line.toUpperCase());
				bufw.newLine();
				bufw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取异常");
		}
		finally
		{
			if(bufr!=null)
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取资源关闭失败");
				}
			if(bufw!=null)
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入资源关闭失败");
				}
		}
	}
}

四、流操作规律
三个明确:
1.明确源和目的
源:输入流。 InputStream Reaer
目的:输出流。 OutputStream Writer
2.操作的数据是否是纯文本
是:字符流。
不是:字节流。
3.明确要使用哪个具体的对象,通过设备来区分。
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台

几种情况:
1.将一个文本文件的数据存储到另一个文本文件中,即复制。
分析:因为操作的数据是纯文本,所以读取用 Reader,写入用 Writer;因为源设备和目的设备都是硬盘,所以读取用 FileReader,写入用 FileWriter;要提高效率,所以读取用 BufferedReader,写入用 BufferedWriter
BufferedReaer bufr = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
2.将键盘录入的数据保存到一个文本文件中。
分析:因为操作的数据是纯文本,所以读取用 Reader,写入用 Writer;因为源设备是键盘,对应的 System.in是字节流,所以用读取转换流 InputStreamReader,目的设备是硬盘,所以写入用 FileWriter;要提高效率,所以读取用 BufferedReader,写入用 BufferedWriter
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new FileWriter("c.txt"));
3.按照指定的编码将一个文本文件的数据存储到另一个文本文件中。
分析:输入流情况同情况1。因为只有转换流可以指定编码,所以输出流用 OutputStreamWriter;要提高效率,所以用 BufferedWriter
BufferedReaer bufr = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d.txt")), "UTF-8");

五、改变标准输入输出设备
System.setIn(InputStream is);//改变标准输入设备
S ystem.setOut(PrintStream ps);//改变标准输出设备

六、将异常信息输出到日志文件中
/*
异常的日志信息
*/
import java.io.*;
import java.text.*;
import java.util.*;

class ExceptionInfoDemo 
{
	public static void main(String[] args) 
	{
		try
		{
			int[] arr = new int[3];
			int a = arr[3];//脚标越界,发生异常
		}
		catch (Exception e)
		{
			try
			{
				//以特定格式获得日期时间
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				String d = sdf.format(new Date());

				PrintStream ps = new PrintStream("ex_info.log");
				ps.println(d);//打印日期时间
				System.setOut(ps);//改变标准输出设备
				ps.close();
			}
			catch (IOException ex)
			{
				throw new RuntimeException("创建异常日志文件失败");
			}
			e.printStackTrace(System.out);//将异常信息打印到标准输出设备中
		}
	}
}

七、将系统信息输出到文本文件中
import java.io.*;
import java.util.*;

class SystemInfo 
{
	public static void main(String[] args) 
	{
		Properties prop = System.getProperties();
		
		try
		{
			prop.list(new PrintStream("sysinfo.txt"));
		}
		catch (IOException e)
		{
			e.printStackTrace(System.out);
		}
	}
}



---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值