黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

时间:2023-02-25 18:54:57

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

IO流

一、概述

 1.IO流是用来处理设备之间的数据传输。

 2.Java对数据的操作时通过流的方式。

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

 4.流按操作数据分为两种:字节流和字符流。流按流向分为:输入流和输出流。

 5.流常用的基类:

  1)字节流的抽象基类:ImputStream和OutputStream。

  2)字符流的抽象基类:Reader和Writer。

 6.体系架构:

  字符流体系架构:

   Reader:用于读取字符流的抽象类。

    |--BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

      |--LineNumberReader:跟踪行号的缓冲字符输入流。

    |--InputStreamReader:转换流,是字节流通向字符流的桥梁。

      |--FileReader:用来读取字符文件的便捷类。

   Writer:写入字符流的抽象类。

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

    |--OutputStreamWriter:转换流,是字符流通向字节流的桥梁。

      |--FileWriter:用来写入字符文件的便捷类。

  字节流体系架构:

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

    |--FileInputStream:从文件系统中的某个文件中获得输入字节, 用于读取诸如图像数据之类的原始字节流。

    |--FilterInputStream:

      |--BufferedInputStream:字节输入流缓冲区。

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

    |--FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流,用于写入诸如图像数据之类的原始字节的流。

    |--FilterOutputStream:此类是过滤输出流的所有类的超类。

      |--BufferedOutputStream:该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

      |--PrintStream:为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。打印的所有字符都使用平台的默认字符编码转换为字节。PrintStream 永远不会抛出 IOException,而是,异常情况仅设置可通过 checkError 方法测试的内部标志。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

 问题思考:字节流和字符流有什么不同?

  答:字节流可以用于进行任何数据类型的操作,而字符流带编码表,只能用于进行纯文本数据类型的操作。


二、格式示例

 IO异常格式的处理:

import java.io.*;
class FileWriterDemo2
{
public static void main(String[] args)
{
//需要将fw引用定义在外部,因为在finally中需要调用close方法
FileWriter fw=null;
try
{
//创建对象的时候,需要定义在try里面,因为抛出了IO异常。
fw=new FileWriter("E:\\heima\\deme2.txt");

//write方法抛出了IO异常。
fw.write("hello world");
}
catch (IOException e)
{
System.out.println("cacht="+e.toString());
}
finally
{
//关闭流资源一定要执行,需定义在finally内部,且close方法抛出了
//IO异常,需要进行try处理。
try
{
//需要对fw是否为null进行判断,因为如果fw为null,则再调用close方法
//发发生NullPoterException异常。如果有多个流资源需要关闭,应进行多次
//判断并关闭,不要写在一个判断里。
if (fw!=null)
{
fw.close();
}
}
catch (IOException ex)
{
System.out.println(ex.toString());
}
}
}
}


三、字符流

 字符流常用对字符文件的操作。对于字符流的操作,应熟练掌握以下几个内容:

 1.在指定目录下创建一个纯文本文件,并在这个文件中写入指定内容:

  示例1:"E:\\heima"的目录下创建纯文本文件demo.txt,并向该文件demo.txt写入内容。

import java.io.*;
class FileWriterDemo
{
public static void main(String[] args) throws IOException
{

FileWriter fw=null;
try
{
//fileWriter对象一被初始化就必须明确被操作的文件的对象。
//而且该文件会被创建到指定目录下,如果该目录下已有同名文件存在,
//则将被覆盖。由于创建的目录可能不存在,因此抛出了IOException。
fw=new FileWriter("E:\\heima\\demo.txt");

//调用write方法,将字符串写入到流中。
fw.write("nihao");

//刷新流对象中的缓冲区的数据。将数据刷到目的地中。
fw.flush();

//可重复进行写入数据到文件中。
fw.write("hello");
fw.flush();
}
catch (IOException e)
{
System.out.println("cacht="+e.toString());
}
finally
{
try
{
if (fw!=null)
{
//关闭流对象,在关闭前会刷新流一次。关闭后不能再向流中写入数据,
//否则将发生IO异常。该动作一定做,因为流在调用window资源,需要进行关闭。
fw.close();
}
}
catch (IOException ex)
{
System.out.println(ex.toString());
}
}
}
}

  程序运行后在"E:\\heima"的目录下的创建了一个Demo.txt文件,文件的内容如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等


 2.对指定目录下的一个纯文本文件进行内容续写:

  示例2:对示例1中创建的文件demo.txt进行内容续写。

