黑马程序员--IO(一)--概述、字符流、字节流、流操作规律

黑马程序员--IO(一)--概述、字符流、字节流、流操作规律

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

一、概述

1、IO流(Input Output):用来处理设备之间的数据传输。java对数据的操作通过流的方式。java用于操作流的对象都在IO包中。
分类:
流按流分为:输入流和输出流。将外设中的数据读取到内存中:输入。将内存中数据写入到外设中:输出。
流按操作数据分为:字节流和字符流。
PS:
流只能操作数据,不能操作文件。
2、常用基类
字节流的抽象基类:InputStream  OutputStream
字符流的抽象基类:Reader  Writer
PS:
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:Reader的子类FileReader。

二、字符流

1、简述
由于字节流是单字节处理方式,当处理汉字这样双字节字符时,它就显得不太方便,这时我们通过字节流+编码表的方式获取字符流。
即让字节流读取文字字节数据后,不直接操作而是先查指定的编码表,获取对应的文字。来便捷文字操作。
字符流的抽象基类:Reader  Writer
2、字符流的读写
读取方式有两种:
read():一次读一个字符。而且会自动往下读。
read(char[] cbuf):将字符读入数组。更优。
//字符流读取数据方式有两种
import java.io.*;
class FileReaderDemo
{
	public static void main(String[] args)throws IOException
	{
		//创建一个文件读取流对象和指定名称的文件相关联。
		//要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
		FileReader fr = new FileReader("demo.txt");

		int ch = 0;

		//第一种方式:调用读取流对象的read()方法,它一次读一个字符。而且会自动往下读。
		while((ch=fr.read())!=-1)	
		{
			System.out.println("ch="+(char)ch);
		}

		FileReader fr2 = new FileReader("demo.txt");

		//第二种方式:read(char[] cbuf):将字符读入数组。以数组形式读取,一次性输出。更优。
		char[] buf = new char[1024];

		int num = 0;
		while((num=fr2.read(buf))!=-1)
		{
			System.out.println(new String(buf,0,num));
		}

		fr.close();
	}
}

/*
字符流:写入、续写、异常处理。
需求:在硬盘上,创建一个文件并写入一些文字数据。处理文字数据首先要考虑字符流。

FileWriter:专门用于操作文件的Writer子类对象。后缀名是父类名;前缀名是流对象功能。
FileWriter会将文件创建到指定目录下,如果该目录下已有同名文件,将被覆盖。
flush():用于对文件刷新。作用:将写入字符流输出到指定文件中。
close和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
*/
import java.io.*;
class FileWriterDemo
{
	public static void main(String[] args)
	{
		FileWriter fw = null;
		try
		{	//创建一个FileWriter对象。初始化时必须明确被操作文件。在构造函数中加入true,可实现对文件续写。
			fw= new FileWriter("demo.txt",true);

			//调用Write方法,字符串写入到流中。
			fw.write("---www\r\neeeee");
		}
		//处理异常
		catch(IOException e)
		{
			System.out.println("wrong:"+e.toString());
		}
		//用finally关闭流,可防止代码异常时流无法关闭的问题。
		finally
		{
			try
			{
				if(fw!=null)
					//关闭流资源,但是关闭前会刷新一次内部的缓冲的数据。
					fw.close();
			}
			catch(IOException e)
			{
				System.out.println("wrong too:"+e.toString());
			}
		}
	}
}

3、字符流的缓冲区-->BufferedWriter、BufferedReader
a 作用:在流的基础上对流的功能进行了增强。所以缓冲区创建之前,必须有对应的流对象。
b 原理:将数据以数组形式储存在内部,最后一次性取出。减少了数据的在内存上的读取频率,提高效率。
newLine():写入换行,返回数据及回车符。属于BufferedWriter类。可以跨平台。
readLine():读取一行数据。属于BufferedReader类。
原理:使用read方法,缓冲读取到的字符并判读换行标记,将标记前的缓冲数据变成字符串返回。

