黑马程序员——IO流3:字节流

本文详细介绍了Java IO流中的字节流与转换流的概念、特点、使用方法及应用场景,包括字节写入流OutputStream、文件字节写入流FileOutputStream、字节读取流InputStream、文件字节读取流FileInputStream、缓冲字节流BufferedInputStream和BufferedOutputStream、读取转换流InputStreamReader和写入转换流OutputStreamWriter、键盘录入实现及优化、复制文件与文本等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

IO流3:字节流

1 字节流概述

在前面的博客《IO流2:字符流》中,我们简单介绍了4个常用的字符读写流:FileReader、FileWriter、BufferedReader和BufferedWriter。通过代码演示和底层原理的简单说明,了解了字符读写流的特点及其应用方法。那么在这一篇博客中就要介绍IO流的第二大类——字节流。字节流其实与字符流一样都是通过读写字节数据,进而对文件进行文件的读写操作,不同的是,字节流内部没有定义编码表,所以我们对于字节流读取或写入的数据不能产生对字符那样的直观感受。

比如我们要操作图片文件等非文本文件时,就需要用到字节流,而在博客《IO1:概述》中我们曾经说过,字节流同样有两个抽象父类——负责读取数据的InputStream和负责写入数据的OutputStream。我们首先介绍OutputStream及其子类的特点及使用方法。

2 字节写入流OutputStream

2.1 抽象父类OutputStream

API文档描述:

       此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。

构造方法:

       由于是抽象类,因此其构造方法并没有定义具体的内容。

方法:

       从OutputStream类定义的5个方法与字符写入流是十分相似的。同样有关闭流资源的close方法、刷新流的flush方法,以及写入数据的write重载方法,而唯一的区别在于字节流的write方法操作的均是字节或者字节数组。

       由于字节流与字符流在方法定义上的相似性,我们就不再对OutputStream进行过多的文字介绍,我们直接通过一个具体子类来认识字节读取流的特点和应用方法。

2.2 文件字节写入流FileOutputStream

根据后缀名是抽象父类名,前缀名表示其功能的命名原则,我们找到OutputStream众多子类中用于操作文件的FileOutputStream。为演示方便,暂时不进行异常处理,

代码1:

import java.io.*;
 
class FileStreamDemo
{
	public static void main(String[] args) throws IOException
	{
		FileOutputStream fos =
			new FileOutputStream("DestFile.txt");
 
		//将一个字符串对象转换为字节数组,写入到指定文件中
		fos.write("abced".getBytes());
 
		fos.close();
	}
}

执行以上代码后,“DestFile.txt”文件中就写入了“abcde”五个字母。之所以还是显示为字符,是因为记事本文件自动通过默认的编码表,对写入的字节数据进行查表操作,并显示了查询结果。

代码说明:

(1)  与字符流一样,创建字节流对象、调用字节流的方法(包括写入方法、关流方法以及刷新)同样会抛出IOException异常及其子类异常,因此需要对其进行处理。但为演示翻遍这里暂时不进行异常处理。

(2)  字节写入流对象一旦创建,同样会在指定的路径下创建指定名称的文件,与字符流是一样的,不再赘述。

(3)  本例中因未涉及文件的读取,因此我们将一个字符串转换为字节数组写入到指定文件中。

(4)  注意到,虽然没有进行流的刷新操作,但是依然可以将指定字符串转换而来的字节数组写入到文件中(记事本可以自动将字节通过编码表转换为字符显示)。这是因为,字符流需要将字节值通过编码表转换为字符,而在GBK编码表中,往往两个字节对应一个中文字符(这根据不同的编码表有不同的对应方式),那么只读取一个字节是没有意义的,此时就需要将先读取的字节数据临时存储到一个数组(缓冲)中,查完表以后通过刷新缓冲,将数据写入到文件中。无论是一次读取一个字符,还是一次读取一个字符数组,都是相同的过程。相反,由于字节流不涉及查表,随读随写,不需要将字节数据临时存放到缓冲中,因此不需要刷新。当然,如果为了提高字节流的读取效率,使用自带缓冲的字节流时,同样需要刷新。因此,只有当流的操作涉及到缓冲时(无论出于编码需要,还是提高效率的需要),都需要刷新缓冲,将其中的数据写入到文件中。

(5)  写入操作完毕后,虽然不必刷新,但是底层流资源必须要关闭。

3 字节读取流InputStream

3.1 抽象父类InputStream

介绍完字节写入流的基本特点和用法,我们接着来说说读取流。

