JAVASE第八章(IO流)


前言

数据在两设备间的传输称为流,流的本质是数据传输,JAVA根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。在整个Java.io包中最重要的就是5个类和一个接口,5个类指的是File、OutputStream、InputStream、Writer、Reader,一个接口指的是Serializable,掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识。


一、File类

File类:java.io包中很重要的一个类,File对象代表磁盘中实际存在的文件和目录,可以对文件或目录的属性进行操作,如:文件名、最后修改日期、文件 大小等。但不能直接对文件进行读/写操作。

那么要如何使用File类呢?
1.File类的构造方法

1.File(String pathname)  通过将给定的路径名字符串创建File实例。 
2.File(String parent, String child)  从父路径名字符串和子路径名字符串创建File实例。 
File f1=new File("D:/demo.txt");
File f2=new File("D:/","demo.txt");

2.File类的常用方法:判断方法、获取方法、创建和删除方法等。

boolean exists() 判断文件是否存在,存在返回true,否则返回false
boolean isFile() 判断是否为文件,是文件返回true,否则返回false
boolean isDirectory() 判断是否为目录,是目录返回true,否则返回false
String getName() 获得文件的名称
long length() 获得文件的长度(字节数)
boolean createNewFile() throws IOException 创建新文件,创建成功返回true,否则返回false,有可能抛出 IOException异常,必须捕捉。
boolean delete() 删除文件,删除成功返回true,否则返回false
public String[] list() 将目录下的子目录及文件的名字,返回到String数组中
public File[] listFiles() 将目录下的子目录及文件的实例返回到File数组中
public class FileDemo1 {
  public static void main(String[] args){  
      File f1=new File("D:/demo.txt");
      File f2=new File("D:/","demo.txt");
      System.out.println(f1.canRead());
      System.out.println(f1.exists());
      System.out.println(f1.isFile());
      System.out.println(f1.isDirectory());
      System.out.println(f1.length());
      System.out.println(f1.getAbsolutePath());
      System.out.println(f1.getParent());
      System.out.println(f1.getName());
      System.out.println(new Date(f1.lastModified()));
  }
}

二、输入及输出的概念

输入:把硬盘上的数据读到程序中,即数据的read操作。
输出:从程序往外部设备写数据,即数据的write操作。

在这里插入图片描述
在这里插入图片描述


三、流的体系结构

流的概念:数据在两设备间的传输称为流,流的本质是数据传输。Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。

流按数据的传输方向可以分为:输入流和输出流。
1.输入流:把外界(键盘、文件、网络)的数据读入到程序中。
2.输入流:把程序中的数据输出到外界(显示器、打印机、文件、网络)。
流按数据的编码格式又可以分为:字节流和字符流。
1.字节流:读写二进制文件,处理单元为1个字节,主要处理音频、图片、歌曲。
2.字符流:主要处理字符或字符串,处理单位为1个字符。

在这里插入图片描述


四、字节流与字符流与转换流

1. 字节流:数据流中最小的数据单元是字节,常用类FileInputStream和FileOutputStream。
2. 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。常用类为FileReader和FileWriter。

字节流和字符流的区别:

1 读写单位不同:
  字节流以字节(8bit)为单位,字符流以字符为单位。
  
2.处理对象不同:
  字节流能处理所有类型的数据(如图片、视频等)
  字符流只能处理字符类型的数据。
  
3.结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。

转换流的特点:字符流和字节流之间的桥梁。

1.对读取到的字节数据经过指定编码转换成字符。
2.对读取到的字符数据经过指定编码转换成字节。
InputStreamReader(InputStream in):将字节流以字符流输入。
OutputStreamWriter(OutStream out):将字节流以字符流输出。

何时使用转换流?

1.当字节和字符之间有转换动作时。
2.流操作的数据需要编码或解码时。

五、输入输出字节流

1.InputStream:字节输入流,用来将文件中的数据读取到java程序中,最常用的子类FileInputStream。

FileInputStream类的常用方法如下:

1.abstract int read() :
读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。

2.int read(byte[]b) :
将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。

3.int read(byte[]b, int off, int len) :
将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。

4.流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。

2.OutputStream:字节输出流,用来将程序中的数据输出到文件中,最常用的子类FileOutputStream。

FileOutputStream的常用方法:

1.abstract void write(int b):
往输出流中写入一个字节。

2.void write(byte[] b) 
往输出流中写入数组b中的所有字节。

3.void write(byte[] b, int off, int len) 
往输出流中写入数组b中从偏移量off开始的len个字节的数据。

其它方法
void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
void close() :关闭输出流,释放和这个流相关的系统资源。

关于输入输出字节流的代码如下所示,代码较长,建议结合文字观看。