//缓冲区的应用:通过缓冲区复制一个java文件
import java.io.*;
class CopyByBuf
{
	public static void main(String[] args)
	{
		//缓冲区要先初始化,不然作用域只在try{}范围内。
		BufferedWriter bufw = null;
		BufferedReader bufr = null;
		try
		{
			//创建字符流缓冲区并加载字符流文件
			bufr = new BufferedReader(new FileReader("helloworld.java"));
			bufw = new BufferedWriter(new FileWriter("hello_copy.txt"));

			String line = null;
		
			//写入信息
			while((line=bufr.readLine())!=null)
			{
				bufw.write(line);
				bufw.newLine();
				bufw.flush();
			}
		}
		catch(IOException e)
		{
			throw new RuntimeException("读写失败!");
		}
		finally
		{
			try
			{
				if(bufr!=null)
					bufr.close();
			}
			catch(IOException e)
			{
				throw new RuntimeException("读取关闭失败!");
			}
			finally
			{
				try
				{
					if(bufw!=null)
						bufw.close();
				}
				catch(IOException e)
				{
					throw new RuntimeException("写入关闭失败!");
				}				
			}
		}
	}
}
LineNumberReader:跟踪行号的缓冲字符输入流。LineNumberReader是BufferedReader的子类。
此类定义了方法setLineNumber(int)和getLineNumber(),它们可分别用于设置和获取当前行号。
import java.io.*;
class LineNumberReaderDemo
{
	public static void main(String[] args)throws IOException
	{
		FileReader fr = new FileReader("helloworld.java");
	
		//读取标的文件
		LineNumberReader lnr = new LineNumberReader(fr);

		String line = null;

		//设置行号起点
		lnr.setLineNumber(100);

		//获取行号
		while((line=lnr.readLine())!=null)
		{
			System.out.println(lnr.getLineNumber()+":"+line);
		}

		lnr.close();
	}
}
PS:
1 缓冲区要结合流才可以使用。
2 无论是读一行还是多个字符,最终在硬盘上仍是一个一个读取。
3 read()和readLine()区别:
read():读取字符数据,它覆盖了父类的read方法。
readLine():另外开辟一个缓冲区,存储的是原缓冲区一行的数据,不包含换行符。

4、装饰设计模式
装饰设计模式:对原有类进行功能的改变、增强。装饰类和被装饰类通常都属于一个体系中。
装饰和继承比较:

//装饰设计模式
class Person
{
	public void chifan()
	{	
		System.out.println("吃饭");
	}
}
//继承方法-->覆盖
class NewPerson extends Person
{
	public void chifan()
	{
		System.out.println("漱口");
		super.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("甜点");
	}
}
class DecorateDemo
{
	public static void main(String[] args)
	{
		Person p = new Person();

		NewPerson np = new NewPerson();
		np.chifan();
		System.out.println("-------------------");
		SuperPerson sp = new SuperPerson(p);
		sp.superChifan();
	}
}
继承的体系:
Reader 专门用于读取数据的类。
  |--TextReader:用于读取文本
  |--MediaReader:用于读取媒体
功能扩展:提高读取效率,加入缓冲技术
Reader
  |--TextReader
  |--BufferTextReader
  |--MediaReader
  |--BufferMediaReader
加入再进行功能扩展,就会发现这个体系会越来越臃肿,不够灵活。

装饰体系思路:
既然加入的都是同一种技术--缓冲。继承是让缓冲和自己的流对象相结合。根据劳动分工,提高效率的特点。
我们可以将缓冲(同一属性的扩展功能)进行单独封装,各司其职,当需要时再针对性调用。
1、将缓冲进行单独封装

class Buffer
{
  Buffer(TextReader text){}
  Buffer(MediaReader media){}
}
2、优化。用多态封装,提高扩展性。
class BufferReader extends Reader
{
        private Reader r;
  BufferReader(Reader r){}
}
3、装饰体系
Reader 专门用于读取数据的类。
  |--TextReader
  |--MediaReader
  |--BufferReader