API文档介绍:

       此抽象类是表示字节输入流的所有类的超类。

构造方法:

       同样因为是抽象类,并没有定义具体的初始化内容。

方法:

       与字符读取流类似,同样定义有关闭流资源的close方法,以及进行读取操作的read重载方法,不再过多说明,不过需要注意的是,read方法的返回值类型并不是byte,而是int,原因我们将在后面的内容中涉及。但是,read重载方法中传入的数组类型是byte。

       除此以外,我们单独提一下下面这个方法,

pubic int available() throws IOException:返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。意思是,该方法可以返回指定文件中,还未读取的字节数,换句话说,如果还没有开始读取指定文件,那么该方法的返回值就是文件中包含的总字节数——文件大小。

3.2 文件字节读取流FileInputStream

(1) 文本文件读取演示

字节读取流的操作方式同样与字符流是类似的,首先通过以下例程演示基本读取操作,注意这一节中的代码为演示方便同样不进行异常处理,

代码2:

import java.io.*;
 
class FileStreamDemo2
{
	public static void main(String[] args) throws IOException
	{
	FileInputStream fis =
		new FileInputStream("DestFile.txt");//操作代码1中创建的文件
 
		//用于存储临时字节数据或者读取到的字节数的变量
		int by = 0;
 
		//一次读取一个字节
		while((by= fis.read()) != -1)
		{
			//为阅读方便将字节值强转为字符(其实已被提升为整型值)
			System.out.println((char)by);
		}
 
		/*
			//通过字节数组读取
			byte[] buf = new byte[1024];
			while((by = fis.read(buf)) != -1)
			{
				//将字节数组转换为字符串
				System.out.println(new String(buf,0,by));
			}
		*/
		fis.close();
	}
}
执行结果为:

a

b

c

e

d

如果通过字节数组读取,执行结果为:

abced

从以上代码可以看出字节流的读取方式,基本上与字符流是差不多的。当读取文本文件时,将读取的字节强转为字符,或者通过字符串封装起来,均可以再转换回原来的文本。

(2) available方法

下面我们再针对available方法单独进行演示,假设有一个名为Demo.txt的文本文件,内容如下:

abc

def

两行字母之间以换行分隔。

代码3:

import java.io.*;
 
class FileStreamDemo3
{
	public static void main(String[] args) throws IOException
	{
		FileInputStream fis =
			new FileInputStream("Demo.txt");
 
		//获取剩余可读字节数
		int size = fis.available();
		System.out.println("size= "+size);
	}
}
执行结果为:

size = 8

可能有朋友对这个结果有些疑问,认为结果应该是6,但换行符'\r\n'也占了两个字节,因此结果为8。既然available方法可以获取到指定文件的大小,那么我们是不是可以将临时用于存放字节的缓冲(数组)大小定义为文件大小呢?这样做就可以一次性刚好读完一个文件,不会浪费数组空间,也不需要不断循环反复读取。这种方式在文件较小的时候是可以的,但是如果被读取文件体积比较庞大,比如视频文件,那么如果一次性在内存中创建如此巨大的数组,可能会造成内存溢出。因此,我们建议还是要创建1024的整数倍大小的缓存,是比较好的选择。

3.3 字节读写流练习

需求:通过字节读取流和字节写入流复制一个图片文件。

思路:

(1)  创建一个字节读取流对象,并与一个图片文件进行关联。

(2)  创建一个字节写入流对象,并与目的文件进行关联。

(3)  通过字节读取流不断的循环读取,将每次读取到的字节数据临时存储到字节缓冲中;与此同时,通过字节写入流,将缓存中的字节数据写入到目的文件中。

(4)  读写完毕后,关闭底层流资源。

代码:

代码4:

import java.io.*;
 
class CopyPic
{
	publicstatic void main(String[] args)
	{
		FileInputStream fis = null;
		FileOutputStream fos = null;
 
		try
		{
			fis=
				new FileInputStream("source.png");
			fos=
				new FileOutputStream("dest.png");
 
			int len = 0;
			//定义字节缓存
			byte[] buf = new byte[1024];
			while((len= fis.read(buf)) != -1)
			{
				fos.write(buf,0,len);
			}
		}
		catch(FileNotFoundException e)
		{
			throw new RuntimeException("指定文件不存在");
		}
		catch(IOExceptione)
		{
			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("写入流关闭失败!");
			}
		}
	}
}
执行以上代码以后,就将原source.png图片文件中的数据,写入到dest.png文件中,实现了图片文件的复制。

 