import java.io.*;
class FileWriterDemo3
{
public static void main(String[] args)
{
FileWriter fw=null;
try
{
//传递一个true穿参数,代表不覆盖已有文件,并在已有文件末尾处添加。
//如果没有已有文件,则会新创建一个文件。
fw=new FileWriter("E:\\heima\\demo2.txt",true);

//添加内容
fw.write("zhangsan");
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if (fw!=null)
{
fw.close();
}
}
catch (IOException ex)
{
System.out.println(ex.toString());
}
}
}
}

  程序运行后,在示例1中的demo.txt文件内容中续写了一些指定内容,截图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等


 3.写入内容的方法总结:

  1)写入单个字符。

   void write(int c):将int类型的字符c写入到指定目的对象中。

  2)写入字符数组或其中的某一部分。

   void write(char[] cbuf):将字符数组cbuf中的全部字符写入到指定对象中。

   void write(char[] cbuf,int off,int len):将字符数组cbuf中从索引off开始写入到指定对象,写入的字符总数为len。

  3)写入字符串或其中的某一部分。

   void write(String str):将整个字符串内容写入到指定对象中。

   void write(String str,int off,int len):将字符串str中从索引off开始写入到指定对象,写入的字符总数为len。

  注:上述所有写入的方法都会抛出IOException异常。 


 4.对指定目录下的一个纯文本文件进行内容读取:

  示例3:对示例2续写的文件demo.txt进行内容读取并打印到控制台。

import java.io.*;
class FileReaderDemo1
{
public static void main(String[] args)
{
FileReader fr=null;
try
{
//创建一个文件读取流对象,和指定的文件demo.txt相关联。
//要保证该文件是存在的,如果不存在则会发生FileNotFoundException异常。
fr=new FileReader("E:\\heima\\demo.txt");

int ch=0;

//read()方法读取文件的内容,一次读一个字符,而且会自动往下读。
//循环读取文件内容,如果读到文件末尾则会返回-1,可根据此条件
//来判断是否继续读取。
while ((ch=fr.read())!=-1)
{
System.out.print((char)ch); //将读取到int类型数据转换成字符类型输出。
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if (fr!=null)
{
fr.close();
}
}
catch (IOException ex)
{
System.out.println(ex.toString());
}
}
}

}
 

  程序运行后的结果如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 5.读取内容的方式总结:

  1)读取单个字符。

   int read():返回值为读取的int类型字符。如果读取的字符已到达流的末尾,则返回 -1。

  2)将字符读入数组或数组中的某一部分。

   int read(char[] cbuf):将字符读入数组。在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。

   int read(char[] cbuf,int offset,int length):将字符读入字符数组cbuf中,从索引offset开始存储,存入的字符个数限制为length。

   注:返回值为实际存储到字符数组的个数。如果读取的字符数已到达流的末尾,则返回 -1。

  3)将字符读入指定的字符缓冲区。了解charBuffer更多用法,参加AIP文档。

   int read(CharBuffer target):返回值为添加到缓冲区的字符数量,如果此字符源位于缓冲区末端,则返回 -1。

 6.字符流缓冲区:

  1)BufferedWriter:字符流写入缓冲区。

  写入数据的方法有:

   a)写入单个字符:

    void write(int c):该方法抛出了IO异常。

   b)写入字符数组:

    void write(char[] c):该方法抛出了IO异常。

   c)写入字符数组的某一部分:

    void write(char[] cbuf, int off, int len) :该方法抛出了IO异常。

   d)写入字符串:

    void write(String str):该方法抛出了IO异常。

   e)写入字符串的某一部分:

    write(String s, int off, int len):如果 len 参数的值为负数,则不写入任何字符。这与超类中此方法的规范正好相反,它要求抛出 IndexOutOfBoundsException。该方法抛出了IO异常。

   示例4:使用缓冲区技术,在"E:\\heima"的目录下创建纯文本文件test.txt,并向该文件test.txt写入内容。

