黑马程序员——java基础——IO流(1)字符流与字节流基本操作

时间:2023-02-16 20:12:11
------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------



一、IO

IO流:即InputOutput的缩写。用于处理设备上数据。

流:可以理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。

1、特点:

1)Java对数据的操作是通过流的方式。

2)流按操作数据分为两种:字节流和字符流。

3)流按流向分为:输入流和输出流。

注意:流只能操作数据,而不能操作文件。

3、IO流的常用基类:

        1)字节流的抽象基流:InputStream和OutputStream

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

注:此四个类派生出来的子类名称都是以父类名作为子类名的后缀,以前缀为其功能;如InputStream子类FileInputStream;Reader子类FileReader。流的操作只有两种:读和写。

 

二、字符流

 

1、 字符流概述

1)字符流中的对象融合了编码表。使用的是默认的编码,即当前系统的编码。

2)字符流只用于处理文字数据,而字节流可以处理媒体数据。

3)既然IO流是用于操作数据的,那么数据的最常见体现形式是文件。查看API,找到一个专门用于操作文件的Writer子类对象:FileWriter。   后缀是父类名。前缀名是流对象的功能。该流对象一被初始化,就必须有被操作的文件存在。

 

2、字符流的读写操作

1)写入字符流

a)创建一个FileWriter对象,该对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件将被覆盖。其实该步就是在明确数据要存放的目的地。

b)调用write(String s)方法,将字符串写入到流中。

c)调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。

d)调用close()方法,关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。

代码示例:

/*
1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。
2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。
3:如果指定位置,出现了同名文件,文件会被覆盖。
*/
public static void main(String[] args) throws IOException { //读、写都会发生IO异常

FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException

//调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中。
fw.write("abcde");
fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。
}

close()flush()的区别:

flush():将缓冲区的数据刷到目的地中后,流可以使用。

close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。

 

FileWriter写入数据的细节:

a)window中的换行符:\r\n两个符号组成。 linux\n

b)续写数据,只要在构造函数中传入新的参数true

c)目录分割符:window \\  /

b)io异常的处理方式:io一定要写finally,并在finally中添加关闭资源操作 

      

2)读取字符流

a)FileReader使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。

b)创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生异常FileNotFoundException

c)调用读取流对象的read()方法。read():一次读一个字符,且会继续往下读。

第一种方式:读取单个字符。第二种方式:通过定义缓冲区的方式进行读取。

d)读取后要调用close方法将流资源关闭。

第一种读取方式代码示例

import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
//创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch = fr.read())!= -1) { //条件是没有读到结尾
System.out.println((char)ch); //调用读取流的read方法,读取一个字符。
}
fr.close();
}
}

读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。

import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。
//因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}


 

3、字符流缓冲技术

字符流的缓冲技术由两个类来实现:BufferedReaderBufferedWriter分别对应读取缓冲和写入缓冲字符流:

1)缓冲区的出现:提高了流的读写效率,所以在缓冲区创建前,要先创建流对象。即先将流对象初始化到构造函数中。 

2)缓冲技术原理:此对象中封装了数组,将数据存入,再一次性取出。

3)写入流缓冲区BufferedWriter  

      a)从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

创建一个字符写入流对象。

b)为了提高字符写入流效率,加入缓冲技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。

         如: BufferedWriter bufw =new BufferedWriter(fw);

注:BufferedWriter缓冲区中提供了一个跨平台的换行符:newLine();可以在不同操作系统上调用,用作数据换行。

 4)读取流缓冲区BufferedReader

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

b)该缓冲区提供了一个一次读一行的方法readLine,方便于堆文本数据的获取,当返回null时表示读到文件末尾。readLine方法返回的时候,只返回回车符之前的数据内容。并不返回回车符。

c)同写入缓冲区一样,在创建读取缓冲区时,也要给缓冲区指定一个要提高效率的输入流 ,如:BufferedReader bufr=new BufferedReader(fr);

缓冲技术代码示例:

/*
需求:使用缓冲技术copy一个文本文件
*/
import java.io.*;

class BufferedCopyDemo {
public static voidmain(String[] args) {
BufferedWriterbfw=null;
BufferedReaderbfr=null;
try{
//创建写缓冲对象
bfw=newBufferedWriter(new FileWriter("ReaderWriterTest_copy.txt"));
//创建读缓冲对象
bfr=newBufferedReader(new FileReader("ReaderWriterTest.java"));
//利用BufferedReader提供的readLine方法获取整行的有效字符。直到全部获取
for (Stringline=null; (line=bfr.readLine())!=null; ){
bfw.write(line);//写入指定文件中
bfw.newLine();//换行
bfw.flush();//将缓冲区数据刷到指定文件中
}
}catch (IOException e) {
throw newRuntimeException("文件copy失败");
}
finally{
if(bfw!=null)
try{
bfw.close();//关闭写入流
}catch(IOException e) {
thrownew RuntimeException("写入流关闭失败");
}
if(bfr!=null)
try{
bfr.close();//关闭读取流
}catch(IOException e) {
thrownew RuntimeException("读取流关闭失败");
}
}
}
}