小知识点1:

       如果我们使用字符流进行图片文件拷贝将会发生什么呢?实际上,复制操作是可以实现的,复制出来的图片将无法打开。这是因为,字符流读取图片文件时,将会对读取到的字节进行查表操作,如果能能够在编码表中查找到对应的字符,那么就会将原字节写入到目的文件中;相反,如果未能查找到对应字节,就会到编码表的未知字符区域进行查找,结果就是将原字节用未知字符区域中的相似字节替代,换句话说,原文件中的数据被修改了,最终复制出来的文件自然也就无法打开。


4 缓冲字节流

4.1 缓冲字节流方法操作演示

字节流同样定义了用于提高读写效率的缓冲字节流——BufferedInputStream和BufferedOutputStream。这两个字节流类同样是通过装饰设计模式,需要通过构造方法初始化真正调用底层流资源,并与文件关联的字节流对象,基于该字节流对象的read或write方法,把读取到的或将要写入文件的字节,临时存储到封装于缓冲字节流内部的数组(缓冲)中,以此提高文件读写效率。由于缓冲字节流的基本定义思路和使用方法与缓冲字符流基本相同,而且读取方法和写入方法与前述FileInputStream和FileOutputStream也是相同的,因此我们就不再进行详细的介绍,直接通过下面的例程来了解缓冲字节流的使用方法,有兴趣的朋友可以自行查阅API文档,

需求:通过缓冲字节流,复制一个mp3文件。

代码:

代码5:为演示方便暂时不进行异常处理

import java.io.*;
 
class CopyMp3
{
	public static void main(String[] args) throws IOException
	{
		/*
			分别创建缓冲字节读写流对象
			并初始化已于文件关联的InputStream子类对象和OutputStream子类对象
		*/
		BufferedInputStream bis =
			new BufferedInputStream(new FileInputStream("source.mp3"));
		BufferedOutputStream bos =
			new BufferedOutputStream(new FileOutputStream("dest.mp3"));
 
		int by = 0;
		//循环调用read方法,并将读取到的字节数据临时存放到内部缓冲中
		while((by= bis.read()) != -1)
		{
			//先将字节数据存入到缓冲
			bos.write(by);
			//刷新缓冲,同时将数据写入到目的文件
			bos.flush();
		}
 
		//关闭流资源
		bis.close();
		bos.close();
	}
}
通过执行以上代码,将指定mp3文件复制到了指定路径的指定文件中。

4.2 自定义缓冲字节读取流

为了能够更好的理解缓冲字节流的运作原理,在这一节内容中我们将自定义缓冲字节读取流,并利用自定义类完成mp3文件的复制。

(1) 原理说明

首先,我们需要知道的是,缓冲字节读取流(为叙述方便,我们将其简称为bis对象)的read方法所返回的字节数据并非是直接从文件中读取而来的,其基本过程是这样的:当我们调用bis对象的read方法时,首先通过从外部传入的InputStream子类对象(为叙述方便,我们将该对象简称为in对象,下同)的read方法,把从硬盘文件中读取到的字节数据存储到内部的字节数组中(所谓的字节缓存),这一过程就是将数据从硬盘复制到内存的过程。当字节数组被存满以后,bis对象的read方法才会从字节数组中取出一个字节数据并返回。

在以上读取过程中,由于需要将读取的字节数据临时存储到字节数组中,因此调用的是in对象的read(byte[] buf)方法,同时通过一个变量len记录该个数(该变量的主要作用是代表缓存中还未被获取的字节个数)。为了将字节缓存中的字节依次向外输出,就需要定义一个指针(用于表示缓存角标的整型变量pos),随着pos值的自增,将其对应位置的字节值通过bis对象的read方法返回给调用者,同时len自减,表示已经从缓存中获取了一个字节。当len值为0时,表示缓冲中的所有数据已获取完毕,此时就再次循环调用in对象的read方法,读取一批字节数据到缓存中,并将pos清零以便继续从缓存头部获取字节数据,不断重复以上过程。

上述过程将不断执行(如果外部调用者不断调用bis对象的read),直到读取到文件末尾,也就是说,最后一次调用bis对象的read方法,字节缓存将很有可能不能被存满,当然变量len的值也不再是缓存大小。那么将最后一部分字节数据输出以后,再次调用bis的read方法,in对象的read方法将会把-1存储到缓存中并将其返回,那么bis的read方法的返回值也将是-1,满足了循环判断条件,整个读取过程到此结束。