<pre name="code" class="java">import java.io.*;
class BufferedWriterDemo
{
public static void main(String[] args)
{
//创建字符流读取缓冲区引用
BufferedWriter bufw=null;

try
{
//创建字符流读取缓冲区对象,并传入文件读取流对象
bufw=new BufferedWriter(new FileWriter("E:\\heima\\test.txt"));

//将字符串内容写入到字符流读取缓冲区中
bufw.write("nihao");

//刷新缓冲区中的数据,在写入内容不多的情况下,可以不用flush方法,但一定要close方法关闭
//资源,即将写入的内容最后关闭的时候一次性刷入文件中。如果写入内容很多,应加flush,防止
//在还没有刷新的情况下,出现停电,导致数据丢失。当加了flush后,可以即时将数据刷入文件中。
bufw.flush();

//换行
bufw.newLine();

bufw.write("nihao");


}
catch (IOException e)
{
throw new RuntimeException("文件写入失败");
}
finally
{
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException ex1)
{
throw new RuntimeException("写入关闭失败");
}
}

}
}

}

  程序运行后在"E:\\heima"的目录下生成了test.txt文件,文件内容如下截图所示: 
黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 2)BufferedReader:字符流读取缓冲区。

  读取数据的方法有:

   a)读取单个字符:

    int read():返回字符的int类型值。如果已到达流末尾,则返回 -1。

   b)将字符读入数组:

    int read(char[] cbuf):返回读入的字符数。如果已到达流的末尾,则返回 -1。在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。

   c)将字符读入数组的某一部分:

    int read(char[] c,int off,int len):返回读取的字符数。如果已到达流末尾,则返回 -1。

   d)读取一个文本行:

    int readLine():返回包含该行内容的字符串,不包含任何行终止符。如果已到达流末尾,则返回 null。

   e)试图将字符读入指定的字符缓冲区:

    int read(charBuffer target):返回添加到缓冲区的字符数量,如果此字符源位于缓冲区末端,则返回 -1。

   注:上述方法都抛出了IO异常。

  示例5:使用缓冲区技术,对示例4创建的test.txt文件进行内容读取并打印输出到控制台。

import java.io.*;
class BufferedReaderDemo
{
public static void main(String[] args)
{
//创建字符流读取缓冲区引用
BufferedReader bufr=null;

try
{
//创建字符流读取缓冲区对象,并传入文件读取流对象
bufr=new BufferedReader(new FileReader("E:\\heima\\test.txt"));

String line=null;

//调用字符流读取缓冲区对象的readLine方法,如果判断不为空,则继续读下一行
while ((line=bufr.readLine())!=null)
{
System.out.println(line);
}
}
catch (IOException e)
{
throw new RuntimeException("文件读取失败");
}
finally
{
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}

}
}

}
  程序运行后的结果如下截图所示:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 7.装饰设计模式:

  1)概述

   当想要对已有的对象进行功能增强时,可以定义类,将已有的功能,并提供加强功能。那么自定义的类就称为装饰类。

   装饰类通常会通过构造方法接受被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。

   示例6:对已存在的Person类进行功能增强,并用SuperPerson类描述。

<pre name="code" class="java">class PersonDemo 
{
public static void main(String[] args)
{
SuperPerson sp=new SuperPerson(new Person());
sp.superEat();
}
}
class Person
{
public void eat()
{
System.out.println("吃饭");
}
}

