当前位置: 首页 > article >正文

【Java学习笔记】13. I/O系统

java中的数据可以使用一套输入/输出流的通信系统来存储和获取。这个系统是在java.io包中实现的。I/O系统主要通过创建输入/输出流来读取和存储数据。通常,I/O系统分为字节流和字符流。字节流用来处理字节、整数和其他简单的数据类型,字符流用来处理文本文件和其他文本数据源。

字节流字符流
InputStreamReader
OutputStreamWriter

13.1 I/O流概述

输入/输出(Input/Output)是指对某个物理或逻辑设备或某种㡳进行数据的输入或输出。例如:对服务器主机数据的读/写、摄像头视频数据的输入、本地文件的输入/输出等。由于数据存取的环境和设备不同,所以数据的读/写方案具有多样性。输入/输出问题在程序射击中是一个复杂的问题。java针对这个问题,提出了自己的解决方案——流(Stream)对象。对不同的输入/输出问题提供了不同的流对象。所有的数据都可以使用流写入和读出。流是程序中数据传输的一条路径。

数据流一般分为输入流(InputStream)和输出流(OutputStream)两种,这两种数据流是两个抽象类,意味着其方法在不同的子类中,有多种不同的实现方法。如操作文件,当向文件写入数据时,它是一个输出流(从内存输出到文件),当从文件中读取数据的时候,它是一个输入流(从文件输入到内存)。键盘只是一个产生输入流的工具,屏幕只是一个输出流的体现工具。

java标准的数据流程序与系统在字符方式(如Dos)下的交互,可分为三种:

  • System.in 标准输入流。他已经打开并准备输入数据。此种数据流通常应用于键盘输入和指定另外的输入源。
    public final static InputStream in = new InputStream();
    -System.out是标准输出流。它已经打开并且准备输出数据。这种数据流通常应用于显示器屏幕输出或指定另外一个输出目标。
    public final static PrintStream out = new PrintStream();

  • System.err是标准错误输出流。他已经打开并准备输出数据。这种数据流通常应用于显示器屏幕输出或指定另外一个输出目标。
    public final static PrintStream err = new PrintStream();

InputStream抽象类是所有输入字节流的超类,它必须提供返回下一个输入字节的方法。输入字节流的基本处理单元是字节。
InputStream的子类
OutStream抽象类是所有输出字节流的超类,它按字节接收数据,并将这些数据发送给接收器。OutputStream抽象类的子类必须提供至少一种写入单个输入字节的方法才能使用。
在这里插入图片描述

