黑马程序员--Java基础--IO流

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

IO流
用于处理设备上的数据,流,可以理解为数据流。
按流向分为:
1.输入流(读)。
2.输出流(写)。
按处理的数据分为:
1. 字节流:处理字节数据的流对象。计算机中最小的数据单元就是字节,这就意味着字节流可以处理设备上的所有数据,一样也可以处理字符数据。
2. 字符流:处理字符数据的流对象。
那为什么还用定义字节流呢?
因为:每一个国家的字符都是不一样的,这样就涉及到了字符编码的问题,就想中文就得有有 指定的编码表,才能正确的解析中文数据,所以为了方便于解析文字,就将字节流和编码表封装在了 对象,而这个对象就是字符流。只要是操作字符数据,优先考虑子字符流。

不管是按照什么方式来划分流,对于流的操作只有两种,既读和写。
常用的四个基类:
字符流:Reader Writer
字节流:InputStream OutputStream

这四个接口,它们的子类都有一个共性的特点,就是子类的后缀名都是其父类名,而前缀名则是这个 子类的功能。

IO体系图:
这里写图片描述

先来简单学习字符流:
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
|—BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

      |—LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
|—InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
      |—FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
|—CharArrayReader:
|—StringReader:

Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
|—BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|—OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
       |—FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
|—PrintWriter:
|—CharArrayWriter:
|—StringWriter:

字节流:
InputStream:是表示字节输入流的所有类的超类。
|— FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|— FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
      |— BufferedInputStream:该类实现缓冲的输入流。
      |— Stream:
|— ObjectInputStream:
|— PipedInputStream:

OutputStream:此抽象类是表示输出字节流的所有类的超类。
|— FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|— FilterOutputStream:此类是过滤输出流的所有类的超类。
       |— BufferedOutputStream:该类实现缓冲的输出流。
       |— PrintStream:
       |— DataOutputStream:
|— ObjectOutputStream:
|— PipedOutputStream:

下面举例来说明。
1、写文件:
需求:创建一个文件,并将数据写入到该文件中。
代码:

import java.io.*;
class FileWriterDemo 
{
    public static void main(String[] args) 
    {   
        FileWriter fw = null;//创建流对象的引用。
        try
        {
            fw = new FileWriter("Liu.txt");//创建流对象,必须指定文件名。

            fw.write("abcdefghi");//写入数据,但是这些数据是写到了流中,并没有写入到目的地中。

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


        }
        catch (IOException e)
        {
            System.out.println(e.toString());//对IO异常做的简单的处理、
        }
        finally{
            try
            {   if(fw!=null)
                    fw.close();//关闭流资源,但是在关闭之前会先刷新一次流中的缓冲区。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//对IO异常做的简单的处理。
            }
        }
    }
}

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

需求:要求在原来文件的基础上进行内容的续写,而不是将原有的文件覆盖掉。
代码和上面的一样,是不过是将创建流对象的时候的构造函数变成了FileWriter(文件名,boolean值);当boolean值为true时,是将现有的数据添加到原有的数据末尾处。
也就是说以上代码只需要将fw = new FileWriter(“Liu.txt”);这句话改成:fw = new FileWriter(“Liu.txt”,true);即可实现数据的续写,而不是就将原有的数据覆盖掉。

2、读文件:
需求:将上面的代码写到指定文件下的数据读出来。
代码:

import java.io.*;
class FileReaderDemo 
{
    public static void main(String[] args) 
    {
        //创建一个读取流对象的引用。
        FileReader fr =null;
        try
        {
            //创建一个文件读取流对象,并指定文件名。
            fr = new FileReader("Liu.txt");

            //调用读取数据的方法。
            //int a = fr.read();//返回int类型。当返回-1时,意味着读到文件的末尾了。

            //这就变成了连续读取数据了、
            int a =0;
            while((a=fr.read())!=-1){
                System.out.println((char)a);//打印读取到的数据。
            }
        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常简单处理、
        }
        finally{
            try
            {
                if(fr!=null)//判断对象引用是否为空。
                    fr.close();//关闭读取流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
        }
    }
}

发现以上代码可以优化:
优化后的代码:

import java.io.*;
class FileReaderDemo 
{
    public static void main(String[] args) 
    {
        //创建一个读取流对象的引用。
        FileReader fr =null;
        try
        {
            //创建一个文件读取流对象,并指定文件名。
            fr = new FileReader("Liu.txt");

            //调用读取数据的方法。
            //int a = fr.read();//返回int类型。当返回-1时,意味着读到文件的末尾了。

            /*
            //这就变成了连续读取数据了、
            int a =0;
            while((a=fr.read())!=-1){
                System.out.println((char)a);//打印读取到的数据。
            }
            */

            //读取数据的第二种方式:用read(char[]);返回的是读到的字符的个数。
            //定义一个char类型的数组,用于存储读到的数据。
            char[] buf = new char[10];

            int num =0;//用来接收读到的字符个数。
            while((num=fr.read(buf))!=-1){
                System.out.print(num+";;;;;;"+new String(buf,0,num));
            }


        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常简单处理、
        }
        finally{
            try
            {
                if(fr!=null)//判断对象引用是否为空。
                    fr.close();//关闭读取流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
        }
    }
}

在接下来练习:
需求:读取任意一个文本文件。这个文件我们就用写入流的代码。
代码:

import java.io.*;
//需求:读取任意一个文本文件。
class FileReaderTest 
{
    public static void main(String[] args) 
    {
        FileReader fr = null;//建立读取流对象的引用。
        try
        {
            fr = new FileReader("FileWriterDemo.java");//创建一个读取流对象,并指定文件名称,也就是把文件与读取流相关联。

            char[] buf = new char[1024];//定义一个char数组,用于缓存读到的数据。大写建议定义成1024的整数倍。

            int num = 0;//定义一个int类型的变量,用来接收read方法返回的读到的字符个数,当返回为-1时,意味着读到了文件的末尾处。
            while((num=fr.read(buf))!=-1){
                System.out.print(new String(buf,0,num));//打印读取的数据。
            }
        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常的简单处理。
        }
        finally{
            try
            {
                if(fr!=null)//判断对象引用是否为空。
                    fr.close();//关闭读取流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常的简单处理。
            }
        }
    }
}

结果为如下图:
这里写图片描述

需求:复制一个图片。
代码:

import java.io.*;
/*
    需求:复制一个图片。
*/
class Test2 
{
    public static void main(String[] args) 
    {
        //创建一个输入字节流对象的引用。
        FileInputStream fi = null;
        //创建一个输出字节流对象的引用。
        FileOutputStream fo = null;
        try
        {
            //创建一个输入字节流对象,并指定文件名称。
            fi = new FileInputStream("C:\\1.png");
            //创建一个输出字节流对象,并指定文件名称。
            fo = new FileOutputStream("D:\\1.png");

            byte[] buf = new byte[1024];//定义一个缓冲区。
            int num = 0;//定义一个int类型的变量,用来接收read返回的数值。
            while((num=fi.read(buf))!=-1){
                fo.write(buf);//将缓冲区中的数据写入到指定目的地,不用刷新。
            }
        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常简单处理。
        }
        finally{
            try
            {
                if(fi!=null)//判断输入流对象的引用是否为空。
                    fi.close();//关闭输入流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
            try
            {
                if(fo!=null)//判断输出流对象的引用是否为空。
                    fo.close();//关闭输出流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
        }
    }
}

复制文件的代码。
代码:
/*
需求:复制一个文本文件。
*/

import java.io.*;
class Test3 
{
    public static void main(String[] args) 
    {
        //创建一个读取流的对象引用。
        FileReader fr = null;
        //创建一个写入流的对象引用。
        FileWriter fw = null;
        try
        {
            //创建一个读取流对象。
            fr = new FileReader("C:\\Test11.java");
            //创建一个写入流对象。
            fw = new FileWriter("D:\\Test22.txt");
            //定义一个char类型数组。用于存储read读取的数据。
            char[] buf = new char[1024];
            //定义一个int类型的变量,用于接收read返回的数值。
            int num = 0;
            while((num=fr.read(buf))!=-1){
                fw.write(buf,0,num);//将读取流读到数组中的数据写入到写入流中。
                fw.flush();//将写入流中的数据刷新到目的地中。
            }
        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常的简单处理。
        }
        finally{
            try
            {
                if(fr!=null)//判断读取流对象引用是否为空。
                    fr.close();//关闭读取流对象。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常的简单处理。
            }
            try
            {
                if(fw!=null)//判断写入流对象的引用是否为空。
                    fw.close();//关闭写入流对象。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
        }
    }
}

缓冲技术:
为了提高流的效率,加入了缓冲技术,所谓的缓冲技术其实就是,在对象中加入了数组,先将数据暂时存放到数组中,然后在一次性的写出。
需求:要求用缓冲技术来复制一个文件。
代码:

import java.io.*;
class BufDemo 
{
    public static void main(String[] args) 
    {
        //创建一个读取流对象的引用。
        FileWriter fw = null;
        //创建一个写入流对象的引用。
        FileReader fr = null;

        //创建写入流缓冲区对象的引用。        
        BufferedWriter bufw  = null;
        //创建读取流缓冲区对象的引用。
        BufferedReader bufr = null;
        try
        {
            //创建读取流对象。
            fr = new FileReader("C:\\Test11.java");
            //创建写入流对象。
            fw = new FileWriter("D:\\haha.txt");
            //加入缓冲技术。其实就是将流作为参数传入到缓冲技术的构造函数中。
            bufw = new BufferedWriter(fw);

            bufr = new BufferedReader(fr);

            //定义一个char数组,用于存储从缓冲读取到的数据。
            char[] buf = new char[1024];
            //定义一个int类型的变量,用于接收read方法返回的数值,用于判断是否读取文件结束。
            int len = 0;
            //调用缓冲区的特有方法。
            while((len = bufr.read(buf))!=-1){
                bufw.write(buf,0,len);//将从读取流读取到的数据写入到写入流中。
                bufw.flush();//刷新写入流的缓冲区,将数据刷到目的地。
            }
        }
        catch (IOException e)
        {
            System.out.println(e.toString());//IO异常简单处理。
        }
        finally{
            try
            {
                if(fw!=null)//判断写入流对象的引用是否为空。
                    fw.close();//关闭写入流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
            try
            {
                if(fr!=null)//判断读取流对象的引用是否为空。
                    fr.close();//关闭读取流资源。
            }
            catch (IOException e)
            {
                System.out.println(e.toString());//IO异常简单处理。
            }
        }

    }
}

IO中的使用到了一个设计模式:装饰设计模式。
装饰设计模式解决:对一组类进行功能的增强。
包装:写一个类(包装类)对被包装对象进行包装;
*1、包装类和被包装对象要实现同样的接口;
*2、包装类要持有一个被包装对象;
*3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;
缓冲技术就是用到了设计模式。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。
我们并且发现:换流有一个子类就是操作文件的字符流对象:
InputStreamReader
  |–FileReader
OutputStreamWriter
  |–FileWrier
想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader(“a.txt”);
InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),”gbk”);
以上两句代码功能一致,
如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader(“a.txt”); //因为简化。

如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。(摘自毕向东课件总结)
File类:
是将文件和文件夹封装成了对象,提供了更多的可以对文件和文件夹操作的功能。
File类常见方法:
1:创建。
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建此抽象路径名指定的目录。
boolean mkdirs():创建多级目录。

2:删除。
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。

3:获取.
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。

4:判断:
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。

5:重命名。
boolean renameTo(File dest):可以实现移动的效果。剪切+重命名。

String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。
如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。

递归:就是函数自身调用自身。
什么时候用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。

递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。

其他:
SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。

表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。

但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。

合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。

RandomAccessFile:
特点:
1:该对象即可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:该对象操作的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。

管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。

总结:
流的操作规律:
1.明确源和目的。
数据源:就是需要读取,可以使用两个体系:InputStream、Reader;
数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;
2.操作的数据是否是纯文本数据?
如果是:数据源:Reader
数据汇:Writer
如果不是:数据源:InputStream
数据汇:OutputStream
3.虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4.需要在基本操作上附加其他功能吗?比如缓冲。
如果需要就进行装饰。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值