//装饰类
class SuperPerson
{
private Person p;

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

//对eat功能进行增强
public void superEat()
{
System.out.println("开胃酒");
p.eat();
System.out.println("甜点");

}
}

  程序运行后的结果如下图所示: 

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等  

  2)装饰与继承的区别:

   继承结构:    

    MyReader     

     |--MyTextReader      

       |--MyBuffferedTextReader:继承增强类。     

     |--MyMediaReader      

       |--MyBufferedMediaReader:继承增强类。     

     |--MyDataReader      

       |--MyBufferedDataReader:继承增强类。

   装饰结构:    

    MyReader     

     |--MyTextReader     

     |--MyMediaReader     

     |--MyDataReader     

     |--MyBufferedReader:装饰类,用于给MyReader子类的功能进行增强。

   区别:装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。装饰类因为增强已有对象,具备的功能和已有功能是相同的,只不过提供了更强的功能。所以装饰类和被装饰类通常属于一个体系中的。但装饰类只能在被装饰类的父类的基础上进行增强,而继承可以在被继承类的基础上直接增强。

  3)自定义装饰类:

   示例7:模拟BufferedReader类。

//自定义装饰类
class MyBufferedReader extends Reader
{
private FileReader r;

MyBufferedReader(FileReader r)
{
this.r=r;
}

//定义读取行功能
public String myReadLine() throws IOException
{
StringBuilder sb=new StringBuilder();

int ch=0;

while ((ch=r.read())!=-1)
{

if (ch=='\r')
continue;
else if (ch=='\n')
return sb.toString();
else
sb.append((char)ch);

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

return null;

}

//直接用传进来的子类进行覆盖,因为该子类一定实现了父类的方法
public int read(char buf,int off,int len)
{
r.read(buf,off,len);
}

//覆盖父类的close抽象方法
public void close()
{
r.close();
}

}


 8.LinenumberReader类:

  1)概述

   LineNumberReader是BufferedReader的子类,用于跟踪行号的缓冲字符输入流。该类的setLineNumber和getLineNumber方法可以用于设置和获取行号。

   示例8:对示例4创建的test.txt文件进行内容读取并打印输出到控制台,且要求打印的内容带有行号,并要求从100行开始输出。

import java.io.*;
class LineNumberReaderDemo
{
public static void main(String[] args)
{
LineNumberReader lnr=null;

try
{

lnr=new LineNumberReader(new FileReader("E:\\heima\\test.txt"));

String line=null;

//设置行号为100
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 ex1)
{
throw new RuntimeException("读取关闭失败");
}
}

}
}

}
  程序运行后的结果如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等
  2)自定义LineNumberReader类:

/*
自定义LineNumberReader类:

思路:
1.定义MyLineNumberReader类,并继承BufferedReader。
2.复写readLine方法,使其没读取一次该方法,行号计数加1。
3.对外提供设置行号和获取的方法。
*/

import java.io.*;

//继承BufferedReader类,可以使类更加简单,只需定义特有方法即可。
class MyLineNumberReader extends BufferedReader
{
//定义行号属性
private int lineNumber;

MyLineNumberReader(Reader r)
{
super(r);
}

public String readLine() throws IOException
{
//每调用一次行号自增1
lineNumber++;
return super.readLine();
}

//设置行号
public void setLineNumber(int lineNumber)
{
this.lineNumber=lineNumber;
}

//获取行号
public int getLineNumber()
{
return lineNumber;
}

}

 9.代码练习:

  练习1:完成对示例4创建的test.txt文件的复制。