由上面可知装饰和继承的异同:
相同点:都是用于功能的扩展。
不同点:
a 装饰扩展性更强;b 装饰避免了继承臃肿的体系;c 装饰降低了类之间的关联性。
d 装饰是对原有类的补充,继承是覆盖或者说是替代。

三、字节流

1、简述
a 字节流:基本操作和字符流类相同。但它不仅可以操作字符,还可以操作其他媒体文件。
b 由于媒体文件数据都是以字节存储的,所以字节流对象可直接将媒体文件的数据写入到文件中,不用进行刷新动作。
c 字节流基类:InputStream(读)、OutputStream(写)
//读取字节流方式
import java.io.*;
class FileStream
{
	public static void main(String[] args)throws IOException
	{
		writerFile();
		readFile_1();
		readFile_2();
		readFile_3();
	}
	//方法一:单个字节获取
	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 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_3()throws IOException
	{
		FileInputStream fis = new FileInputStream("fos.txt");

		//int num = fis.available();

		//定义一个恰好的缓冲区,不用循环操作。
		byte[] buf = new byte[fis.available()];

		fis.read(buf);
		
		//System.out.println("num="+num);
		System.out.println(new String(buf));

		fis.close();		
	}
	public static void writerFile()throws IOException
	{
		//输出字节流到指定文件
		FileOutputStream fos = new FileOutputStream("fos.txt");
		
		//不涉及任何转换,所以不用刷新。
		fos.write("abcde".getBytes());

		fos.close();
	}
}
PS:
1 FileOutputStream、FileInputStream的flush方法内容为空,没有任何实现,调用没有意义。
2 int available():获取对象的数据大小。它是InputStream的特有方法。因为字节流可以操作媒体文件,所以直接获取数据大小,
可能因为获取的数据太大造成内存溢出。虚拟机默认内存是64M。

2、字节流的缓冲区:提高字节流读写效率。
2.1 读写存在类型提升和强转现象
read():会将字节byte类型提升为int类型。原因:防止直接返回byte类型值,造成read误判为程序结束标记的情形。
write():会将int类型强转为byte类型。原因:有始有终,保障数据唯一性。

/*
自定义字节流的缓冲区:
1,定义数组
2,定义指针
3,定义计数器。
*/
import java.io.*;
//装饰类
class MyBufferedInputStream
{
	private InputStream in;

	private byte[] buf = new byte[1024];

	private int pos = 0, count = 0;

	MyBufferedInputStream(InputStream in)
	{
		this.in = in;
	}

	//返回数据类型是int类型不是byte类型,这样避免了值为-1,正常情形下程序的停止。
	public int myRead() throws IOException
	{
		//每句第一次读取数据的情形。通过in对象读取硬盘上数据,并存储在buf中。并计数。
		if(count==0) 
		{
			count = in.read(buf);
			if(count<0)
				return -1;
			pos = 0;

			//数组以byte类型存储。
			byte b = buf[pos];
 
			count--;
			pos++;
			//b&255,此时的b已经是int类型,它&255会只获取一个8位,补充位补0,防止-1的出现。
			return b&255;
		}
		//每句话第二次读取数据的情形
		else if(count>0)
		{
			byte b = buf[pos];

			count--;
			pos++;
			//0xff=255
			return b&0xff;			
		}

		return -1;
	}
	public void myClose() throws IOException
	{
		in.close();
	}
}
PS:
它们的转变原理是:
因为数据的字节流形式是二进制:0,1;
该文件中,因为字节流中会存在一个字节全是1的情形:1111-1111
定理:负数的二进制等于整数二进制取反+1。
0000-0001   1的二进制
1111-1110 取反
0000-0001 +1
1111-1111   -1的二进制

因为返回指针值是byte类型,但是类的返回值是int类型,所以返回值存在类型提升问题。
这样定义其实是为了解决直接返回值为-1,造成read方法误判断为程序结束标记的情形。即:while((ch=fis.read())!=-1)

byte:-1  ---->  int:  -1; 
00000000 00000000 00000000 11111111类型提升时,若补位为0,则值为 255
11111111 11111111 11111111 11111111   类型提升时,若补位为1,则值为 -1