(2) 代码体现

代码6:

import java.io.*;
 
class MyBufferedInputStream
{
	private InputStream in;
	//字节缓存,大小为1024的整数倍
	private byte[] buf = new byte[1024];
	//分别定义用于记录缓存大小的len和代表指针的pos
	private int count = 0 ,pos = 0;
	//用于存储每次从缓存中获取的字节值
	private byte b;
	MyBufferedInputStream(InputStream in)
	{
		this.in = in;
	}
	public int read() throws IOException
	{
		//只有当len为0时,才从文件中读取字节存储到缓存中
		if(len == 0)
		{
			//通过in对象读取文件中的字节数据,并存储到字节缓存中
			len = in.read(buf);
			//若读到文件末尾,直接返回-1
			if(len< 0)
				return -1;
 
			//清空指针
			pos = 0;
		}
 
		b = buf[pos++];//获取指针对应的字节数据
		len--;
 
		return b;
	}
	public void myClose() throws IOException
	{
		in.close();
	}
}
class CopyMp3_2
{
	public static void main(String[] args) throws IOException
	{
		MyBufferedInputStream mbis =
			new MyBufferedInputStream(new FileInputStream("source.mp3"));
		BufferedOutputStream bos =
			new BufferedOutputStream(new FileOutputStream("dest.mp3"));
 
		int by = 0;
		while((by= mbis.read()) != -1)
		{
			bos.write(by);
			bos.flush();
		}
 
		mbis.myClose();
		bos.close();
	}
}
上述代码的执行过程将会非常迅速,但是目的文件的大小远小于源文件,说明mp3文件复制失败。

大家都知道文件中存储的数据在硬盘中都是以二进制形式存在,而一个字节是8个二进制位,如果读取到某个字节中的8个二进制全为1时,则对应的十进制数值就是-1(因为Java语言中的基本数据类型全为有符号值,最高位总是用于表示正负),那么返回这个-1时由于满足了while循环的条件致使其提前结束,这也就是造成文件复制失败的原因。

对于上述问题的解决办法是,在返回这个byte数据时,将其提升为int类型,使其从8个二进制位,扩展为32个二进制位,而由于前24位将用1补齐,所以这样提升后的int数据的十进制值还是-1,因此在返回这个int数据时“&”上255(255的32位二进制形式为,前24位是0,后八位为1),也就将前面的24个二进制数1全变为0,只保留了末尾的八位,那么返回的就是原来的字节数据,这一过程可用下图表示。那么这也就解释了为什么字节流read方法的返回值类型是int而不是byte的原因。

 

那么我们按照以上方法,将原代码6中的语句“return b”修改为“returnb & 255”,再次执行,就可以成功复制mp3文件了。

可能有朋友还有这样的疑问:虽然上述方法还原了原字节数据所代表的值,但是原有数据从一个字节大小变为四个字节,那么再写入到目的文件时,新文件大小岂不是源文件的4倍?实际上,BufferedOutputStream的write方法在将读取流返回的整型值写入到文件以前,会把该整型值强制转换为byte,换句话说,只向文件中写入了整型值的后八位(当然此时前24为均为0),以此保持新旧文件大小的一致性。

5 转换流

转换流共包含两个类——读取转换流InputStreamReader和写入转换流OutputStreamReader。从这两个类名,我们大致可以判断出它们的主要作用——实现字节与字符之间的转换。那么我们将在下面的内容中介绍这两个类的特点及使用方法。

5.1 读取转换流——InputStreamReader

在介绍读取转换流以前,我们首先来实现一个简单的实例——键盘录入,通过这一实例来引入读取转换流,进而介绍其特点和用法。

5.1.1 键盘录入实现原理

在我们以前的代码例程中,经常要对一些数据进行处理,这些数据的来源要么是在代码中指定,要么是通过读取流从硬盘文件中获取。这些方式相对都不够灵活,通常我们都希望在程序启动以后,用键盘输入一些数据,然后让程序帮我们进行计算,这一方式更符合现实中用户使用软件的情形,此时就需要实现键盘录入。

我们知道System类的静态字段out,对应的是标准输出设备,也就是控制台;而另一个静态字段in,对应的就是标准输入设备——键盘。查阅System类的API文档,对in字段的描述为:

public static finalInputStream in:“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者有主机环境或用户指定的另一个输入源。也就是说,in实际是封装在System内部的静态成员变量,而它指向的是一个字节读取流对象,该对象数据的来源就是键盘。既然in指向的是一个InputStream子类对象,那就说明可以调用其read方法来获取从键盘输入的数据了,阅读下面的代码,

代码7:

import java.io.*;
 
class ReadIn
{
	public static void main(String[] args) throws IOException
	{
		//获取一个键盘为数据源的字节读取流对象
		InputStream in = System.in;
 
		//读取一个字节数据
		int by = in.read();
 
		//将该数据打印到控制台
		System.out.println(by);
	}
}
当我们执行以上代码之后将会发现程序无法自行结束,它会一直等待键盘录入。这就表明该read方法是阻塞式方法,也就是说,read方法没有读到数据,就会一直处于等待状态。此时,如果我们键入“abc”,然后按下回车键(这里回车键的作用就是表示输入一行完毕,这与文本文件中的回车符是同样的作用),那么打印结果将是97(由于是字节读取流,因此read方法返回的是字符所对应的ASCII值),也就是说程序读取到了第一个字符“a”。显然,如果执行两次read方法,将会分别打印97和98。如果将以上代码修改为调用3次read方法,而只键入一个字符“a”,并按下回车键停止录入,打印结果将是97、13和10。出现这一结果的原因是,换行符由两个字符组成——“\r”和“\n”,而它们对应的ASCII值分别是13和10。

5.1.2 键盘录入一行文本

以上代码17虽然简单实现了将键盘输入的单个字符打印到控制台的功能,但是每次只能读取到输入文本的第一个字符,因此我们需要对代码17进行改进,实现一行一行读取键盘录入的文本,并打印到控制台。

需求:开启键盘录入,读取一行字符,就将这一行字符打印到控制台,直到输入“over”停止键盘录入程序。

思路:

(1)  通过System.in获取一个与键盘关联的InputStream对象。

(2)  开启一个无线循环(判断条件中直接定义true),不断调用read方法获取键盘录入的字符,并将该字符存储到一个临时缓存——StringBuilder中。

(3)  当用户按下回车键时,将缓存中的字符转换为字符串,判断字符串内容是否为“over”,如果是,则停止键盘录入;否则,将该字符串打印到控制台。

代码:

代码8:

import java.io.*;
 
class ReadIn2
{
	public static void main(String[] args) throws IOException
	{
		InputStream in = System.in;
		//定义临时缓存
		StringBuilder sb = new StringBuilder();
      
		//用于接收一行文本的字符串变量
		String str = null;
		//用于接收读取到的一个字节的变量
		int ch = 0
		while(true)
		{
			ch = in.read();
			//表示用户按下回车键,继续下一次循环
			if(ch == '\r')
				continue;
			else if(ch == '\n')
			{
				str = sb.toString();
				//清空缓存
				sb.delete(0,sb.length());
				//如果键入over,停止键盘录入
				if("over".equals(str))
					break;
				else
					//转为大写用于区分用户输入与打印结果
					System.out.println(str.toUpperCase());
			}
			else
				sb.append((char)ch);
		}
		in.close();
	}
}
代码说明:

(1)  可能有朋友想到,while循环的判断条件可以设置成以下形式,

while((ch = in.read()) != -1)

就像通过字节读取流读取文件时那样。但是,我们通过键盘是无法输入“-1”这个值的,因为即使分别按下“-”和“1”两个按键,他们还是会被识别为两个字符,因此循环将无法停止。

(2)  每次将存储在临时缓存中的字符转换为字符串后,都要清空缓存,否则缓存内的字符越积越多,每次都会打印以前输入的字符。

(3)  关流动作可做可不做。

5.1.3 读取转换流

其实,以上代码18中的读取原理,与代码14中自定义字符缓冲读取流的readLine方法是一样的。那么最为简便高效的方法就是直接使用BufferedReader的现成的方法——readLine来实现键盘录入一行文本,而不是自己去定义。但是,BufferedReader的构造方法中只能传递Reader子类对象,而System.in返回的是InputStream子类对象,因此字符缓冲流是不能直接对其进行“装饰”的。我们知道,无论是什么文件在硬盘中都是以字节的形式存在的,包括文本文件,因此面对上述问题,最直观的想法就是将字节流对象转换为字符流对象,并传递给字符缓冲流即可。那么Reader的一个子类——InputStreamReader便可以提供这一转换功能。

(1)  InputStreamReader——读取转换流概述