/*

需求:完成<span style="font-size:14px;">对示例4创建的test.txt文件</span>的复制。

思路:
1.在指定目录下创建一个文件,用来存储复制文本内容。
2.创建一个文件读取流对象,与要复制文本文件相关联。
3.将要复制的文本文件的内容读取到数组中。
4.将数组中的内容写入到新创建的文件中。
*/
import java.io.*;
class FileCopyDemo1
{
public static void main(String[] args)
{
FileWriter fw=null;
FileReader fr=null;

try
{
fw=new FileWriter("E:\\heima\\testCopy.txt");
fr=new FileReader("E:\\heima\\test.txt");

//定义字符数组用来存储
char[] buf=new char[1024];

int num=0;


while ((num=fr.read(buf))!=-1)
{
//将数组中的内容写入到新创建的文本文件中,不需要刷新。
fw.write(buf,0,num);
}
}
catch (IOException e)
{
throw new RuntimeException("文件读取失败");
}
finally
{
if (fr!=null)
{
try
{
fr.close();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}
if (fw!=null)
{
try
{
fw.close();
}
catch (IOException ex2)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}

}
  程序运行后在"E:\\heima"的目录下创建了一个testCopy.txt文件,该文件的内容的截图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

  练习2:使用字符流缓冲区技术完成练习1。

/*
思路:
1.在指定目录下创建一个文件,用来存储复制文本内容。定义一个字符写入流缓冲区,并与新创建的文本文件相关联。
2.创建一个文件读取流对象,与要复制文本文件相关联,定义一个字符读取流缓冲区,并与文件读取流对象相关联。
3.用readLine方法循环取出字符读取缓冲区的内容。
4.将readLine取出的内容写入到字符写入流缓冲区中。
5.用newLine方法换行,并刷新到新创建的文件中。
*/
import java.io.*;
class FileCopyDemo2
{
public static void main(String[] args)
{
BufferedWriter bufw=null;
BufferedReader bufr=null;

try
{
bufw=new BufferedWriter(new FileWriter("E:\\heima\\test_Copy.txt"));
bufr=new BufferedReader(new FileReader("E:\\heima\\test.txt"));

String line=null;


while ((line=bufr.readLine())!=null)
{

bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("文件读取失败");
}
finally
{
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException ex2)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}

}
  程序运行后在"E:\\heima"的目录下创建了test_Copy.txt文件,该文件的内容截图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等


四、字节流

 字节流常用于对非字符文件的操作,如图象、视频等,但不表示字节流不能操作字符文件,而是用字符流来操作字符文件更为便捷。字节流应掌握的内容基本同字符流一致。

 1.写入内容的方法总结:

  1)将指定字节写入此文件输出流。

   void write(int b):该方法抛出了IO异常。

  2)将 b.length 个字节从指定 byte 数组写入此文件输出流中。

   void write(byte[] b):该方法抛出了IO异常。

  3)将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。

   void write(byte[] b,int off,int len):该方法抛出了IO异常。

  注:上述所有方法都是FileOutputStream类特有方法,且写入文件时,不需要进行刷新操作。


 2.读取内容的方法总结:

  1)从此输入流中读取一个数据字节。

   int read():返回下一个数据字节。如果已到达文件末尾,则返回 -1。

  2)从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。

   int read(byte[] b):返回读入缓冲区的字节总数。如果因为已经到达文件末尾而没有更多的数据,则返回 -1。在某些输入可用之前,此方法将阻塞。

  3)从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。

   int read(byte[] b,int off,int len):读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。如果 len 不为 0,则在输入可用之前,该方法将阻塞;否则,不读取任何字节并返回 0。


 3.复制图片(复制视频等原理类似):

  示例9:复制指定目录的文件。