当byte:-1提升到int类型,若补位为1,其值仍为-1。read方法以读到-1为停止标记,造成程序停
止,无法满足需求。
所以我们要在前面补0,这样就可以避免-1的出现。

补0方式:&255
    11111111 11111111 11111111 11111111
 & 00000000 00000000 00000000 11111111
------------------------------------------------------------
    00000000 00000000 00000000 11111111
同时这也意味着,我们读取完后有4个字节需要写入,这样其实改变了原数据,
所以write实际执行时,会执行一个强转动作,只执行最后一个有效字节。

四、流操作规律

1、键盘录入
1.1 标准输入输出流
System.in:对应的标准输入设备:键盘。 它是InputStream类型。
System.out:对应的标准的输出设备,控制台。它是PrintStream类型。

1.2整行录入
当使用输入流进行键盘录入时,只能单字节录入。为了提高效率,可以自定义一个数组将一行字节进行储存。当一行录入完毕,
再放回本行录入内容。这种方式正是字符流的readLine方法的原理。
那我们为什么不直接使用readLine方法呢?但由于readLine方法是字符流BufferedReader类中方法。而键盘录入的read方法是字节流
InputStream的方法。这时我们必须将字节流转换成字符流再使用readLine方法。这样我们就用到了转换流。

1.3 转换流:InputStreamReader、OutputStreamWriter
作用:当字节流中数据都是字符时,将字节流转换成字符流。

1.3.1 InputStreamReader:将字节流转成字符流步骤:
         a 获取键盘录入对象
         InputStream in = System.in;
         b 将字节流对象转成字符流对象,使用转换流。
         InputStreamReader isr = new InputStreamReader(in);
         c 为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
         BufferedReader br = new BufferedReader(isr);
         以上步骤可简化为:最常用方式
         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

1.3.2 OutputStreamWriter:将字符流转成字节流。
         以字符形式录入,以字节形式存储到硬盘。步骤和InputStreamReader相似。

//转换流演示
import java.io.*;
class TransStreamDemo
{
	public static void main(String[] args) throws IOException
	{
		//获取键盘录入对象
		//InputStream in = System.in;

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

		//加入缓冲区
		//BufferedReader bufr = new BufferedReader(isr);

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


		//字符流转字节流。
		//OutputStream out = System.out;
		//OutputStreamWriter osw = new OutputStreamWriter(out);
		//BufferedWriter bufw = new BufferedWriter(osw);
		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();
	}
}
2、流操作的基本规律:
2.1
  源:键盘录入。
  目的:控制台。
2.2 需求:想把键盘录入的数据存储到一个文件中。
  源:键盘。
  目的:文件。
  文件是字符形式。使用InputStreamReader。
2.3 需求:想要将一个文件的数据打印在控制台上。
  源:文件。
  目的:控制台。
  控制台获取的是字节形式。使用OutputStreamWriter。
2.4 如何选择流对象?
通过三个明确,确定流对象的使用:
a 源和目的。
  源:读取流。InputStream  Reader
  目的:输出流。OutputStream Writer
b 明确体系:操作的数据是否是纯文本。
  是:字符流
  否:字节流
c 明确要使用的具体对象。通过设备来进行区分:
  源设备:内存,硬盘,键盘。
  目的设备:内存,硬盘,控制台。
2.5 事例分析:
2.5.1 需求:将一个文件中数据存储到另一个文件中。复制文件
1)源:读取流。InputStream  Reader
      明确体系:数据类型:纯文本:Reader
      明确对象:硬盘上的一个文件。Reader体系中可以操作文件的对象是:FileReader。
  FileReader fr = new FileReader("a.txt");
  提高效率
  BufferedReader bufr = new BufferedReader(fr);
2)目的:输出流:OutputStream Writer
      明确体系:数据类型:纯文本:Writer
      明确对象:硬盘上的一个文件。Writer体系中可以操作文件的对象FileWriter。
  FileWriter fw = new FileWriter("b.txt");
  提高效率
  BufferedWriter bufw = new BufferedWriter(fw);