根据前缀表示功能,后缀表示父类的类名命名方式,InputStreamReader非常直观的向我们展示了它最大的特点——将字节转换为字符。那么既然是Reader子类,它所主要提供的还是对于字符的读取。

API文档描述:InputStream是字节流通向字符流的桥梁:它使用指定的charset(编码表,默认为GBK)读取字节并将其解码为字符。

构造方法:

public InputStreamReader(InputStream in):创建一个使用默认字符集的InputStreamReader。由于该类的主要作用就是将读取到的字节转换为字符,因此创建该类对象必须要为其初始化一个字节读取流对象。默认编码表为GBK。

方法:

该类的主要方法与Reader是相同的,这里不再赘述。

(2)  InputStreamReader应用

了解了InputStreamReader类的功能以后,我们就通过它来简化并优化代码18。

代码9:

import java.io.*;
 
class ReadIn3
{
	public static void main(String[] args) throws IOException
	{
		 //获取与键盘关联的字节流对象
		InputStream in = System.in;
 
		//将字节流转换为字符流
		InputStreamReader isr =
			new InputStreamReader(in);
 
		//通过字符缓冲读取流对象,将转换流封装起来,提高读取效率
		BufferedReader bufr =
			new BufferedReader(isr);
 
		String line = null;
		while((line= bufr.readLine()) != null)
		{
			//定义结束标记
			if("over".equals(line))
				break;
 
			System.out.println(line);
		}
		bufr.close();
	}
}
在以上代码中,通过将System.in返回的InputStream对象经InputStreamReader和BufferedReader两次封装以后,实现了键盘录入一行文本的功能。同样由于无法手动输入结束标记,因此规定当读取到的文本内容为“over”时,结束键盘录入。那么显然,代码19相对代码18更为简洁高效一些。

5.2 写入转换流——OutputStreamWriter

InputStreamReader的作用是将读取到的字节,通过内部的编码表转换为字符,那么相对应的OutputStreamWriter,就是将字符转换为字节,并写入到目的中(这里指的目的可以是硬盘中的文件,也可以是内存中的容器对象,后面的内容中将有涉及)。

5.2.1 写入转换流概述

API文档描述:

       OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。也就是说,通过该类的实例对象可以将传递到write方法的字符转换为字节,写入到目的中。

构造方法:

publicOutputStream(OutputStream out):创建使用默认字符编码的OutputStreamWriter。同样,在创建写入转换流的对象时要为其初始化一个字节写入流对象。

方法:

该类方法与Writer是基本相同的,这里不再赘述。

5.2.2 写入转换流应用

我们将在代码19的基础上加入OutputStreamWriter,来完成以下例程,

代码10:

import java.io.*;
 
class TransStreamDemo5
{
	public static void main(String[] args) throws IOException
	{
		//获取与键盘关联的字节流对象
		InputStream in = System.in;
 
		//将字节流转换为字符流
		InputStreamReader isr =
			new InputStreamReader(in);
 
		//通过字符缓冲读取流对象,将转换流封装起来,提高读取效率
		BufferedReader bufr =
			new BufferedReader(isr);
 
		//获取与标准输出流——控制台关联的字节写入流对象
		OutputStream out =
			System.out;
		OutputStreamWriter osw =
			new OutputStreamWriter(out);
		BufferedWriter bufw =
			new BufferedWriter(osw);
 
		String line = null;
		while((line= bufr.readLine()) != null)
		{
			//定义结束标记
			if("over".equals(line))
				break;
 
			//将读取到的一行字符,打印到控制台
			bufw.write(line);
			//实现分行打印
			bufw.newLine();
			//刷新,以实现实时显示录入的一行文本
			bufw.flush();
		}
		bufr.close();
		bufw.close();
	}
}
通过以上代码同样可以实现将键盘录入的一行文本打印到控制台中。

代码说明:

(1)  将System.out返回的标准输出流对象,经OutputStreamWriter和BufferedWriter封装起来,不仅可以提高读写效率,还能够利用newLine方法,在提高可移植性的同时,实现录入文本与打印文本的分行显示。

(2)  每打印一行文本,必须要进行刷新动作。由于BufferedWriter类涉及到内部缓存,因此必须要对该缓存进行刷新操作,才能真正将文本写入到目的中。

(3)  若要进一步简化以上代码,可以将创建两个流对象的多行代码合并为一行,如下所示,

代码11:

BufferedReader bufr =
	new BufferedReader(newInputStreamReader(System.in));
BufferedWriter bufw =
	new BufferedWriter(newOutputStreamWriter(System.out));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值