/*

思路:
1.用字节读取流对象与图片关联。
2.用字节写入流对象创建一个新的图片文件,用来进行存储获取到的文件数据。
3.通过循环读写完成数据的存储。
4.关闭资源。

注:字节流写入数据时不需要刷新,但仍需关闭资源。
*/
import java.io.*;
class CopyPicDemo
{
public static void main(String[] args)
{
FileInputStream fis=null;
FileOutputStream fos=null;

try
{

fos=new FileOutputStream("E:\\heima\\2.png"); //创建字节写入流对象,生成复制图像文件。
fis=new FileInputStream("E:\\heima\\1.png"); //创建字节读取流对象,并与要被复制的图像文件关联。

byte[] by=new byte[1024];

int len=0;
while ((len=fis.read(by))!=-1)
{

fos.write(by,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败");
}
finally
{
if (fis!=null)
{
try
{
fis.close();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}
if (fos!=null)
{
try
{
fos.close();
}
catch (IOException ex1)
{
throw new RuntimeException("写入关闭失败");
}
}

}
}
}
  程序运行后,复制图像文件与被复制图形文件对比图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 4.字节流缓冲区

  字节流缓冲区有两个:BufferedInputStream(字节流读取缓冲区)和BufferedOutputStream(字节流写入缓冲区)。

  从输入流读取数据的方法总结:

   1)从输入流中读取数据的下一个字节: 

    int read():返回下一个数据字节。如果到达流末尾,则返回 -1。

   2)从此输入流中将 byte.length 个字节的数据读入一个 byte 数组中:

    int read(byte[] b):返回读入缓冲区的字节总数,如果因为已经到达流末尾而没有更多的数据,则返回 -1。在某些输入可用之前,此方法将阻塞。

   3)从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中:

    int read(byte[] b, int off, int len):返回读取的字节数。如果已到达流末尾,则返回 -1。

   注:上述方法都抛出了IO异常。


  将数据写入缓冲的输出流的方法:

   1)将指定的字节写入此缓冲的输出流:

    void write(int b):该方法抛出了IO异常。

   2)将 b.length 个字节写入此输出流:

    void write(byte[] b):该方法抛出了IO异常。

   3)将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流:

    void write(byte[] b,int off,int len):该方法抛出了IO异常。

  示例10:使用缓冲区技术完成对示例9的复制文件操作。

import java.io.*;
class BufferedStreamDemo
{
public static void main(String[] args)
{
BufferedInputStream bufis=null;
BufferedOutputStream bufos=null;

try
{
//创建字节流写入缓冲区对象
bufos=new BufferedOutputStream(new FileOutputStream("E:\\heima\\2.png"));

//创建字节流读取缓冲区对象
bufis=new BufferedInputStream(new FileInputStream("E:\\heima\\1.png"));

int num=0;
while ((num=bufis.read())!=-1)
{
bufos.write(num);
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if (bufis!=null)
{
try
{
bufis.close();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}
if (bufos!=null)
{
try
{
bufos.close();
}
catch (IOException ex1)
{
throw new RuntimeException("写入关闭失败");
}
}

}
}
}
   程序运行后,复制图像文件与被复制图形文件对比图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 5.读取键盘录入:

  示例11:通过键盘录入,当录入一行的数据后,就将该行数据转换成大写并进行打印。如果录入的数据是over,那么停止录入。

/*

System.out:对应的标准输出设备,控制台。
System.in:对应的标准输入设备,键盘。
*/
import java.io.*;
class ReadInDemo
{
public static void main(String[] args)
{
InputStream in=System.in;
StringBuilder sb=new StringBuilder();
try
{
while (true)
{
int ch=in.read();
if (ch=='\r')
{
continue;
}
else if (ch=='\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);
}

}
catch (IOException ex)
{
throw new RuntimeException("发生异常");
}
finally
{
try
{
in.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
  程序运行后的结果如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等
 

 6.转换流

  转换流包括两个:InputStreamReader(字节流通向字符流)和OutpuStreamWriter(字符流通向字节流)。

  示例12:通过转换流完成对示例11的操作。

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;
}
bufw.write(line.toUpperCase()); //将读取内容转换成大写,并写入字符流写入缓冲区
bufw.newLine(); //换行
bufw.flush(); //刷新字符流写入缓冲区的数据到控制台
}
}
catch (IOException ex)
{
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("写入关闭失败");
}
}
}
}
}
  程序运行后的结果如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