2.5.2 需求:将键盘录入的数据保存到一个文件中。
1)源:InputStream Reader

      明确体系:数据类型:纯文本:Reader。
      明确对象:键盘。对应的是System.in。
      但Reader是字符流,System.in是字节流。为了操作键盘的文本数据,所以字符流最方便。
      所以既然明确了Reader,那么就将System.in转换成Reader。用Reader体系中的转换流:InputStreamReader
  InputStreamReader isr = new InputStreamReader(System.in);
  提高效率
  BufferedReader bufr = new BufferedReader(isr);
2)目的:OutputStream Writer
      明确体系:数据类型:纯文本:Writer。
      明确对象:硬盘上的一个文件:FileWriter。
  FileWriter fw = new FileWriter("c.txt");
  提高效率
  BufferedWriter bufr = new BufferedWriter(fw);
扩展:想要把录入的数据按照指定的编码表(utf-8)存到文件中。
2)目的:OutputStream  Writer
      明确体系:数据类型:纯文本:Writer。
      明确对象:硬盘上的一个文件:FileWriter。
      但是FileWriter是使用的默认编码表:GBK。但存储时,需要加入指定编码表utf-8。
      而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。
      而该转换流对象要接受一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream。
  OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");

  提高效率
  BufferedWriter bufw = new BufferedWriter(osw);

/*
练习:将键盘录入的数据按照utf-8编码表存入文件中。
思路:
1)源:InputStream Reader
   明确体系:数据类型:纯文本:Reader。
   明确对象:键盘:System.in。因为是文本数据,Reader最好操作,
   所以需要借助Reader体系中的转换流:InputStreamWriter。
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
2)目的:OutputStream Writer
   明确体系:数据类型:纯文本:Writer
   明确对象:硬盘上的文件:FileWriter。但是FileWriter是使用的默认编码表:GBK。因为存储时需要加入
   指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。
   而该转换流对象要接受一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream。
		BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(),"UTF-8"));
*/
import java.io.*;
class TransStreamDemo1
{
	public static void main(String[] args) throws IOException
	{
		//键盘录入的最常见写法:
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));

		BufferedWriter bufw = 
			new BufferedWriter(new OutputStreamWriter(new FileOutputStream("lady.txt"),"UTF-8"));

		String line = null;

		while((line=bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			bufw.write(line);
			bufw.newLine();
			bufw.flush();
		}
		bufr.close();
	}
}
运行结果:

PS:
1 转换流什么时候使用?
通常在涉及到字符编码转换时使用。它是字符和字节之间的桥梁。

2 异常日志信息
当程序在执行出现不希望直接给用户看的问题时,我们需要以异常日志的形式储存信息,方便程序员查看、调整。

//异常的日志信息独立反映。
import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfoDemo
{
	public static void main(String[] args)throws IOException
	{
		try
		{
			int[] arr = new int[2];
			System.out.println(arr[3]);
		}
		catch(Exception e)
		{
			try
			{
				Date d = new Date();
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//H:24小时制
				String s = sdf.format(d);

				PrintStream ps = new PrintStream("exeception.log");
				ps.println(s);
				System.setOut(ps);
			}
			catch(IOException ex)
			{
				throw new RuntimeException("日志文件创建失败");
			}
			e.printStackTrace(System.out);
		}
	}
}
3 系统属性信息文本
Properties getProperties():获取系统信息。
void list(PrintStream out):将信息输出到指定输出流中。
new PrintStream("sysinfo.txt"):将输出流中数据存入指定文件中。

// getProperties():确定当前的系统属性。
import java.util.*;
import java.io.*;
class SystemInfo
{
	public static void main(String[] args)throws IOException
	{
		Properties prop = System.getProperties();

		prop.list(new PrintStream("sysinfo.txt"));
	}
}
4 通过System类的setIn、setOut方法可以对默认设备进行改变。
System.setIn(new FileInputStream("1.txt")):将源改成文件1.txt。

System.setOut(new FileOutputStream("2.txt")):将目的改成文件2.txt。

5 体系图

字节流继承体系图:


字符流继承体系图:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值