java基础之IO技术_1

        IO技术是java中十分、特别、非常重要的一门技术,也是java的开发中使用频率相当高的技术,所以必须要掌握。

一.IO技术简介


1.IO流的概念

IO技术我们也成为IO流,是Input(输入)和Output(输出)的简写,他是用来对电脑上的的数据进行传输的技术,我们首先要明确一点就是所有的数据无论是图片、文本、电影等等在电脑上的体现就是一堆数字,而IO技术就是用来处理这堆数字的,java给我们提供了流的概念,好像这些数字就是一汪水潭,IO就像是水管可以抽水和放水,非常形象。IO流的的主要特征有5点:

        1>IO流用来处理设备间的数据传输

        2>Java对数据的操作是通过流的方式。

        3>Java用于操作流的对象都在IO包中。

        4>IO流按照操作数据可以分为字符流和字节流。

        5>IO流按照流向可以分为输入流和输出流。

2.字符流与字节流

        在java早期的时候是只有字节流的,因为无论任何数据在电脑上的体现都是用10来表示,在数据的分类中有一个常见的类型就是字符,各个国家的文字都有一张自己的码表,用来标识自己的文字所对应的二进制数,例如欧美使用的是ASCII码表,中国使用的是GBK码表,这两个码表中的数字可能会有冲突的情况,我用中国的码表写出来的数据如果用外文的码表来查就会出现乱码的情况,所以就出现了字符流,他可以指定码表,同时也拥有了专门应用与操作字符的特有方法。

        字节流和字符流都拥有自己的体系,每种流都有很多的对象,但是他们通过不断抽取都会形成一个抽象基类用来表征这个体系中的方法。字节流的抽象基类是:InputStream、OutputStream;字符流的抽象基类是Reader、Writer。

        这四个类就是他们的初始形态了,由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀,前缀则是该类的功能,十分容易区分。例:InputStream的子类FileInputStream;Reader的子类FileReader。

我们来看一看IO体系中的常用类的分类:



二.字符流


1.Writer抽象类

Writer是字符写入流体系中的抽象基类,他主要有三个方法:

        1>  写入,无返回值

        方法名是Write(参数);通过重载方式可以写入单个字符、字符数组、字符串。

        2>  刷新,无返回值

        flush();注意Writer写入字符后并不是写入到文件里面而是先把这些字符存储到流中,利用刷新方法才能把这个字符写入到指定的文件中。

        3>  关闭

        close();关闭此流,我们开启流后就相当于开启了一条通道,这条通道是占用我们的底层资源和内存空间的,如果我们已经完成了任务那么这条通道就必须要关闭掉。注意点是调用此方法是会先刷新一下,把缓冲区的内容写入到文件中然后才关闭流。

由于Writer是抽象基类,不可以new对象,我们通过他的子类FileWriter来演示一下基本方法的使用:

class IODemo {
	