五、自定义缓冲区

 1.自定义字符流缓冲区:

  字符流缓冲区提供了一个一次读一行的方法readline,方便于对文本数据的读取。当返回null时,表示读到文件末尾。

  readLine方法的原理:无论是读一行,获取读取的多个字符。其实最终都是在硬盘上一个一个读取,所以最终使用的还是read方法一次读一个的方法。

  注:readLine方法返回的时候,只返回回车符之前的数据内容,并不返回回车符。

  示例13:自定义字符流读取缓冲区。

<pre name="code" class="java">import java.io.*;
class MyBufferedReaderDemo
{
public static void main(String[] args)
{
MyBufferedReader bufr=null;

try
{
//创建自定义字符流读取缓冲区对象
bufr=new MyBufferedReader(new FileReader("E:\\heima\\BufferDemo.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 ex1)
{
throw new RuntimeException("读取关闭失败");
}
}

}
}

}

//自定义字符读取流缓冲区
class MyBufferedReader
{
private FileReader r;

MyBufferedReader(FileReader r)
{
this.r=r;
}

//定义读取行功能
public String myReadLine() throws IOException
{
StringBuilder sb=new StringBuilder();

int ch=0;

while ((ch=r.read())!=-1)
{

if (ch=='\r')
continue;
else if (ch=='\n')
return sb.toString();
else
sb.append((char)ch);

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

return null;

}

//定义关闭字符读取流功能
public void myClose() throws IOException
{
r.close();
}
}

  文件BufferedDemo.txt内容截图如下: 

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

  

   程序运行后的结果如下图:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等

 2.自定义字节流缓冲区:

  示例14:自定义字节流读取缓冲区。

import java.io.*;
class MyBufferedInputStreamDemo
{
public static void main(String[] args)
{
MyBufferedInputStream mybufis=null;
BufferedOutputStream bufos=null;

try
{
//创建自定义字节流写入缓冲区对象
bufos=new BufferedOutputStream(new FileOutputStream("E:\\heima\\2.mp3"));

//创建字节流读取缓冲区对象
mybufis=new MyBufferedInputStream(new FileInputStream("E:\\heima\\1.mp3"));

//byte[] by=new byte[1024];

int num=0;
while ((num=mybufis.myRead())!=-1)
{
//num是int类型,那么写入文件的字节数是不是4个字节呢?不是的,因为write方法
//里对int类型做了强制类型转换,只保留了int类型的最后八位。所以还是只写入了
//一个字节的数据。
bufos.write(num);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败");
}
finally
{
if (mybufis!=null)
{
try
{
mybufis.myClose();
}
catch (IOException ex1)
{
throw new RuntimeException("读取关闭失败");
}
}
if (bufos!=null)
{
try
{
bufos.close();
}
catch (IOException ex1)
{
throw new RuntimeException("写入关闭失败");
}
}

}
}
}

//自定义字节流读取缓冲区
class MyBufferedInputStream
{
private InputStream is;
private byte[] by=new byte[1024*4];
private int pos=0,count=0;

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

//一次读一个字节,从缓冲区(字节数组)获取
public int myRead() throws IOException
{

if (count==0)
{
//通过is对象获取硬盘上的数据,并存储在by数组中
count=is.read(by);
pos=0;
}
if (count>0)
{
int b=by[pos];
pos++;
count--;

//为什么要与上255?因为字节流的字节数据都是二进制,如果读取的一个字节数据为1111-1111,即十进制为-1,
//那么提升为int类型时,还是-1,此时这个字节数据的-1就会和循环读取的判断条件-1一致,导致循环停止,复
//制文件失败,所以需与上255,从而保证int类型的最后一个八位不变,前面三个八位都为0,此时就不会出现返回
//-1的现象。这也是返回类型不是byte而是int的原因所在。
return b&255;
}
return -1;
}

public void myClose() throws IOException
{
is.close();
}
}
  程序运行后的截图如下:

黑马程序员——Java IO流(一)之IO流概述、字符流、字节流等