Reader是读取字符流的抽象类。子类必须实现的方法只有两个:
read(char[],int,int)close().多数情况下需要重写一些方法,提高效率。
在这里插入图片描述
Writer是写入字符流的抽象类。其子类必须实现的方法只有三个`
write(char[],int off,int len)\ flush()\ close()
在这里插入图片描述

13.2 文件

在I/O处理中,首先想到的是如何进行文件的读/写。java中有关文件处理的类有:File、FileInputStream、FileOutputStream、RandomAccessFile、FileDescriptor;接口:FilenameFilter。
这节主要讲述File类和RandomFile类的常用方法。

13.2.1 File 类

不同操作系统的路径名称是有差别的。例如:

WindowsLinux
D:\workspace\Capther13/home/workspace/Capther13

windows的路径使用UNC(通用命名约定(Universal Naming Convention))路径名,
\\开始的目录表示上下文环境所在的目录的硬盘根目录。
如果没有以\\作为路径的开始,则表示相对于当前工作目录的路径,并通过盘符(C/D/E/…)形式表示硬盘指定。

Linux没有Windows系统硬盘驱动器的概念。他的路径以/开始,表示从根目录开始的绝对路径,不以/开始的路径是相对于当前路径的相对路径。


一个File对象的实例被创建之后,他的内容不能被修改。File实例对象可以用于表示一个文件,还可以用于表示一个目录。甚至可以查询文件系统。
在java中,无论是文件还是目录,都使用File类的实例表示

1.文件或目录的生成

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.文件名的处理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.文件属性测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.普通文件信息和工具
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
5.目录操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.2.2 File 类的应用

【例1】遍历文件夹下的文件,并且输出文件夹的信息
package example;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;

public class FileDemo {


    public static <ReadFileList> void main(String[] args) {
//        String filename = args[0];          //由参数获取文件名
        String filename = "D:\\eclipse-eclipse\\Capther11-\\src\\main\\java\\example";          //由参数获取文件名

        File file = new File(filename);

        Filter filter = new Filter("java");//创建并初始化文件过滤器
        //创建FileDemo实例,并调用ReadFlieList()方法
        new FileDemo().ReadFileList(file, filter);

    }


    public void ReadFileList(File file ,Filter filter){
        if(file.isDirectory()) {//判断文件是不是目录
            try{
                //列出所有文件及目录
                File[] files = file.listFiles(filter);//创建目录数组
                //通过数组创建数组列表
                ArrayList<File> ArrayList = new ArrayList<File>();
                for (int i = 0;i<files.length;i++){
                    //先列出目录
                    if (files[i].isDirectory()){//判断是否为目录
                        //输出路径名
                        System.out.println("【"+files[i].getPath()+"】");
                        ReadFileList(files[i],filter); //递归调用ReadFileList方法
                    }else{
                        //文件先存入fileList,待会再列出
                        ArrayList.add(files[i]);
                    }
                }

                //列出文件
                for (File f : ArrayList){
                    ReadFileList(f,filter);
                }
                System.out.println();//输出换行符

            }catch (Exception e){
                e.printStackTrace();
            }
        }else if(file.isFile()){    //当file是文件时
            FileDesc(file);     //调用文件排序方法
        }

    }


    public void FileDesc(File file){
        if (file.isFile()){
            System.out.print(file.toString()+"\n该文件");
            System.out.print(file.canRead()?"可读":"不可读");
            System.out.print(file.canWrite()?"可写":"不可写");
            System.out.println(file.length()+"字节");
        }
    }

}

class Filter implements FilenameFilter{
    String extent;  //拓展名

    public Filter(String extent) {
        this.extent = extent;
    }

    public boolean accept(File dir, String name) {
        return name.endsWith("."+extent);//返回文件的拓展名
    }
}

zzzZZZZZZssx

13.2.3 RandomAccessFile类

通常,文件的操作都是循环进行的,在文件中进行一次存取操作,它的读取/写入位置就医相对于目前的位置移动一次。实际上,如果需要读取或写入的动作只是在某一个区段内,则可以采用随机存取(Random Access)的方法。随机存取可在文件中任意地移动存取的位置。
在Java中, RandomAccessFile类可以给用户提供随机访问的方法,使用它的seek()方法来指定存取的位置。位置移动的单位是字节。
类的生明
接口DataInput用于在二进制流中读取字节,并根据基本数据类型进行重构。
接口DataOutput用于将数据从任意Java的基本类型转换为一系列的字节,并写入二进制流。

1.构造方法
在这里插入图片描述

在这里插入图片描述
2.文件指针的操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为了保证移动存取位置的正确性,通常在随机文件读取之前固定每个数据的长度。如固定为每一个雇员的数据大小,java中并没有提供直接的方法来取得固定长度的数据,必须自己计算获得。

13.2.4 RandomAccessFile类的应用

RandomAccessFile类在随机(相对顺序而言)读/写等长记录数据的格式时有很大的优势,比如,读取数据库中某一条记录时。但是RandomAccessFile类只能用于操作文件,不能访问其他的I/O设备,如网络、内存映像等。

【例2】一个演示保存和访问雇员对象的例子
package org.example;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {

    public static void RandomWriteFile(File file) throws IOException {
        Employee[] employees = new Employee[4]; //创建数组
        //初始化数组
        employees[0] = new Employee("张三",24);
        employees[1] = new Employee("李四",22);
        employees[2] = new Employee("王五",24);
        employees[3] = new Employee("钱六",22);
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");;//创建RandomAccessFile
        try {
            RandomAccessFile randomAccessFile1 = new RandomAccessFile(file, "rw");
        }catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        try {
            for (Employee e:employees){
                randomAccessFile.writeChars(e.getName());
                randomAccessFile.writeInt(e.getAge());
            }
            randomAccessFile.close();//关闭randomAccessFile
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static String readName(RandomAccessFile randomAccessFile)throws IOException {
        char[] name = new char[8];
        for (int i = 0;i< name.length;i++){
            name[i] = randomAccessFile.readChar();//读取字符
        }
        //将空字符取代为空格符并返回
        return new String(name).replace('\0',' ');
    }

    public static Employee[] RandomReadFile(File file) throws Exception{
        RandomAccessFile randomAccessFile;//创建RandomAccessFile对象
        randomAccessFile = new RandomAccessFile(file,"r");
        Employee[] employee = new Employee[4];
        //Employee类的占用空间
        int num = (int) randomAccessFile.length()/Employee.size();
        for (int i = 0;i<num;i++){
            randomAccessFile.seek((i)*Employee.size());
            //使用对应的read()方法读出数据
            employee[i] = new Employee(readName(randomAccessFile),randomAccessFile.readInt());
        }
        randomAccessFile.close();//关闭randomaccessfile
        return employee;

    }

    public static void main(String[] args) throws Exception{
        String filename = "employeeExample";//创建并初始化文件名称
        File file = new File(filename);//创建并初始化File对象
        RandomWriteFile(file);//调用RandomWriteFile()方法
        Employee[] employee = RandomReadFile(file);//返回文件中保存的employee
        //使用for循环遍历employee数组
        for (Employee e : employee){
            System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());
        }

    }
}

class Employee {
    String name;
    int age;
    final static int LEN = 8;//创建并初始化静态

    public Employee(String name, int age) {
        if (name.length()>LEN){
            name = name.substring(0,8); //截取字符串的子字符串
        }else {
            while (name.length()<LEN)
                name = name+"\u0000";
        }
        this.name = name;
        this.age = age;

    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

    //获取类占用的空间
    public static int size(){
        return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。
    }
}

在这里插入图片描述

13.3 字节流InputStream、OutputStream

计算机中所有数据都以0和1的方式保存,但是如果在两个不同设备或者环境之间进行数据的读/写,则也必须以0与1的方式进行。java中数据源与目的地之间的数据流动抽象为一个流(Stream),流中间流动的是位数据。
java中有两个类用于流的抽象表示:
java.io.InputStream&java.io.OutputStream

13.3.1 字节输入、输出流

**字节输入流(InputStream)**是所有字节输入流的超类,它是一个抽象类, 它的派生类必须重新定义字节输入流中声明的抽象方法。
read()方法用于从输入流中读取一个字节的内容并以整型返回。如果遇到流的结束符则返回-1;如果流没有结束,但暂时又没有数据可读,那么该方法就会处于阻塞情况,直到流中有了新的可读数据。

1.从流中读取数据
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.关闭流
在这里插入图片描述
3.使用输入流中的标记
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


字节输出流OutputStream是所有字节输出流的超类,它是一个抽象类,它的派生类必须重新定义字节输出流中定义的抽象方法。

1.输出数据
在这里插入图片描述
2.清空输出流
在这里插入图片描述
3.关闭流
在这里插入图片描述

【例3】in对象输入和out对象输出的实例
package example;

import java.io.IOException;

public class ReadCharacter {

	public static void main(String[] args) {
		
		try {
			System.out.print("请输入字符:");//输出字符串信息
			//获取键盘数据并输出
			System.out.println("输入字符十进制表示为:"+System.in.read());
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
通常情况下,由于InputStream或OutputStream的方法都比较底层,所以很少直接使用,而是通过实现他们的子类来实现更高级的操作,使输入/输出更便捷。

13.3.2 字节文件输入、输出流

  1. 字节文件输入流java.io.FileInputStream是InputStream的子类。该类指从某个磁盘文件中获得输入字节,并读取数据到目的地,至于哪些文件可用则取决于主机环境。
  2. 字节文件输出流java.io.FileOutputStream是OutputStream类的子类,它用于将数据写入磁盘文件或者FileDescriptor的输出流,文件是否可用取决于平台。
  3. 当创建一个FileInputStream或FileOutputStream实例对象时,指定文件应该是存在而且可读的。在创建一个FileInputStream实例对象时,如果指定文件已经存在,那么这个文件将被清除。如果这个文件不存在,就会创建新的文件。
    不能指定被打开的文件。
  4. FileInputStream的read()方法每次可读取一个字节,并以Int类型返回。或者使用read()方法时读取一个byte数组。读取的字节个数取决于数组的长度。
    byte数组通常被视为一个缓冲区,扮演数据中转的角色。
【例4】实现文件复制功能
package org.example;

import java.io.*;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class FileInputAndOutputStream {
   public static void main(String[] args) {
       try {
           byte[] buffer = new byte[1024];
           //源文件
//            FileInputStream fileInputStream = new FileInputStream(new File(args[0]));
           FileInputStream fileInputStream = new FileInputStream(new File("D:\\LOLFolder\\lol.txt"));
           //目的文件
//            FileOutputStream fileOutputStream = new FileOutputStream(new File(args[0]));
           FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\LOLFolder\\lol2.txt"));
           //avaliable()获取未读取的数据长度
           System.out.println("复制文件"+fileInputStream.available()+"字节");
           while(true) {
               if(fileInputStream.available()<1024) {
                   //剩余的数据比1024小
                   //一位一位读出再写入目的文件
                   int remain = -1;
                   while((remain=fileInputStream.read())!=-1){
                       fileOutputStream.write(remain);
                   }
                   break;//终止循环

               }else {
                   //从源文件读取数据至缓冲区
                   fileInputStream.read(buffer);
                   //将数据数组写入目的文件
                   fileOutputStream.write(buffer);
               }
           }
           //关闭流
           fileInputStream.close();
           fileOutputStream.close();
           System.out.println("复制完成");

       }catch(ArrayIndexOutOfBoundsException e) {
           e.printStackTrace();
       }catch(IOException e) {
           e.printStackTrace();
       }
   }
}

在这里插入图片描述

FileOutputStream默认以创建新文件的方式启动流。如果创建的文件存在,则文件将被覆盖。如果实例化时指定FileOutputStream为附加模式(构造方法第二个append参数为true),如果创建的文件存在,则直接打开源文件并启动流,写入数据附加到文件末端。如果不存在,则创建新文件并启动流。

13.3.3 字节缓冲输入、输出流

  1. 在 13.3.2中,FileOutputStream和fileInputStreamsm中使用了一个byte数组作为数据缓冲区。这是因为硬盘文件的存取速度远远低于内存中的数据存取速度。为了 减少对磁盘的访问,通常在读入文件的时候不以一个字节作为读取单位,而是读入一定长度的数据。同样写入数据也需要写入一定长度的数据,这样可以提高文件的处理效率。
  2. 字节缓冲输入流java.io.BufferedInputStream和字节缓冲输出流java.io.BufferedOutputStream为InputStream、OutputStream类增加缓冲区功能。通常通过创建一个InputStream、OutputStream类型的实例来构建BufferedInputStream、BufferedOutputStream实例。
  3. BufferedInputStream的数组成员buf是一个个位数组,默认是2048字节。当读取数据来源时(例:文件),他会尽量将buf填满。当使用read()方法时,实际上是先读取buf里的数据,而不是直接读取数据来源。当buf中的数据不足时, BufferedInputStream才会调用给定的InputStream对象的read()方法,从指定的装置中提取数据。
  4. BufferedOutputStream的数组成员buf是一个个位数组,默认是512字节。当使用write()方法写入数据时,实际上会现将数据写入buf中,当buf已满时才会调用给定的OutputStream对象的write()方法将buf数据写入至目的地。
  5. 使用 BufferedOutputStream、BufferedInputStream不需要自行设置缓冲区,还能使文件操作变简单,效率变高。
【例5】BufferedOutputStream、BufferedInputStream类的方法演示
package org.example;

import java.io.*;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class FileInputAndOutputStream {
   public static void main(String[] args) {
       try {
           byte[] data = new byte[1];//创建byte类型的数组
           //源文件
//            FileInputStream fileInputStream = new FileInputStream(new File(args[0]));
           File srcFile = new File("D:\\LOLFolder\\lol.txt");
           //目的文件
//            FileOutputStream fileOutputStream = new FileOutputStream(new File(args[0]));
           File desFile = new File("D:\\LOLFolder\\lol2.txt");

           //创建并初始化对象
           BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFile));
           BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desFile));

           System.out.println("复制文件"+srcFile.length()+"字节");
           while(bufferedInputStream.read(data)!=-1) {
              bufferedOutputStream.write(data);
           }
           //关闭流
           bufferedOutputStream.flush();
           bufferedInputStream.close();
           bufferedOutputStream.close();
           System.out.println("复制完成");

       }catch(ArrayIndexOutOfBoundsException e) {
           e.printStackTrace();
       }catch(IOException e) {
           e.printStackTrace();
       }
   }
}

在这里插入图片描述

  • BufferedOutputStream、BufferedInputStream类并没有改变InputStream、OutputStream类的接口方法,读入和写出还是由InputStream类的read()方法和OutputStream类的write()方法负责。
  • BufferedOutputStream、BufferedInputStream类在对数据进行操作之前,动态的为他们添加了缓冲区的功能。
  • 为了保证缓冲区中的数据可以全部被写入目的地,建议在关闭流之前执行flush()方法,将缓冲区中的数据全部写入目的流。

13.3.4 字节数据输入、输出流

  1. 字节数据输入流java.io.DataInputStream 和字节数据输出流java.io.DataOutStream提供了一些针对Java基本数据类型的写入和读出操作。
  2. 由于Java的数据类型占用空间大小有规定,在对基本数据类型数据进行写入和读出操作时,不需要担心不同平台间数据大小差异的问题。
  3. 例如,一个对象的成员数据都是Java的基本数据类型,要对这些数据存取就可以通过使用DataInputStream和DataOutputStream类来实现。
【例6】DataInputStream和DataOutputStream类的方法演示
package org.example;

import java.io.*;

//  File f =new File("D:\\LOLFolder\\lol2.txt");


public class DataIOStreamDemo {
    public static void main(String[] args) throws IOException {
        String filename = "D:\\LOLFolder\\lol3.txt" ; //创建并初始化文件名字符串
        //创建并初始化Employee类型数组
        Employee[] employees = {
                new Employee("张三",23),
                new Employee("张三",23),
                new Employee("张三",23),
                new Employee("张三",23),
        };
        try{
            //创建DataOutputStream对象
            DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(filename));
            for(Employee Employee:employees){
                //写入UTF字符串
                dataOutputStream.writeUTF(Employee.getName());
                //写入int数据
                dataOutputStream.writeInt(Employee.getAge());
            }
            //读出所有数据至目的地
            dataOutputStream.flush();
            //关闭流
            dataOutputStream.close();
            DataInputStream dataInputStream = new DataInputStream(new FileInputStream(filename));
            for (int i = 0; i <employees.length ; i++) {
                //读取UTF字符串
                String name = dataInputStream.readUTF();
                //读取int数据
                Integer sorce = dataInputStream.readInt();

                employees[employees.length-1-i] = new Employee(name,sorce);
            }
            //关闭流
            dataInputStream.close();
            //输出还原后数据
            //使用for循环遍历employee数组
            for (Employee e : employees){
                System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}



class Employee {
    String name;
    int age;
    final static int LEN = 8;//创建并初始化静态

    public Employee(String name, int age) {
        if (name.length()>LEN){
            name = name.substring(0,8); //截取字符串的子字符串
        }else {
            while (name.length()<LEN)
                name = name+"\u0000";
        }
        this.name = name;
        this.age = age;

    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

    //获取类占用的空间
    public static int size(){
        return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。
    }
}

在这里插入图片描述

  • DataInputStream 和 DataOutStream类并没有改变InputStream、OutputStream类的方法,数据的读取仍由nputStream、OutputStream类的方法负责实现。
  • DataInputStream 和 DataOutStream类只是在实现对应的方法时,添加了动态判断数据类型的功能。
  • 其他流对象也可以使用DataInputStream 和 DataOutStream类。

13.3.5 字节对象输入、输出流

  • 通常情况下,java中的数据以对象的形式放在内存中,所以希望数据也可以以对象的形式保存于硬盘文件,并且在下次运行程序时可以读取硬盘文件和数据文件,并还原为对象。
  • java提供了字节对象输入流java.io.ObjectInputStream、字节对象输出流java.io.ObjectOutputStream来实现这样的操作。
  • 如果想实现直接存储数据对象,则在定义类的时候必须实现java.io.Serializable 接口。但是java.io.Serializable接口没有规定必须实现的方法,所以这里的实现实际上就是给对象贴上一个标识,代表对象是可以序列化的
  • 对象序列化是指对象能够将自己的状态信息数据保存下来的特性。对象序列化的目的,是使得程序中的一个对象在存储或者进行网络传输的时候,另一个程序可以将对象还原。
【例7】ObjectOutputStream、ObjectInputStream类的方法演示
package org.example;

import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;

//  File f =new File("D:\\LOLFolder\\lol2.txt");


public class DataIOStreamDemo {
   public static void main(String[] args) throws IOException {
       String filename = "D:\\LOLFolder\\lol3.txt" ; //创建并初始化文件名
       //创建并初始化Employee类型数组
       Employee[] employees = {
               new Employee("张三",23),
               new Employee("张三",23),
               new Employee("张三",23),
               new Employee("张三",23),
       };

       //写入新文件
       writeObjectToFile(employees,filename);

       try{
           //读取文件数据
           employees = readObjectsFromFile(filename);

           for (Employee e : employees){
               System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());
           }
           System.out.println();//换行符
           //初始化Employee数组
           employees = new Employee[2];
           employees[0] = new Employee("李四",24);
           employees[1] = new Employee("王五",24);
           //附加新对象到附件
           appendObjectsToFile(employees,filename);
           //读取文件数据
           employees = readObjectsFromFile(filename);

           for (Employee e : employees){
               System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());
           }

       }catch (Exception e){
           e.printStackTrace();
       }

   }

   private static void appendObjectsToFile(Employee[] employees, String filename) throws FileNotFoundException {
       File file = new File(filename);
       if(!file.exists()){
           throw  new FileNotFoundException();
       }
       try {
           //附加模式
           ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file,true)){
               //如果要附加对象至文件后,必须重新定义这个方法

               @Override
               protected void writeStreamHeader() throws IOException {
               }//抛出异常
           };
           for (Employee employee:employees){
               outputStream.writeObject(employee);
           }
           outputStream.close();
       }catch (IOException e){
           e.printStackTrace();
       }
   }

   //将指定文件中的对象数据返回
   private static Employee[] readObjectsFromFile(String filename) throws FileNotFoundException{
       File file = new File(filename);
       //如果文件不存在就抛出异常
       if(!file.exists()){
           throw  new FileNotFoundException();
       }
       //使用List先存储读回的对象
       List<Employee> list = new ArrayList<Employee>();
       try {
           FileInputStream fileInputStream = new FileInputStream(filename);
           ObjectInputStream objInputstream =new ObjectInputStream(fileInputStream);
           while (fileInputStream.available()>0){
               //读取一个对象到列表中
               list.add((Employee)objInputstream.readObject());
           }
       }catch (Exception e){
           e.printStackTrace();
       }
       Employee[] employee = new Employee[list.size()];
       return list.toArray(employee);
   }

   //将指定的对象写入指定的文件
   private static void writeObjectToFile(Employee[] employees, String filename) {
       File file = new File(filename);
       try{
       ObjectOutputStream objoutputStream = new ObjectOutputStream(new FileOutputStream(filename));

           for (Object obj :employees){
       //将对象写入文件
           objoutputStream.writeObject(obj);
       }
       //关闭objoutputStream
           objoutputStream.close();
       } catch (Exception e){
           e.printStackTrace();
       }
       }
}


//将对象附加到指定文件之后




class Employee implements Serializable {
   String name;
   int age;
   final static int LEN = 8;//创建并初始化静态

   public Employee(){
   }


   public Employee(String name, int age) {
       if (name.length()>LEN){
           name = name.substring(0,8); //截取字符串的子字符串
       }else {
           while (name.length()<LEN)
               name = name+"\u0000";
       }
       this.name = name;
       this.age = age;

   }

   public String getName() {
       return name;
   }

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

   public Integer getAge() {
       return age;
   }

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

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

   //获取类占用的空间
   public static int size(){
       return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。
   }
}

在这里插入图片描述

13.4 字符流Reader、Writer

java.io.Reader和java.io.Writer及其子类用于处理字符流(Character Stream)。它们对流数据的操作是以一个字符(2字节)的长度为单位进行处理的,并且可以进行字符编码处理。
也就是说Reader、Writer及其子类可以用于纯文本文件的读/写、

13.4.1 字符读、写流

  1. 字符读流java.io.Reader和字符写流java.io.Writer支持Unicode标准字符集。在进行文本文件读/写时,一般使用Reader/Writer子类,子类通常会重新定义相关的方法。
  2. Reader&Writer是抽象类,他们只是提供了用于字符流处理的接口,不能生成实例,只能通过他们的子类对象处理字符流。

1.字符读流Reader抽象类
该类是处理所有字符流输入类的父类。
Reader的子类必须实现read(char[],int,int)和close()方法
在这里插入图片描述
2.字符写流Writer抽象类
该类是处理所有字符流输出的父类。
该类的子类必须实现write(char[],int,int),flush(),和close()方法。
在这里插入图片描述

13.4.2 字符输入、输出流

字符输入流InputStreamReader和字符输出流OutputStreamWriter分别是Reader、Writer子类,可以用InputStream、OutputStream类进行字符处理,即以字符为基本单位进行读/写操作。
类InputStreamReader、OutputStreamWriter实现字符流与字节流间的转换,字符流操作效率比字节流高。
想显示纯文本内容,则不用自己判断字符编码。直接操作InputStreamReader和OutputStreamWriter对象来读取文本。

【例8】ObjectOutputStream、ObjectInputStream类的方法演示
package org.example;

import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class IOStreamRWDemo {
    public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("D:\\LOLFolder\\lol2.txt");
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
            FileOutputStream fileOutputStream = new FileOutputStream("D:\\LOLFolder\\lol3.txt");
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
            int ch = 0;
            while (((ch = inputStreamReader.read())!=-1)){
                System.out.print((char) ch);
                outputStreamWriter.write(ch);
            }
            System.out.println();
            inputStreamReader.close();
            outputStreamWriter.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

  • InputStreamReader、OutputStreamWriter在流中存取是以系统默认编码进行字符转换的。也可以自己指定编码类型。

13.4.3 文件读、写字符流

  1. 存取文本文件还有更为便捷的方式——直接视图文件读字符类java.io.FileReader 和文件写字符类java.io.FileWriter。他们分别继承自InputStreamReader和OutputStreamWriter类,并且可以直接指定文件名称或File对象打开指定的文件。
  2. 字符转换根据系统默认的编码类型来实现,如果用户要指定编码,就需要使用InputStreamReader和OutputStreamWriter类。
  3. FileReader和FileWriter类的使用非常简单,仅需在创建对象的时候指定文件即可。
  4. Linux系统中板鞋的文本文件换行字符为‘\n’,而windows系统重编写的文本文件换行字符为“\r\n”
【例9】FileReader 、FileWriter类的方法演示

package org.example;

import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class IOStreamRWDemo {
    public static void main(String[] args) {
        try {
            FileReader fileReader = new FileReader("D:\\LOLFolder\\lol2.txt");
            FileWriter fileWriter = new FileWriter("D:\\LOLFolder\\lol3.txt");

            int in = 0;
            char[] wlnChar = {'\r','\n'};//声明并初始化字符数组
            while ((in = fileReader.read())!=-1){
                if (in =='\n'){
                    fileWriter.write(wlnChar);
                }else {
                    fileWriter.write(in);
                }
            }

            fileReader.close();
            fileWriter.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

13.4.4 字符缓冲区读、写流

  1. 字符缓冲读流java.io.BufferedReader 与字符缓冲区写流java.io.BufferedWriter的默认缓冲区大小为8192个字符。
  2. BufferedReader类在读取文本文件时,会先从文件读取字符到缓冲区,然后使用read()方法从缓冲区读取。如果缓冲区数据不足,则会再从文件中读取字符到缓冲区。
  3. 同样,BufferedWriter类在写入数据时,不直接写到目的地,而是先写入缓存区,如果缓冲区已满,则进行一次对目的地的写操作。因此,通过缓冲区域可以减少对磁盘的输入/输出操作。提高文件的读取效率。
【例10】BufferedReader 、BufferedWriter类的方法演示
package org.example;

import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class IOStreamRWDemo {
    public static void main(String[] args) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\LOLFolder\\lol2.txt"));
            String input = null;

            while (!(input = bufferedReader.readLine()).equals("quit")){
                bufferedWriter.write(input);
                bufferedWriter.newLine();//写入与操作系统相关的换行符


            }

            bufferedReader.close();
            bufferedWriter.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

在这里插入图片描述

13.5 拓展训练

13.5.1 训练一:按顺序创建文件

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

【例11】使用BufferedWriter类实现字符串的顺序写入
package example;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class OrderWriteFile {

   public static void main(String[] args) {
   	FileWriter fw ;
   	try {
   		fw = new FileWriter("D:\\LOLFolder\\lol2.txt");
   		BufferedWriter bf = new BufferedWriter(fw); //创建缓冲字符输出流对象
   		for(int i = 0 ; i < 10 ; i++) {
   			bf.write("Java"+i +"\n");
   		}
   		bf.close();
   	}
   	catch(IOException e){
   		e.printStackTrace();
   	}
   }
}

在这里插入图片描述

13.5.2 训练二:将一个大文件分割为多个小文件

【例12】in和out输入/输出

为了方便携带和传输(比如邮箱的邮件都有大小限制),可以将文件进行分割。

package org.example;

import java.io.*;

//  File f =new File("D:\\LOLFolder\\lol2.txt");

public class SplitFile {
    static final String SUFFIX = ".txt";//分割后的文件拓展名

        //将指定的文件按照给定的文件的字节数进行分割
        public static String[] divide(String name,long size)throws Exception{
            File file = new File(name);
            if (!file.exists()||(!file.isFile())){
                throw new Exception("指定文件不存在");
            }
            //获得被分割父文件,将来被分割成的小文件存放在这个目录下。
            File parentFile =file.getParentFile();
            long fileLength = file.length();
            if (size<=0){
                size = fileLength/2;
            }
            //获得被分割后的小文件数目
        int num = (fileLength%size != 0)?(int) (fileLength/size + 1):(int) (fileLength/size);
            String[] fileNames = new String[num];//存放被分割后的小文件名
            FileInputStream in = new FileInputStream(file);//输入文件流,即被分割的文件
            long end = 0; //输入文件流的开始和结束下标
            long begin = 0;
            for (int i = 0; i < num; i++) {  //根据要分割的数目输出文件
                //对于前num - 1 个小文件,大小都为指定的size
                File outFile = new File(parentFile,file.getName() + i +SUFFIX );
                FileOutputStream out = new FileOutputStream(outFile);//构建小文件的输出流
                end += size;//将结束下标后移size
                end = (end>fileLength)?fileLength:end;
                for (;  begin < end ; begin++) {
                    out.write(in.read());//从输入流中读取字节存储到输出流中
                }
                out.close();
                fileNames[i] = outFile.getAbsolutePath();
            }
            in.close();
            return fileNames;
        }

        public static void readFileMessage(String fileName){ //读出分割成的小文件中的内容
            File file = new File(fileName);
            BufferedReader reader = null ;
            try {
                reader = new BufferedReader(new FileReader(file));
                String string = null;
                //按行读取内容,直到读入null则表示读取文件结束
                while ((string = reader.readLine())!=null){
                    System.out.println(string);
                }
                reader.close();
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                if (reader!=null){
                    try {
                        reader.close();
                    }catch (IOException e1){

                    }
                }
            }

        }

        public static void main(final String[] args)throws Exception{
            String name = "D:\\LOLFolder\\lol2.txt";
            long size = 250;
            String[] fileNames = SplitFile.divide(name,size);
            System.out.println("文件"+name+"分割结果如下“");
            for (int i = 0;i<fileNames.length;i++){
                System.out.println(fileNames[i]+"内容如下");
                SplitFile.readFileMessage(fileNames[i]);
                System.out.println();
            }
        }


}

在这里插入图片描述

13.6 技术解惑

13.6.1 把InputStream转换成String的几种方法

在java中遇到如何把InputStream转换成String的情形。比如从文件或网络中得到一个InputStream,需要转换成字符串输出或赋予其他变量。
常用的方法就是按字节一次次读到缓冲区,或建立BufferedReader逐行读取。
在这里插入图片描述

13.6.2 读取大文件用哪个类合适

  1. 读取文件行的标准方式是在内存中读取。这种方法带来的问题,是文件的所有行都被放在内存中。当文件足够大时,会很快导致程序抛出OutOfMemoryError异常。
  2. 把文件所有的内容都放在内存中会很快耗尽可用内存,不论实际可用的内存有多大。
  3. 实际上,大多数情况下不需要把文件的所有行一次性的放入内存中。相反,只需要遍历文件的每一行,然后进行相应的处理,处理完后把它扔掉。
    使用java.util.Scanner 类扫描文件内容,可以一行一行的连续读取文件。
【例13】使用java.util.Scanner 类扫描文件内容
package org.example;

import java.io.*;
import java.util.Scanner;

//  File f =new File();

public class ReadBigFileDemo {
   public static void main(String[] args) {
       FileInputStream inputStream = null;
       Scanner sc = null;
       String path = "D:\\LOLFolder\\lol2.txt";

       try {
           inputStream = new FileInputStream(path) {
               @Override
               public int read() throws IOException {
                   return 0;
               }
           };
           sc = new Scanner(inputStream);
           while (sc.hasNextLine()){
               String line = sc.nextLine();
               System.out.println(line);
           }
           if (sc.ioException()!=null){
               throw sc.ioException();
           }
       }catch (IOException e){
           e.printStackTrace();
       }finally {
           if (inputStream!=null){
               try {
                   inputStream.close();
               }catch (IOException e){
                   e.printStackTrace();
               }
           }
           if (sc!=null){
               sc.close();
           }
       }

   }

}


http://www.kler.cn/a/386768.html

相关文章:

  • [CKS] K8S Dockerfile和yaml文件安全检测
  • Tomcat 和 Netty 的区别及应用场景分析
  • docker构建jdk11
  • JavaWeb后端开发知识储备1
  • vue2.7.14 + vant + vue cli脚手架转vite启动运行问题记录
  • Linux如何更优质调节系统性能
  • 信号与噪声分析——第二节:随机变量的统计特征
  • Pr:视频效果使用详解(全集 · 2025版)
  • flutter鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
  • 【物联网技术】ESP8266 WIFI模块在AP模式下实现UDP与电脑/手机网络助手通信——UDP数据透传
  • 【数字图像处理】一篇搞定傅里叶变换
  • Git 入门篇(二)
  • Centos7安装Redis 远程连接
  • 【LeetCode】【算法】206. 反转链表
  • nodejs 020: React语法规则 props和state
  • 采用macvlan绕过某些软件需要MAC授权的问题
  • Mac电脑中隐藏文件(即以 . 开头的文件/文件夹)的显示和隐藏的两种方法
  • javascript实现sha512和sha384算法(支持微信小程序),可分多次计算
  • Cesium着色器的创意和方法(五——Polyline)
  • opencv保姆级讲解-guI和pymsql实现人脸识别打卡(6)
  • 【WebRTC】视频编码链路中各个类的简单分析——VideoEncoder
  • C++20 概念与约束(2)—— 初识概念与约束
  • 三分钟学会Docker基本操作,快速入门容器技术!
  • 还在网盘?分享百兆级大文件传输工具--Wormhole:不限速在线文件传输下载利器
  • Java 类和对象
  • Spring Boot开发入门教程