public class StreamDemo2 {
    public static void main(String[] args) {
        FileInputStream in=null;
        FileOutputStream out=null;
        //首先我们告诉java输入流去读写计算机哪个文件
        try {
            in=new FileInputStream("D:/demo.txt");
            out=new FileOutputStream("D:/test.txt");
            int b;
            while((b=in.read())!=-1){   //从该输入流读取一个字节的数据。
                out.write(b);
            }
        } catch (FileNotFoundException e) {         //
            e.printStackTrace();
        } catch (IOException e) {      //读写文件时可能会有异常
            e.printStackTrace();
        }
            finally {
            if(in!=null){
                try {
                    in.close();       //关闭流,撤销管道,供其他使用
                } catch (IOException e) {  //关闭流时可能会发生异常
                    e.printStackTrace();
                }
            }
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
public class StreamDemo3 {
    public static void main(String[] args) {
        FileInputStream in=null;
        FileOutputStream out=null;
        try {
            //先告诉计算机要去读写哪个文件
            in=new FileInputStream("D:/demo.txt");
            out=new FileOutputStream("D:/test.txt");
            byte[]b=new byte[1024];  //定义一个数组用来存储读入的数据
            int length=0;     //记录每次读取实际的个数
            while((length=in.read(b))!=-1){  //当读到结尾时返回-1
                out.write(b,0,length);     //将 len字节从位于偏移量 off的指定 byte阵列写入此输出流。
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

六、输入输出字符流

1.Reader:字符输入流,其常用的子类为FileReader。
2.Writer:字符输出流,其常用的子类为FileWriter。

public class CharDemo1 {
    public static void main(String[] args) {
        try {
            FileReader f1=new FileReader("D:/1.txt");
            BufferedReader b1=new BufferedReader(f1);
            //构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。
            FileWriter f2=new FileWriter("D:/2.txt",true);  //往后面追加
            BufferedWriter b2=new BufferedWriter(f2);
           /* char[]b=new char[5];
            int length=0;
            while((length=b1.read(b))!=-1){ //读取一个字符并以整数的形式返回, 如果返回-1已到输入流的末尾
                b2.write(b,0,length);
            }*/
            String line=null;
            while((line=b1.readLine())!=null){  //每次读入一行,读到结尾时返回null
                b2.write(line);   //往目标中写入一行
                b2.newLine();      //写入一个换行符
            }
            b1.close();
            b2.flush();
            b2.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

七、缓冲流

缓冲流:令输入输出流具有1个缓冲区, 显著减少与外部设备的IO次数, 而且提供一些额外的方法,常用的缓冲流有

1.缓冲字节输入流:BufferedInputStream
2.缓冲字节输出流:BufferedOutputStream
3.缓冲字符输入流:BufferedReader
4.缓冲字符输出流:BufferedWriter

缓冲流其高效的原因是什么?
1、BufferedInputStream高效的原理:创建一个较大的byte数组(8192byte),一次性从底层输入流中读取多个字节来填充byte数组,当程序读取一个或多个字节时,可直接从byte数组中获取(在内存中而不是硬盘,所以效率高),当内存中的byte读取完后,会再次用底层输入流填充缓冲区数组。

2、BufferedOutputStream高效的原理:也是准备了一个数组,当外界调用write方法想写出一个字节的时候,将这个字节存储到数组中,一直到该数组所有8192个位置全都占满,才把这个数组中的所有数据一次性写出到目标文件中。如果最后一次循环过程中,没有将数组写满,最终在关闭流对象的时候,也会将该数组中的数据刷新到文件中。
在这里插入图片描述

拿缓存输入流举个例子:现在有8192个快递在仓库(硬盘),如果我们一个一个的拿(每读取一个字节就要访问一次硬盘),效率非常的低。即便使用麻袋(read(byte b[]))一次拿多个快递,也会频繁的对磁盘操作。我们可以一次性把8192个快递放到驿站中(利用底层输入流),当我们需要取一个或者多个快递时可直接从驿站(内存,速度快)中获取。

同理,缓存输出流可以理解为:现在要往一个地方发一堆快递,如果一件一件或者拿麻袋发,效率非常低。可以把收到的快递暂时存到一个大卡车(数组缓冲区)上面,等到快递数量到达8192时一次性将快递发送过去(写出到目标文件中)。

关于缓冲流怎么使用如下面代码所示:

public class BufferedDemo1 {
    public static void main(String[] args) {
        try {
            FileInputStream in=new FileInputStream("D:/1.mp3");
            //套上一层更高级的管道
            BufferedInputStream b1=new BufferedInputStream(in);
            FileOutputStream out=new FileOutputStream("D:/2.mp3");
            //套上一层更高级的管道
            BufferedOutputStream b2=new BufferedOutputStream(out);
        /* 里面有个缓冲区,可以自定义大小,默认大小为8192,当缓冲区满时往外写
           public BufferedOutputStream(OutputStream out) {
                this(out, 8192);

            }*/
            byte[]b=new byte[1024];
            int length=0;
            while((length=b1.read(b))!=-1){
                b2.write(b,0,length);
            }
            /*public synchronized void write(byte b[], int off, int len) throws IOException {
                if (len >= buf.length) {  //当传进来的元素个数长度大于缓冲区长度,直接往外写
                    flushBuffer();
                    out.write(b, off, len);
                    return;
                }
                if (len > buf.length - count) {
                    flushBuffer();  //当缓冲区满时调用
                }
                System.arraycopy(b, off, buf, count, len);  //先把数据拷贝到缓冲区中
                count += len;
            }*/
       /*     private void flushBuffer() throws IOException {
                if (count > 0) {
                    out.write(buf, 0, count);   //往外写数据
                    count = 0;    //重置count
                }
            }*/
            b1.close();
            b2.flush();
            b2.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

补充:缓冲流实际上是设计模式中装饰模式的一种实现。字节流和字符流,都是装满时自动写出,或者没满时手动flush写出,或close时刷新写出。


八、Print流

Print打印流:只做输出流,没有输入流,可以分为字节打印输出流和字符打印输出流。

public class PrintWriterDemo1 {
	public static void main(String[] args) {
	try {
	//PrintWriter 是字符类型的打印输出流,它继承于Writer。
	//将对象的格式表示打印到文本输出流。 这个类实现了全部在发现print种方法PrintStream 。
	// 它不包含用于编写原始字节的方法,程序应使用未编码的字节流。
	PrintWriter myPrint=new PrintWriter("D:/my.html");
	//使用指定的文件创建一个新的PrintWriter,而不需要自动的线路刷新。
	myPrint.write("<h1>这是我自己设置的服务器");
	myPrint.write("<h1>这是我自己设置的服务器");
	myPrint.write("<h1>这是我自己设置的服务器");
	myPrint.write("<h1>这是我自己设置的服务器");
	myPrint.write("<h1>这是我自己设置的服务器");
	myPrint.close();
	} catch (FileNotFoundException e) {
	e.printStackTrace();
	}
  }
}

九、对象输入输出流

对象输入输出流:作用是持久化保存对象,用对象输出流保存对象(对象的序列化过程),需要使用时用对象输入流恢复使用(对象的反序列化过程)。

如果需要序列化对象,需要实现生成一个序列化id号,实现Serializable接口后默认生成一个版本号,一旦类中信息发生改变,版本号自动变化,所以可以显性的定义一个版本号。

注意:使用transient关键字加到属性上,可以不把该属性写进序列化文件。

public class ObjectOutputStreamDemo1 {
    public static void main(String[] args) {
        try {
            //要将序列化之后的对象保存下来,需要通过对象输出流(ObjectOutputStream) 将对象状态保存
            //对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程
            FileOutputStream out=new FileOutputStream("D:/myFile.txt");
       //    Date d=new Date();
            ObjectOutputStream myOut=new ObjectOutputStream(out);  //处理流封装了一个节点流
          //  myOut.writeObject(d);  //用writeObject()方法可以直接将对象保存到输出流中
            Student s1=new Student("金子豪",21);
            Student s2=new Student("张三",22);
            myOut.writeObject(s1);
            myOut.writeObject(s2);
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



public class ObjectInputStreamDemo1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //对象 的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程
        FileInputStream in=new FileInputStream("D:/myFile.txt");
        ObjectInputStream myInput=new ObjectInputStream(in);
      //  Object obj= myInput.readObject();  //readObject()方法可以直接读取一个对象,
   /*     if(obj instanceof Date){
            Date d=(Date)obj;  //向下转型,父类转换成子类
            System.out.println(d.toString());
        }*/
        System.out.println(myInput.readObject());
        System.out.println(myInput.readObject());


    }
}


public class Student implements Serializable {
    //如果需要将一个类的对象信息序列化到文件中,需要生成一个序列化id号,
    //实现接口后默认生成一个版本号,一旦类中信息发生改变,版本号自动变化
    //所以可以显性的定义一个版本号。
    private static final long serialVersionUID = -8244863449082098647L;
    transient String  name; //可以不把该属性写进序列化文件,忽略该属性
    int age;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

总结

在Java语言中使用字节流和字符流的步骤基本相同,以输入流为例,首先创建一个与数据源相关的流对象,然后利用流对象的方法从流输入数据,最后执行close()方法关闭流。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JinziH Never Give Up

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值