字节流部分和字符流部分的体系架构很相似,有四个基本流:InputStream、OutputStream、BufferedInputStream、BufferedOutputStream,其中,InputStream、OutputStream是字节流中的基类。
一、字节流演示:复制媒体文件
字符流中有FileWriter、FileReader用于对文本文件的字符读写操作,在字节流中,有FileInputStream、FileOutputStream用于对文件的字节读写操作。
(1)使用read()方法复制mp3文件。
【1】不使用缓冲流
public int read()
throws IOException
从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。返回:下一个数据字节;如果已到达文件末尾,则返回 -1。抛出:
该方法为堵塞式方法,即如果流中没有数据,则会等到流中有数据再读取。
1 private static void function1() throwsIOException {2 FileInputStream fis=new FileInputStream("music.mp3");3 FileOutputStream fos=new FileOutputStream("1.mp3");4 intch;5 while((ch=fis.read())!=-1)6 {7 fos.write(ch);8 }9 fis.close();10 fos.close();11 }
View Code
【2】使用缓冲流BufferedInputStream与BufferedOutputStream。
1 private static void function1() throwsIOException {2 FileInputStream fis=new FileInputStream("music.mp3");3 FileOutputStream fos=new FileOutputStream("1.mp3");4 BufferedInputStream bis=newBufferedInputStream(fis);5 BufferedOutputStream bos=newBufferedOutputStream(fos);6 intch;7 while((ch=bis.read())!=-1)8 {9 bos.write(ch);10 bos.flush();11 }12 bis.close();13 bos.close();14 }
View Code
使用缓冲流和不使用缓冲流程序执行效率差别很大(只针对读取单个字节的这种特殊情况),这是因为不使用缓冲流的时候是从文件直接读取单个字节,而使用缓冲流的时候是从内存读取单个字节,所以速度要快一些。
(2)使用read(byte[]buf)方法复制文本文件。
【1】不使用缓冲流的情况
public int read(byte[] b)
throws IOException
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。参数:b - 存储读取数据的缓冲区。返回:读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。抛出:
注意,该方法也是堵塞式方法。
1 private static void function2() throwsIOException {2 byte buf[]=new byte[1024];3 FileInputStream fis=new FileInputStream("music.mp3");4 FileOutputStream fos=new FileOutputStream("2.mp3");5 intlength;6 while((length=fis.read(buf))!=-1)7 {8 fos.write(buf,0,length);9 }10 fis.close();11 fos.close();12 }
View Code
【2】使用缓冲流的情况。
1 private static void function2() throwsIOException {2 byte buf[]=new byte[1024];3 FileInputStream fis=new FileInputStream("music.mp3");4 FileOutputStream fos=new FileOutputStream("2.mp3");5 BufferedInputStream bis=newBufferedInputStream(fis);6 BufferedOutputStream bos=newBufferedOutputStream(fos);7 intlength;8 while((length=bis.read(buf))!=-1)9 {10 bos.write(buf,0,length);11 bos.flush();12 }13 bis.close();14 bos.close();15 }
View Code
着这种情况下两者的程序执行效率相差不多,原因是两者都使用了数组。因此很多情况下不需要使用此缓冲流。
二、键盘录入
System.in为标准输入流(对应的设备为键盘),System.out为标准输出流(对应的设备为显示器)
System.in:
public static final InputStream in
“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
System.out:
public static final PrintStream out
“标准”输出流。此流已打开并准备接受输出数据。通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
对于简单独立的 Java 应用程序,编写一行输出数据的典型方式是:
System.out.println(data)
注意:默认的输入流和默认的输出流是不需要关闭的,因为一旦关闭,就用不了了,这和其他的流有很大的不相同之处。
1.从键盘输入单个字符。
(1)只输入一个字符的标准形式(非中文)
int ch=System.in.read();
System.out.println(ch);
使用System.in.read()方法由于读取的只有一个字节,所以不能表示一个中文字符(至少两个字节)。
(2)读入3个字符。
使用for循环控制:
1 private static void singleCharacterInputDemo02() throwsIOException {2 intch;3 System.out.println("请输入:");4 for(int i=1;i<=3;i++)5 {6 ch=System.in.read();7 System.out.println(ch);8 }9 System.out.println("程序结束!");10 }
View Code
运行结果:
很明显,程序运行的结果有问题,明明需要输入三个字符,但是却值输入了一个字符就结束了循环。原因就是系统默认在回车的时候添加上'\r'、'\n'两个字符。
验证:
private static voidCheckRN() {
System.out.println((int)'\r');
System.out.println((int)'\n');
}
可以发现输出的两个数字分别是13、10,这样就验证了我们的猜想。
2.需求:获取用户键盘录入的数据并将数据变成大写显示在控制台上,如果用户输入的数据是over,则结束键盘录入。
分析:输入的字符长度不确定,所以使用可变长的容器:StringBuilder或者StringBuffer
1 private static void function1() throwsIOException {2 //1.获取流对象。
3 InputStream is=System.in;4 //2.创建容器StringBuilder
5 StringBuilder sb=newStringBuilder();6 //3.获取键盘录入,转变并判断、显示
7 intch;8 while((ch=is.read())!=-1)9 {10 if(ch=='\r')11 continue;12 if(ch=='\n')13 {14 if(sb.toString().equals("over"))15 {16 break;17 }18 else
19 {20 System.out.println(sb.toString().toUpperCase());21 sb.delete(0, sb.length());22 continue;23 }24 }25 sb.append((char)ch);26 }27 }
View Code
观察代码可以发现这和之前写的MyBufferedReader的readLine方法的实现很相似,我们可以考虑是否能使用字符缓冲流将此流对象封装起来,使用readLine方法读取一行数据。
分析:BufferedReader只能接受字符流对象,但是System.in,即InputStream对象为字节流对象,所以不能直接操作。因此如果有一个转换流,将字节流转换为字符流的话,将会省去比较多的麻烦。
3.转换流简介。
之前提到了能将字节流转换为字符流的流,思考,如果该类存在,它应当在字符流的体系中还是字节流的体系中?
分析:字节流很简单,正因为出现了字符流,因此才需要转换,因此该流应当在Reader体系中。相应的,如果有能将字符流转换为字节流的类,该类应当在Writer体系中。
Java中提供了能将字节流转换为字符流的类InputStreamReader,与之相应的,java也提供了将字符流转换为字节流的类OutputStreamWriter。
(1)InputStreamReader:
public class InputStreamReaderextends Reader
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
可以看到,该类有个很大的特点,那就是能够按照指定方式的编码读取字节流中的数据。事实上InputStreamReader就是字节流+编码的体现,而其子类FileReader封装的也正是InputStream的字节流+编码,只不过编码方式是平台上的默认编码而已。
(2)OutputStream:
public class OutputStreamWriterextends Writer
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。
为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
(3)指定编码的方式:使用构造方法。
两种方式指定编码的方式都是在生成对象的时候就已经制定好了的。即不仅仅指定要接受的流对象,还要指定读取或者写入的编码方式。
4.使用转换流将字节流转换为字符流并反其道而行之将从键盘读到的数据写入到屏幕上显示。
(1)不使用缓冲流也不使用字符到字节的转换流。
1 private static void function01() throwsIOException {2 InputStream is=System.in;//该流为字节流
3 InputStreamReader isr=new InputStreamReader(is);//该流是字符流4 //int ch=isr.read();//读取一个字符5 //System.out.println(ch);
6 char buf[]=new char[1024];7 int length=isr.read(buf);8 System.out.println(new String(buf,0,length));9 System.out.println(length);10 }
View Code
运行结果:
虽然输入了10个字符,但是却显示字符长度为12,这是因为默认添加了'\r'与'\n'两个字符。很明显,需要手动判去除这两字个字符。
1 private static void function01() throwsIOException {2 InputStream is=System.in;//该流为字节流
3 InputStreamReader isr=new InputStreamReader(is);//该流是字符流4 //int ch=isr.read();//读取一个字符5 //System.out.println(ch);
6 char buf[]=new char[1024];7 int length=isr.read(buf);8 System.out.println(new String(buf,0,length-2));9 System.out.println(length-2);10 }
View Code
运行结果:
很明显,每次这么做的话就很麻烦了,所以可以考虑使用缓冲流提高效率。
(2)使用字符缓冲流提高输入效率。
1 private static void function02() throwsIOException {2 InputStream is=System.in;3 InputStreamReader isr=newInputStreamReader(is);4 BufferedReader br=newBufferedReader(isr);5 String str=br.readLine();6 System.out.println(str);7 System.out.println(str.length());8 }
View Code
运行结果和上面的第二个示例完全相同,除此之外,BufferedReader类还提供了其它方法如newLine方法简化了我们的书写。因此,如果需要提高效率的话,使用BufferedReader是很好地选择。
(3)使用字符转字节的转换流和缓冲流。
1 private static void function03() throwsIOException {2 InputStream is=System.in;3 InputStreamReader isr=newInputStreamReader(is);4 BufferedReader br=newBufferedReader(isr);5
6 OutputStream os=System.out;7 OutputStreamWriter osw=newOutputStreamWriter(os);8 BufferedWriter bw=newBufferedWriter(osw);9
10 String str=br.readLine();11 bw.write(str);12 bw.flush();//不能忘了刷新操作,否则无法显示。
13 }
View Code
注意,使用字符缓冲刘输出数据之后要刷新流,否则无法显示出内容(除非缓冲区已满)。
简写形式:
BufferedReader br=new BufferedReader(newInputStreamReader(System.in));
BufferedWriter bw=new BufferedWriter(newOutputStreamWriter(System.out));
5.转换流的强大功能。
(1)转换流和FileWriter与FileReader的关系。
FileReader的继承体系:
java.io.Reader
|--java.io.InputStreamReader
|--java.io.FileReader
FileWriter的继承体系:
java.io.Writer
|--java.io.OutputStreamWriter
|--java.io.FileWriter
由此可见,FileReader和FileWriter的父类就是转换流。但是我们知道,FileReader和FileWriter并没有转换流的功能,这是因为它的构造方法中明确了要处理的参数是文件,并且使用默认的编码集(系统平台上使用的编码),因此,我们可以想象的出来,当操作文件的时候,
BufferedReader br=new BufferedReader(new FileReader("date.txt"));
等价于
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("date.txt")));
即FileReader默认使用系统平台的编码将字节流转变成了字符流。
同样的,
BufferedWriter bw=new BufferedWriter(new FileWriter("date.txt"));
等价于
BufferedWriter bw1=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("date.txt")));
说明:等价指的是转换的动作相同,即使用默认系统平台的编码将字符流转变为字节流或者将字节流转换为字符流。很明显使用FileWriter和FileReader操作文件要比使用转换流操作文件方便的多,但是因为方便,所以就有了缺点,那就是使用FileWriter和FileReader不能够指定编码方式。因此,如果要以指定的编码方式读或者写就需要使用转换流。
(2)转换流的编码解码。
转换流通过构造方法确认使用的编码解码方式。
首先明确
编码:由已知到未知,由字符到字节,为OutputStreamWriter。
解码:由未知到已知,由字节到字符,为InputStreamReader
【1】InputStreamReader的构造方法:
常用的有两个:
public InputStreamReader(InputStream in)创建一个使用默认字符集的 InputStreamReader。
public InputStreamReader(InputStream in,String charsetName)throws UnsupportedEncodingException
创建使用指定字符集的 InputStreamReader。
可以看到,如果要指定编码,必须使用正确的编码名称,否则会抛出不支持的编码异常。
【2】OutputStreamWriter的构造方法。
创建使用指定字符集的 OutputStreamWriter。
【3】使用指定的编码方式将键盘上输入的文字保存入硬盘上的data.txt文件中。
注意!!!!如果想要验证某种方式下的读写,在Eclipse环境下必须设置文件的编码方式,设置默认的编码方式之后,java程序标准输入的编码方式(键盘等)将会变成这种编码方式,比如,一个中文字符将会表示成3个字节分别输入。
方式一:读入写出都是用utf-8编码(Eclipse环境下设置源文件的编码类型为utf-8)。
1 private static void encodeAndeDecodeDemo() throwsIOException {2 InputStreamReader isr=new InputStreamReader(System.in,"utf-8");3 System.out.println("kind of encode :"+isr.getEncoding());4 BufferedReader br=newBufferedReader(isr);5 OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("date2.txt"),"utf-8");6 System.out.println("kind of encode :"+osw.getEncoding());7 BufferedWriter bw=newBufferedWriter(osw);8 String str=null;9 while(!(str=br.readLine()).equals("over"))10 {11 System.out.println(str);12 bw.write(str);13 bw.newLine();14 bw.flush();15 }16 bw.close();17
18 isr=new InputStreamReader(new FileInputStream("date2.txt"),"utf-8");19 System.out.println("kind of encode :"+isr.getEncoding());20 br=newBufferedReader(isr);21 System.out.println(br.readLine());22 br.close();23 }
View Code
写入你好两个中文,结果:无乱码
方式二:读入写出都是使用gbk编码(Eclipse环境下设置文件的编码类型为gbk)。
1 private static void encodeAndeDecodeDemo() throwsIOException {2 InputStreamReader isr=new InputStreamReader(System.in,"gbk");3 System.out.println("kind of encode :"+isr.getEncoding());4 BufferedReader br=newBufferedReader(isr);5 OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("date2.txt"),"gbk");6 System.out.println("kind of encode :"+osw.getEncoding());7 BufferedWriter bw=newBufferedWriter(osw);8 String str=null;9 while(!(str=br.readLine()).equals("over"))10 {11 System.out.println(str);12 bw.write(str);13 bw.newLine();14 bw.flush();15 }16 bw.close();17
18 isr=new InputStreamReader(new FileInputStream("date2.txt"),"gbk");19 System.out.println("kind of encode :"+isr.getEncoding());20 br=newBufferedReader(isr);21 System.out.println(br.readLine());22 br.close();23 }
View Code
显示结果:无乱码
【4】使用指定的编码输入文件
例:使用utf-8编码写入文件,使用gbk编码读出(Eclipse环境下将源文件设置成utf-8编码方式)。
1 private static void encodeAndeDecodeDemo() throwsIOException {2 InputStreamReader isr=new InputStreamReader(System.in,"utf-8");3 System.out.println("kind of encode :"+isr.getEncoding());4 BufferedReader br=newBufferedReader(isr);5 OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("date2.txt"),"utf-8");6 System.out.println("kind of encode :"+osw.getEncoding());7 BufferedWriter bw=newBufferedWriter(osw);8 String str=null;9 while(!(str=br.readLine()).equals("over"))10 {11 System.out.println(str);12 bw.write(str);13 bw.newLine();14 bw.flush();15 }16 bw.close();17
18 isr=new InputStreamReader(new FileInputStream("date2.txt"),"gbk");19 System.out.println("kind of encode :"+isr.getEncoding());20 br=newBufferedReader(isr);21 System.out.println(br.readLine());22 br.close();23 }
View Code
结果:出现乱码。
例:使用gbk编码写入文件,使用utf-8编码读出(Eclipse环境下将源文件设置成gbk编码方式)
1 private static void encodeAndeDecodeDemo() throwsIOException {2 InputStreamReader isr=new InputStreamReader(System.in,"gbk");3 System.out.println("kind of encode :"+isr.getEncoding());4 BufferedReader br=newBufferedReader(isr);5 OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("date2.txt"),"gbk");6 System.out.println("kind of encode :"+osw.getEncoding());7 BufferedWriter bw=newBufferedWriter(osw);8 String str=null;9 while(!(str=br.readLine()).equals("over"))10 {11 System.out.println(str);12 bw.write(str);13 bw.newLine();14 bw.flush();15 }16 bw.close();17
18 isr=new InputStreamReader(new FileInputStream("date2.txt"),"utf-8");19 System.out.println("kind of encode :"+isr.getEncoding());20 br=newBufferedReader(isr);21 System.out.println(br.readLine());22 br.close();23 }
View Code
结果:出现乱码。