	public static void main(String[] args) {
		/*新建一个FileWriter对象,注意构造函数没有空参的,因为我们一开始就要
		指定该对象要把字符写入到哪个文件中,而且如果指定的路径中有同名文件那么
		就会覆盖掉他。另外创建对象时可能会抛出异常,因为指定的路径可能不存在,
		所以要捕捉或者抛出*/
		
		//为了让该对象作用于整个函数我们把对象定义在外面
		FileWriter fw = null;
		//创建对象、写入、刷新都会抛出异常,他们是紧密联系在一起的,所以可以放到一个捕获体系中
		try {
			//在e盘下创建一个"IO.txt"文件,把字符写入到该文件中
			fw = new FileWriter("e://IO.txt");
			//写入字符串,其实是写入到流当中,并不是写入到文件中
			fw.write("IO,你好!");
			
			//刷新才能把字符串写入到文件中
			fw.flush();
			
			//写入字符数组
			char[] ch = {'字','符','流'};
			fw.write(ch);
			
		} catch (IOException e) {
			System.out.println(e.toString());
		}
		
		//关闭流的动作是一定要执行的,所以要写到finally中来
		finally{
			/*关闭流的动作中会先刷新一次流,即便我们没有把字符数组刷到文件中,
			关流时也会先刷新一次。另外关流操作也会抛出异常*/
				try {
					if(fw!=null)
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

        在这个示例中,我们的创建了文件“IO.txt”,如果在指定目录下有一个IO.txt文件,则会覆盖掉这个文件,如果我们想在原有文件的基础上进行续写怎么办,FileWriter有一个构造方法:FileWriter(String fileName,boolean append ),当append为true ,新加内容写到 fileName文件的末尾,续写而不是覆盖。

class IOWriterDemo2 {
	public static void main(String[] args) {
		// 对“IO.txt“文件进行续写
		FileWriter fw = null;
		try {
			//找到该文件
			fw = new FileWriter("e://IO.txt",true);
			//换行重新写入文字
			fw.write("\r\n文件的续写");
			//刷新
			fw.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		//关流
		finally
		{
			try {
				if(fw!=null)
					fw.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
	}
}

2.Reader字符读取基类

Reader基类可以读取字符文件,他的最主要的方法就是读取和关闭:

        1>  读取方法

                a>read()

                返回int类型,当read方法是空参时,会逐个获取字符,并把此字符转为int类型返回,当读取到最后再次调用此方法时会返回-1,代表已经读到文件结尾。

                b>read(char[]  buf)

                返回int类型,将字符读取到字符数组中,此方法返回的是读到的字符个数,如果读到文件的结尾处,那么返回-1,注意此方法是一次性把文件中的内容读取到数组当中。只要我们定义的字符数组可以包容文件内的字符那么就能读取到此文件的所有内容。

        2>close()

        关闭读取流,此方法是一定要执行的。

注意点:在初始化该体系的子类时要明确目的文件,如果该文件不存在则会报出找不到文件异常:FileNotFoundException。

           我们通过他的子类FileReader来演示一下这些方法的使用,FileReader使用的是默认编码表,在我的电脑中默认码表为GBK。

class FileReaderDemo1 {
	
	public static void main(String[] args) {
		
		//定义一个文件读取对象
		FileReader fr = null;
		try {
			/*因为要读取文件,所以必须在初始化时指定文件名。
			由于文件可能不存在,所以要捕获异常*/
			fr = new FileReader("e://IO.txt");
			
			//第一种读取方法,读取单个字符
			//getFile1(fr);
			
			//第二种读取方式,读取字符数组
			getFile2(fr);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		//无论是否有异常,流是必须要关闭的。
		finally
		{
			try {
				if(fr!=null)
					fr.close();
			} catch (IOException e2) {
				e2.printStackTrace();
			}
		}
	}

	//第一种读取文件的方式:读取单个字符
	private static void getFile1(FileReader fr) {
		int num = 0;
		//read方法会抛异常在此捕获一下
		try{
			while((num = fr.read())!=-1)
			{
				//因为返回int类型,所以要强转为char类型
				System.out.print((char)num);
			} 
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
	
	//第二种读取文件的方式:读取字符数组
	private static void getFile2(FileReader fr) {
		/*首先定义一个字符数组,由于事先不知道文件的大小,我们定义数组
		长度的时候一般都定义成1024的整数倍,也就是以2k为单位*/
		char[] cbuf = new char[1024];
		//捕捉异常
		try {
			//将字符读取到字符数组中,返回-1代表读到最后
			int num = 0;
			while((num = fr.read(cbuf))!=-1)
			{
				System.out.print(new String(cbuf,0,num));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3.读取流与写入流的关联

        我们可以利用读取流读取一个文件中的数据然后通过写入流写入到指定的文件中,这就是两个流的关联,用于复制文件,下面通过一个示例来演示一下,我们把D盘下的IOtest.java文件复制到E盘下:

class ReaderTest1 {
	public static void main(String[] args) {
		//定义字符读取流与字符写入流
		FileWriter fw = null;
		FileReader fr = null;
		try {
			//写入流的目的地是E盘IOTest.java文件,没有则会创建
			fw = new FileWriter("e://IOTest.java");
			//读取流的源头是D盘下的IOTest.java文件
			fr = new FileReader("d://IOTest.java");
			//定义字符数组来读取源文件
			char[] cbuf = new char[1024];
			int num = 0;
			while((num = fr.read(cbuf))!=-1)
				//将读取到的字符数组写入到写入流中
				fw.write(cbuf,0,num);
				//刷新该流,写入到文件中
				fw.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		//关流,注意两个流要分别关闭
		finally
		{
			try {
				if(fr!=null)
				fr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				if(fw!=null)
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

三.字符流缓冲区


1.字符流缓冲区简介

        字符流缓冲区是在字符流的基础上对流进行了功能的增强,缓冲区的概念是把数据先存放在一个数组当中,然后再一起读取或者写入,这样提高了读写的效率,这个把数据存放在数组当中的动作由缓冲区帮我们完成了,我们只需调用相关方法就可以使用。

缓冲区的主要特征如下:

        1>  缓冲区的出现提高了对数据的读写效率

        2>  缓冲区是在流的基础上进行了增强,所以要结合流才能使用,所以在构建缓冲区对象是必须要和流关联在一起。

        3>  缓冲区对应两个类,写入缓冲区:BufferedWriter和读取缓冲区BufferedReader。

        4>  缓冲区的内部封装的是数组。

2.BufferedWriter

        BufferedWriter指的是写入流缓冲区,此类在初始化时必须要与字符输出流关联在一起,而且还可以指定缓冲区的大小,如果我们要写入的数据并不是非常大的话,默认的缓冲区域就足够了。

        此类的方法与字符输出流没有太大区别,但是这个类有一个特有方法:newLine(),这个方法可以实现换行的功能,而且这个方法是依赖于虚拟机的,无论在什么样的操作系统下,这个方法都可以实现换行的功能。

下面来演示一下此类的使用:

class BufferedWriterDemo {

	public static void main(String[] args) {
		// 首先创建一个字符写入流
		FileWriter fw = null;
		//创建一个字符写入流缓冲区
		BufferedWriter bufw = null;
		try {
			fw = new FileWriter("e://缓冲区.txt");
			//将字符写入流与缓冲区关联
			bufw = new BufferedWriter(fw);
			//利用缓冲区的写入方法写入数据,注意此方法同写入流方法相同,并不是写到文件中
			bufw.write("你好,缓冲区!");
			//特有方法newLine()可以直接换行,不用再手写”\r\n“来实现换行了。
			bufw.newLine();
			//刷新缓冲区
			bufw.flush();
			//继续写入
			bufw.write("吧啦啦啦...");
			
		}
		//为了演示方便,此处就用IOException概况
		catch (IOException e) {
			e.printStackTrace();
		}
		/*关闭字符写入流缓冲区,注意点是关闭缓冲区就是关闭与其相关的流对象,
		他的底层就是调用字符写入流的close()方法,所以不用再去关流了*/
		finally{
			
			if(bufw!=null)
				try {
					bufw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

3.BufferedReader

      此类是字符读取流缓冲区,其使用方法与Reader大同小异,但是这个类中有一个十分重要的方法:readLine(),这个方法可以读取一行文本,返回String类型,当返回null时,代表已经读到此文件的结尾处,返回来的这行文本是不包含回车符的,我们读取出来后需要自己加回车符。

      readLine()方法的读取原理:读取流无论是读一行还是读多个字符其实都是调用read()方法一个个读取,只是readLine()方法读取到回车符“\r\n”时,会把这一行数据转成String类型返回。

      我们来看一看此类的使用:
class BufferedReadeDemo {

	public static void main(String[] args) {
		//定义全局变量,以便可以关流
		FileReader fr = null;
		BufferedReader bufr = null;
		
		try {
			//读取e盘下的”缓冲区.txt“文件
			fr = new FileReader("e://缓冲区.txt");
			//将流对象与缓冲区关联起来
			bufr = new BufferedReader(fr);
			
			//利用readLine()读取一行数据
			String line = null;
			while((line = bufr.readLine())!=null)
				//readLine()方法不包含换行符,所以得我们自己加换行
				System.out.println(line);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		//关流,只要关闭缓冲区,就相当于关闭了读取流
		finally
		{
			if(bufr!=null)
				try {
					bufr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

四. 装饰设计模式


1.装饰设计模式的简介

        装饰设计模式听起来很专业的样子,其实很简单,就是定义一个类,这个类可以对已有的类进行功能的扩展,这就叫装饰设计模式,这个自定义类就叫装饰类。以BufferedReader和FileReader为例来说明,FileReader不具备readLine()方法,但是我们又不想去更改FileReader这个类,于是就定义了一个类BuffedReader来对FileReader进行功能的扩展。

装饰类一般都会通过构造方法来接收被装饰类对象,然后基于被装饰类对象的方法再提供一些更为强大的功能。

2.装饰和继承的区别

        继承也可以解决对原有类进行功能扩展的任务,但是当一个体系中的类都面临着升级时,这个体系就会变得十分的臃肿,假如我们有一个读取体系,他的顶层形态是Reader,旗下还有读取文本的类、读取视频的类等等,但是我们想扩展这些子类的功能时,利用继承的话,每一个子类会再创建一个子类来完成功能的扩展,这个体系就变得庞大不好掌握。

        我们就可以设计一个装饰类,这个类可以对TextReader、MedioReader等子类进行功能的扩展,我们所有的子类对象都可以传递进这个装饰类中添加缓冲功能,也就是这个装饰类可以接收任意子类对象,而这些子类又都是Reader体系下的,根据多态的原理,我们在装饰类构造方法中接收Reader类型的就可以了,经过装饰类的替代继承,我们发现整个体系就不用再创建那么多子类来完成功能的扩展了。

        通过分析这个体系的演化方式我们可以看到装饰设计模式比继承要灵活,而且避免了体系的臃肿,降低了类与类之间的关系。这个装饰类是用来对这个体系中的子类进行功能的增强的,所以这个装饰类也是属于此体系。由此我们可以看到在构建我们的程序时不要过多的依赖继承,继承可能会让我们的体系变得复杂,可以适时适当的使用装饰设计模式来完成功能的增强。

五.字节流


1.字节流简介

        字节流按流向可分为字节写入流(InputStream)与字节读取流(OutputSteam),字节流与字符流的不同之处在于字节流可以处理任何的数据,因为任何数据都是以字节形式存放在电脑上的,无论是文本还是图片我们都可以使用字节流来操作,字节流与字符流的操作方式大同小异,原理都是一样的,必须要与我们电脑上的文件关联在一起,而且都会调用底层的资源,也就是必须要关闭流,下面介绍一下字节流的使用。

2.OutputStream字节写入流

(1)OutputStream常用方法

        1>写入方法,无返回值

                a>  write(byte[] b )

                将整个字节数组中的数据写入到文件中

                b>  write(byte[] b,int strat,int len)

                将字节数组中从start位开始一直写入len个字节

                c>write(int b)

                是一个抽象方法,可以将指定的字节数组写到此流中。  

        2>flush()

        刷新写入流,强制写出把缓冲区的所有字节,注意点是当我们调用write()方法写数据的时候,即便我们不刷新他,他也会把数据写入到文件中,这是因为当我们没有指定缓冲区时是一个一个字节往文件中存的,flush()的作用在于当有缓冲区时,调用flush()方法会将缓冲区里的数据写出去,比如说我们在操作字符输出流Writer体系的类时,字符流的底层是调用的字节流,我们指定一个字符可能对应多个字节,字符流会先把这多个字节看做一组,这就相当于缓冲区,如果不调用刷新方法就无法把缓冲区里的内容写出去,这也是字符流和字节流的不同之处。

        3>close()

        关流,无论是字符流还是字节流都一定要执行的方法,这个方法通常也是写在finally语句中,字节输出流的close方法不带有刷新缓冲区的作用

        我们通过OutputStream的子类FileOutputStream类演示一下字节输出流的使用:

class FileOutputStreamDemo {

	public static void main(String[] args) {
		// 新建一个OutPutStream子类对象
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream("e://小绿.txt");
			
			//String类中的getBytes()方法将字符串转换成字节数组然后全部写出
			fos.write("小绿,你好!\r\n".getBytes());
			
			//从字节数组中的第2位开始,写两位长度
			fos.write("我是小宏!".getBytes(),2,7);
			//无需调用flush方法就可以写出数据
			//fos.flush();
			
		} catch (IOException e) { 
			e.printStackTrace();
		}
		//关流
		finally{
			if(fos!=null)
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
	/*
	 * 在e盘下打开“小绿.txt”文件,里面的内容是:
	 * 	小绿,你好!
		是小宏
	 * */
}

3.InputStream字节读取流

(1)字节读取流是按照字节来读取文件的,他的读取方法中有读一个字节或者读一堆字节然后存放到字节数组中去,利用字节读取流可以读取任意数据,我们来看看他的常见方法。

        1>读取方法,返回值为int,当读取到文件末尾时返回-1。

                a>  read()

                此方法是逐个读取字节,读取一个文件需要循环的次数较多,所以此方法并不如他的重载形式使用起来方便。

                b>  read(byte[] b)

                这个方法可以将读取到的字节存放在字节数组中,使用起来比较方便,应用比较多。

                c>  read(byte[] b,int start,int len)

                此方法也是将读取到的字节从strat下标位开始存储,读取len个长度,但读取到的字节数可能会小于len(因为读取到了文件的末尾)。

        2>  available()

        返回类型为int,此方法可以返回相关联文件的字节长度,因此我们在构建字节数组缓冲区的时候就可以知道该定义多少长度的数组了。

        此方法要慎用,因为如果读取的文件超大的话,返回来的数字就会很大,我们再建立一个超大的字节数组那么很可能会产生内存溢出的风险。

        3>  close()

        必须要调用的关流方法,不再赘述。

        我们通过复制一个图片来演示一下读取流和写入流的配合使用:

/*
* 1.首先使用字节读取流获取到该图片
* 2.利用字节输出流创建一个图片文件,用于存储获取到的图片数据
* 3.循环读写完成图片的复制
* 4.关闭资源
* */
class FileOutputStreamDemo {

	public static void main(String[] args) {
		// 构建字节流对象作用于整个函数
		FileOutputStream fos = null;
		FileInputStream fis = null;
		try {
			//读取一张图片
			fis = new FileInputStream("e://图片//cat.jpg");
			//写入到e盘
			fos = new FileOutputStream("e://猫.jpg");
			
			//available()方法可以返回字节数目,用于创建一个大小刚好的数组(慎用)
			byte[] buf = new byte[fis.available()];
			
			//把字节读到该数组中
			fis.read(buf);
			//把数组中的内容写入到指定文件
			fos.write(buf);			
		} catch (IOException e) { 
			e.printStackTrace();
		}
		//关闭资源
		finally{
			if(fos!=null)
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if(fis!=null)
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

4.字节流缓冲区

(1)字节流也对应有缓冲区:BufferedInputStream字节读取流缓冲区和BufferedOutputStream字节输出流缓冲区。他们和字符流缓冲区一样都要与其对应的流关联在一起,他们的出现是因为内部封存了数组,可以对我们读取的字节有一个临时存储的作用。这个两个类的使用与字符缓冲区没有太大区别,原理是一样的,在这就不演示了。

(2)我们通过山寨一个字节读取缓冲区的reade()方法来复制一个MP3文件,以此说明一个更为重要的内容,就是read和write方法的底层到底是怎样运行的:

//首先我们自定义字节流读取缓冲区
class MyBufferedInputStream
{
	//缓冲区中需要一个字节数组来存储读到的数据
	private byte[] buf = new byte[1024];
	
	//定义一个指针和计数器来判断读取的情况
	private int pos =0,count = 0;
	
	//构造方法将流传递进来,此处为演示方便直接传入File下的输入流
	private FileInputStream in;
	MyBufferedInputStream(FileInputStream in)
	{
		this.in = in;
	}
	//定义读取方法
	public int myRead()throws IOException 
	{
		/*当计数器为0时,我们定义的字节数组才被全部取出,我们才可以
		继续往里面装入数据*/
		if(count==0)
		{
			/*通过in来读取硬盘上的数据,并存入buf中,同时记录下字节的个数
			注意当read读取到最后没有字节了会返回-1*/
			count = in.read(buf);
			
			//当流的read方法读取到最后返回了-1,这时候我们也返回-1
			//代表已经读取完毕
			if(count<0)
				return -1;
	
			//计数器重新归0
			pos = 0;
			//取0指针下的元素
			byte b = buf[pos];
			
			count--;
			pos++;
			return b&255;//参看后面的重点讲解
		}
		//当计数器不为0时,我们不再往数组中转入数据
		else if(count>0)
		{
			//取该指针下的元素
			byte b = buf[pos];
			count--;//当计数器为0时,我们重新装入数据
			pos++;
			return b&0xff;//同b&255
		}
		return -1;

	}
	//复写close()方法
	public void myClose()throws IOException
	{
		in.close();	
	}
}
class MyBIStream
{
	public static void main(String[] args) 
	{
		
		MyBufferedInputStream bfis = null;
		BufferedOutputStream bfos = null;
		try
		{
			bfis = new MyBufferedInputStream(new FileInputStream("E:\\菊花台.mp3"));
			bfos = new BufferedOutputStream(new FileOutputStream("E:\\菊花.mp3"));
			int by = 0;
			//注意使用缓冲区的时候不用再自己定义数组,缓冲区里面有数组
			while((by = bfis.myRead())!= -1)
			{
				bfos.write(by);
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		//关流
		finally
		{
			try
			{
				if(bfis!=null)
					bfis.myClose();
			}
			catch (IOException e)
			{
				e.printStackTrace();}
			try
			{
				if(bfos!=null)
					bfos.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();}
		}
	}
}

        程序解析:是不是因为代码稍多,就不知道东西南北了。但这个这个示例我自我感觉十分重要,他不仅有底层方法的简述,还有一个程序员的思想。

        让我们来看一看return b这句话吧,首先我们定义的 b 是byte类型的,但是myRead()方法返回值是int类型的,在IO体系中你会发现读取方法返回值都是int类型的,刚开始的时候我也没有注意这点,只记住返回-1时就代表到达文件结尾就OK了,但是现在我们来看一看为什么他要是int类型而不是byte类型。

        如果我们直接返回b时,我们定义的b就被自动提升了,因为b是byte类型的,但是有一个问题,这个问题会导致我们的程序不能使用,虽然我们的思路是正确的,但是往往还是考虑不全,会遗忘掉最基础的东西:二进制。二进制是计算机最为基础的东西,我们虽然在让程序编写更适合人的思考方式,但是我们依旧在跟机器打交道,所以我们一定不可以忽略掉机器的思想。

        回归示例中,我们知道MP3文件在电脑上就是一堆二进制数,我们是一个字节一个字节的读取的,这是会出现一个特殊情况:读到了1111 1111,如果byte型的这个字节是他的话,他是谁,他就是是-1

问题就在这,而我们的接收值是int型的,b被提升为int,byte型提升为int型,由2个字节变为4个字节,少变长高位补1,但我们取到的int还是是-1,我们就依旧不会进行写入操作,我们的程序就废了,那么为什么还要提升为int呢,原因就是我们可以在保留原有数据基础上把这个-1改变,但是怎么改变呢?

这是byte型的1111 1111(-1)

11111111  11111111  11111111  11111111   这是提升后int型的-1;

而如果前面不补1,而补0变成:

0000000000000000 00000000 11111111 (这个值是255)

这就可以避免了-1,然而怎么可以把    byte型的11111111变为我们想要的int型的11111111,答案就是让他与上255,也就是

        11111111 11111111 11111111 11111111

&      00000000 00000000 00000000 11111111

        ------------------------------------------------------------

        00000000 00000000 00000000  11111111

所以我们要把b&255,才可以把-1改变掉。然而当我们返回值变成了int,成为了4个字节,那么在写入缓冲区操作的类BufferedOutputStream中write()方法岂不是也写4个字节,这样我们的文件就会胖了4倍,所以java在写write方法时把这个int型转换为byte型,然后再把这个字节数据写入到指定文件中。


六.转换流


1.转换流的介绍

        在IO体系中我们说子类的名称一般都是以父类名作为后缀名,功能作为前缀名,但是在字符流流体系下有两个类:InputStreamReader和OutputStreamWriter,这两个类都是字符流体系中的类,但是他们的前面却是字节流体系的名称,他们为什么这么特殊,原因就是这个两个类可以完成字节流和字符流的转换。

2.InputStreamReader读取转换流

(1)读取转换流解析

        此类是Reader体系下子类,他可以将字节转换成字符,是字节通向字符的桥梁。读取转换流必须要跟字节读取流关联在一起,所以在读取转换流的构造函数中必须要传入字节读取流。

        我们还可以在读取转换流的构造函数中指定编码表,因为字节转换成字符必须要依靠于编码表,不同的编码表中同一字节可能对应不同字符,当我们不指定编码表时使用系统默认的编码表。

(2)方法介绍

        1>读取方法,返回int,读到末尾返回-1,读取方法与Reader类中的方法没有太大区别。

                a>  read()

                读取单个字符,注意读取转换流中的读取方法是读取的字符,虽然他接收的是字节读取流,但是他按照编码表读相关的字节数,比如汉字是两个字节,他读取完这两个字节然后去查码表并把读取到的这个字符转为int值返回。

                b>  read(char[] cbuf,int offset, int length)

                此方法与将字符读取到字符数组中,从offse位开始存储,存储length个长度。

        2>getEncoding()
        返回String类型, 表示此流使用的字符编码的名称。

读取转换流的最大好处在于可以利用应对字符的方法而处理来自于字节流的数据,我们来演示一下:

class InStreamReaderDemo {

	public static void main(String[] args) {
		//字节输入流获取键盘输入
		InputStream is = System.in;
		//构建读取转换流与键盘录入相关联
		InputStreamReader isr = new InputStreamReader(is);
		//为了适应readLine方法将读取转换流与字符读取流缓冲区关联
		BufferedReader br = new BufferedReader(isr);
	
		String line = null;
		//可以多次输入
		while(true)
		{
			System.out.println("请输入数据:");
			try {
				line = br.readLine();
				//如果输入“out”退出程序
				if("out".equals(line))
					break;
				//转换成大写打印出来
				System.out.println(line.toUpperCase());
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

3.OutputStreamWriter写入转换流

(1)写入转换流的简介

        写入转换流是字符通向字节的桥梁,他可以接收一个字节输出流,然后利用把字符转换成字节的形式输出。此流中的方法与Writer类中的写入、刷新、关闭方法在使用上并无区别,在构建此类对象时可以指定使用什么样的码表。

        我们通过读取转换流来获取键盘录入,通过写入转换流来把数据打印到控制台中,同时指定他的编码表为ASCII码表。为了提高效率我们可以使用缓冲区来对转换流进行操作:

class InStreamReaderDemo {

	public static void main(String[] args){		
		//当我们指定编码表时必须要对产生的异常捕捉或抛出
		try {
			//默认输入为键盘录入,为了提高效率利用缓冲区
			BufferedReader br = new BufferedReader(
								new InputStreamReader(System.in,"ASCII"));
			
			//默认输出为控制台,利用缓冲区提高效率
			BufferedWriter bw = new BufferedWriter(
								new OutputStreamWriter(System.out,"ASCII"));
			
			String line = null;
			//可以多次输入
			while(true)
			{
				try {
					line = br.readLine();
					//如果输入“out”退出程序
					if("out".equals(line))
						break;
					
					//利用写入转换流把内容转成大写输出在控制台上
					bw.write(line.toUpperCase());
					//换行
					bw.newLine();
					//注意写入转换流是字符流的方法所以要刷新
					bw.flush();
					
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} 
		//此异常是为了捕捉编码表输入有误时报出的异常
		catch (UnsupportedEncodingException e1) {	
			e1.printStackTrace();
		}
	}
}

七.IO流的使用规律及两个实用程序


1.锁定IO流对象的三部曲

我们已经学习了IO流体系中常用的对象,很不幸的是学的太多,真正使用起来才发现无从下手,由此我们来总结一下IO流的使用规律。

      确定要使用哪个对象通过3步完成。

      第一步:明确进还是出

           如果要读取,就选择输入流:InputStream或Reader

           如果要写出,就选择输出流:OutputStream或Writer

      第二步:明确要操作的数据是否是纯文本数据

           如果是,使用字符流

           如果不是,使用字节流

      第三步:确定对象

           通过设备区分应该使用什么对象

           源设备有:内存、硬盘、键盘

           目的设备:内存、硬盘、控制台

      我们通过分析把键盘录入的数据存放到电脑文件中把这三步阐述一下:

        1>明确进还是出

        键盘录入是进,写入到电脑文件中是出。

        2>明确是否是纯本文

        键盘录入的数据是纯文本,但是键盘录入对象却是System.in是字节流,

        我们就想到了InputStreamReader读取转换流,将System.in转换成字符流 。

        InputStreamReader  isr =new InputStreamReader(System.in)

        3>确定对象

        源设备是键盘,而且我们也已经通过转换流把System.in转成了字符流;现在来看一看输出的目的,我们要把录入的数据写入到硬盘中,可以通过字符流关联硬盘上的文件:

        FileWriter  fw = new FileWriter(“键盘录入.txt”);

        然后还有一个问题就是我们现在已经确定好了该使用的流,那么我们要不要提高效率,如果需要,缓冲区就出现了:

        BufferedReader br = new BufferedReader(isr);

        BufferedWriter bw = new BufferedWriter(fw);

这就是确定使用具体哪个对象的三个步骤,通过这三步一般都可以分析出来我们的需求所要应用到的对象。

2.IO流中的两个实用例子

(1)我们在实际开发的时候是没有控制台的,当我们的程序出现了异常,我们又不能实时的处理这些异常,通常都会在硬盘中创建一个文件来记录下异常的发生,也就是来来建立一个异常日志。

        我们的程序在捕获到异常时,在catch语句通常都会写:e.printStackTrace()来把异常信息打印到控制台,这个printStackTrace()方法默认的输出是控制台,其实这个方法是调用了他的重载形式printStackTrace(PrintStream s),而PrintStream对象默认是System.out(控制台),我们就可以通过改变System.out的默认输出来在本地磁盘下建立一个文件用来存储异常。

下面我们简单演示一下一个异常日志的创建过程。

class ExceptionDemo
{
	public static void main(String[] args) 
	{
		//把可能出现错误的语句放在try中,如果出现了异常那么就把他写入到本地的文件中
		try
		{
			int[] arr = new int[1024];
			System.out.println(arr[1025]);
		}
		catch (Exception e)
		{
			PrintStream ps = null;
			try
			{
				//定义一个异常日志,用来存放发生的异常
				ps = new PrintStream("e://异常日志.txt");
					
				//为了在异常日志看到该异常发生的时间,我们使用Date类获取时间
				Date d = new Date();
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM日dd日E HH:mm:ss");
				String s = new String(sdf.format(d)+"\r\n");
				ps.write(s.getBytes());
				//把默认输出改为日志文件
				System.setOut(ps);
			}
			//如果日志创建错误那么就得让我们看到了
			catch (IOException i)
			{
				throw new RuntimeException("日志创建错误!");
			}finally
			{
				if(ps!=null)
					ps.close();
			}
			//默认输出已经改为本地e盘下的"异常日志.txt”文件了
			e.printStackTrace(System.out);
		}
	}
}
/*
 * 打开e盘下的"异常日志.txt”文件,里面记录了一个异常的发生:
 * 	2015年11日30日星期一 23:47:25
	java.lang.ArrayIndexOutOfBoundsException: 1025
	at edu.heima.zhuanhuan.ExceptionDemo.main(ExceptionDemo.java:21)
 * */

(2)获取系统的信息并存储在文件当中,在Map集合体系中有一个类叫HashTable,他有个子类叫做Properties,这个类可以获取到我们系统的属性,在系统的属性中有很多我们需要的东西,比如他的默认编码啊,操作系统啊等等,我们想把这个系统信息写入到一个文本文件中,这就涉及到了IO技术。

        Properties中都是以键值对来存放系统信息的,在该类中有一个方法:list(PrintStream out),同第一个例子一样我们只要把默认输出更改为我们系统下的一个文本文件就可把系统信息存入到这个文件中了。示例如下:

class  SystemDemo1
{
	public static void main(String[] args)
	{
		//获取系统属性信息
		Properties prop =  System.getProperties();
		//创建输出流与本地文件关联
		PrintStream ps = null;
		try {
			ps = new PrintStream("e://系统信息.txt");
			//调用list方法把系统信息写入到文本文件中
			prop.list(ps);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}finally
		{
			if(ps!=null)
				ps.close();
		}	
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值