4、装饰设计模式

1)什么是装饰设计模式

IO中的使用到了一个设计模式:装饰设计模式。当想对已有对象进行功能增强时,可定义类:将已有对象传入,基于已有对象的功能,并提供加强功能,那么自定义的该类称之为装饰类。这种设计方法成为装饰设计模式

2)装饰类的特点

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

注:在定义类的时候,不要以继承为主;可通过装饰设计模式进行增强类功能。灵活性较强,当装饰类中的功能不适合,可再使用被装饰类的功能。

代码示例:

import java.io.*;
//自定义一个对BufferedReader进行功能扩展的装饰类
import java.io.*;
class MyBufferedReader extends Reader{
//若继承Reader抽象类就要复写其中的抽象方法(read和close方法)。
private Reader r;
MyBufferedReader(Reader r){
this.r = r;
}
//自定义一个自己的readLine()
public StringmyReadLine()throws IOException{
//定义一个临时容器。原BufferReader封装的是字符数组。
//为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
StringBuilder sb = newStringBuilder();
int ch = 0;
while((ch=r.read())!=-1){
if(ch=='\r')//如果遇到换行符,则继续
continue;
if(ch=='\n')//如果遇到回车符,表示该行读取完毕
returnsb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)//如果读取结束,容器中还有字符,则返回元素
returnsb.toString();
return null;
}
//覆盖Reader类中的抽象方法。
public int read(char[] cbuf,int off, int len) throws IOException{
returnr.read(cbuf,off,len) ;
}
public void close()throwsIOException{
r.close();
}
//自定义增强功能方法
public void myClose()throwsIOException{
r.close();
}
}
//测试效果
class MyBufferedReaderDemo{
public static voidmain(String[] args) throws IOException{
FileReader fr = new FileReader("buf.txt");
MyBufferedReader myBuf= new MyBufferedReader(fr);
String line = null;
while((line=myBuf.myReadLine())!=null){
System.out.println(line);
}
myBuf.myClose();
}
}

三、字节流

1、字节流概述:

1)字节流和字符流的基本操作是相同的,但字节流还可以操作其他媒体文件。

2)由于媒体文件数据中都是以字节存储的,所以,字节流对象可直接对媒体文件的数据写入到文件中,而可以不用再进行刷流动作。

3)读写字节流:InputStream   输入流(读)

                            OutputStream  输出流(写)

4InputStream特有方法:

int available();//返回文件中的字节个数

注:可以利用此方法来指定读取方式中传入数组的长度,从而省去循环判断。但是如果文件较大,而虚拟机启动分配的默认内存一般为64M。当文件过大时,此数组长度所占内存空间就会溢出。所以,此方法慎用,当文件不大时,可以使用。

 

2、字节流的读写操作

       字节流的读写操作与字符流读写原理基本一样,知识字符流操作的是字符类型的内容,而字节流操作的是字节。

而且字节流同样具有缓冲技术BufferedInputSreamBufferedOutputStream 

字节流复制图片代码示例

import java.io.*;
class CopyPic2 {
public static voidmain(String[] args)throws IOException {
FileInputStream fis =new FileInputStream("1.jpg");//创建一个字节输入流关联源文件
FileOutputStream fos =new FileOutputStream("2.jpg");//创建一个自己输出流关联目标文件

BufferedInputStreambis=new BufferedInputStream(fis);//创建缓冲区,设置读取文件对象
BufferedOutputStreambus=new BufferedOutputStream(fos);//创建复制文件名

byte[] bb=newbyte[1024*5]; //定义一个字节数组
int len=0;
while((len=bis.read(bb))!=-1)//判断文件是否读取完{
bus.write(bb,0,len);//写入到目标文件
bus.flush();
}
bus.close();
bis.close();
}
}

四、流的操作规律:

 

1、明确源和目的。

数据源:就是需要读取,可以使用两个体系:InputStreamReader

数据汇:就是需要写入,可以使用两个体系:OutputStreamWriter

2、操作的数据是否是纯文本数据?

如果是:数据源:Reader

       数据汇:Writer 

如果不是:数据源:InputStream

数据汇:OutputStream

3、虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

明确操作的数据设备。

数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)

数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)

4、需要在基本操作上附加其他功能吗?比如缓冲。

如果需要就进行装饰。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于 字节流 编码表。

凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。