1、IO原理

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。

如读/写文件,网络通讯等。 Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。 java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

通过IO可以完成硬盘文件的读和写:

输入Input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

输出Output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2、流的分类

1,按操作数据单位不同分为:

        字节流(8 bit):一次读取一个字节,什么类型的文件都能读取。

                        包括:文本文件,图片,声音,视频等文件。

        字符流(16 bit):一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,

                        这种流不能读取:图片,声音,视频等文件。只能读取纯文本文件,

                        连word文件都无法读取。

2,按数据流的流向不同分为:输入流,输出流

3,按流的角色的不同分为:节点流,处理流(抽象基类) 字节流 字符流等

Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的

(抽象基类)

字节流

字符流

输入流

java.io.InputStream

java.io.Reader

输出流

java.io.OutputStream

java.io.Writer

由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

注意:

1,在Java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。

2,所有流都实现了:java.io.Closeable接口,都是可以关闭的,都有close方法。

3,所有输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush方法。

4,(1)一般情况下,在使用流后都要进行关闭,减少资源的损耗。

     (2)输出流在最终输出之后,一定要记得flush(),刷新一下,

                这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道)

 3,需要掌握的流:

文件专属:

        java.io.FileInputStream

        java.io.FileOutputStream

        java.io.FileReader

        java.io.FileWriter

转换流:(将字节流转换成字符流)

           java.io.InputStreamReader 

           java.io.OutputStreamReader 

 缓冲流专属:

        java.io.BufferedReader

        java.io.BufferedWriter

        java.io.BufferedInputStream

        java.io.BufferedOutputStream

数据流专属:

        java.io.DataInputStream

        java.io.DataOutputStream

标准输出流:

        java.io.PrintWriter

        java.io.PrintStream

对象专属流:

        java.io.ObjectInputStream

        java.io.ObjectOutputStream

代码介绍:

java.io.FileInputStream:

注意:它的read方法的使用:具体看代码 。它的执行流程:

java中io流写文件内容 java文件io流原理_字节流

public class IO_Test01 {
    public static void main(String[] args) {

        //使用成员变量,方便使用close方法进行关闭流
        FileInputStream fis= null;
        //因为FileInputStream类抛出了 FileNotFoundException(编译时异常)
        //需要处理异常
        try {
            //创建文件字节流输入对象,使用绝对路径
            fis = new FileInputStream("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            
            while (true) {
                // read()方法需要处理IOException异常
                //这个方法返回的是字节的ASCLL码值
                int read = fis.read();

                if (read==-1)break;
                    System.out.print(read+" ");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {//避免空指针异常
                //处理close方法抛出的编译异常IOException
                try {
                    //关闭流
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
结果:
87 101 108 99 111 109 32 116 111 32 73 79

使用byte数组: 

//创建文件字节流输入对象,使用绝对路径
 fis = new FileInputStream("C:\\Users\\cxvqq\\Desktop\\实验.txt");
 //开始读,采用byte数组,一次读取多个字节。最多读取”数组.length“个字节。
 byte[] bytes = new byte[4];//准备一个byte数组

   while (true){
     //这个方法返回的是读取字符的数量
     int read1 = fis.read(bytes);
     if (read1==-1)break;
         //将len长度的字节数组装成字符串,然后打印
         System.out.println(new String(bytes,0,4));//最多返回数组的长度4
    }
结果:
Welc
om t
o IO

java.io.FileOutputStream:

注意:

在这里writer方法参数只能有:int ,boolean,byte数组三种数据类型。

byte数组可以按规定的字符数量输出,和上面输入是差不多的。

如果使用的是  write(byte b[], int off, int len)  这种方式写入,会把原有的数据覆盖

public class IO_Test03 {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        FileInputStream fis=null;
        try {
            fos = new FileOutputStream("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            //写入一个byte数组
            byte[] bytes={'w','e','l','c','o','m'};
            fos.write(bytes);
            //检查写入
            fis =new FileInputStream("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            byte[] bytes1 = new byte[4];
            while (true){
                int read = fis.read(bytes1);
                if (read==-1)break;
                //一般读入多少,输出多少
                System.out.println(new  String(bytes1,0,4));
               // System.out.println(new String(bytes1));//这样效率不高
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
结果:
welc
omlc

注意以上代码重复输出了“lc”,为什么?: 如下图

java中io流写文件内容 java文件io流原理_字符流_02

 java.io.FileReader, java.io.FileWriter

几乎和上面一样,但是注意:

字符流只能传输普通文本文档

写入时只能是int,String ,char数组类型,

St'ring和char数组可以按规定的字符数量输出,和上面输入是差不多的。

public class IO_Test04 {
    public static void main(String[] args) {

        FileWriter fiw=null;
        FileReader fir= null;
        try {
            fiw = new FileWriter("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            //写入一个字符数组
            char[] chars={'w','e','l','c','o','m'};
            fiw.write(chars);
            fiw.flush();
            //检查是否写入
            fir = new FileReader("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            //准备一个char数组
            char[] chars1 = new char[4];
            while (true){
                int read = fir.read(chars1);
                if (read==-1)break;
                System.out.println(new String(chars1,0,4));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fiw.close();
                fir.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
结果:
welc
omlc

文件的复制:

以字节流为例:字符流是差不多的。但是要注意字符流只能传输普通文本文档。

//文件的复制
public class IO_Test05 {
    public static void main(String[] args) {
        FileInputStream fir = null;
        FileOutputStream fos=null;
        try {
            //获取一个文件字节输入流对象
            fir = new FileInputStream("C:\\Users\\cxvqq\\Desktop\\新建 文本文档.txt");
            //获取一个文件字节输出流对象
            fos=new FileOutputStream("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            byte[] bytes = new byte[1024 * 1024];//以一次1M的的方式读和写
            int count=0;
            while ((count=fir.read(bytes))!=-1){
                fos.write(bytes,0,count);//边读边写
                fos.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fir.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  java.io.BufferedReader:

     

缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须先要有流对象。

为了提高字符写入/出流的效率,加入的缓冲技术,在对象里面封装了数组,只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可,记住只要用到缓冲区就要刷新。关闭缓冲区,其实关闭缓冲区就是在关闭缓冲区中的流对象。

BufferedReader里面的对象都是字符流,要使字节流变得高效,需要使用转换流。

或者使用: java.io.BufferedInputStream


BufferedReader: 带有缓冲区的字符输入流 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。

public BufferedReader(Reader in) : 构造方法中Reader是一个抽象类,FileReader继承了该抽象类

BufferedReader类当中的常用方法: String readLine() 读一行文字。 当没有读取到任何文字的时候返回null

BufferedWriter可以以此类推。

注意:

当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流

外部负责包装的这个流,叫做:包装流,或者处理流

        代码示例: 

public class IO_Test06 {
    public static void main(String[] args) {
        try {
            FileReader reader = new FileReader("C:\\Users\\cxvqq\\Desktop\\实验.txt");
            BufferedReader bufferedReader = new BufferedReader(reader);
            String s=null;
            while ((s=bufferedReader.readLine())!=null){
                System.out.println(s);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class IO_Test07 {
    public static void main(String[] args) {
        // 字节流
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("UserFile");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 通过转换流转换(InputStreamReader将字节流转换成字符流)
        // fis是节点流 reader 是包装流
        InputStreamReader reader = new InputStreamReader(fis);

        // 这里的BufferedReader类当中的构造方法只能传一个字符流,不能传字节流
        /*
        构造方法当中Reader是一个抽象类 InputStreamReader继承了该抽象类
        所以可以把字节流先转换成字符流 然后该字符流继承了Reader抽象类 利用多态 能够传进去
         */
        BufferedReader br = new BufferedReader(reader);

    }
}

   java.io.PrintWriter, java.io.PrintStream

标准字符流输出,标准字节流输出,都是输出流,它们是不需要手动close()关闭的

public class IO_Test08 {
    public static void main(String[] args) {
        // 联合起来写
        System.out.println("hello world");  // 将"hello world" 输出到控制台
        // 分开写
        PrintStream ps =System.out; // System.out 返回的是一个PrintStream
        ps.println("junker");
        ps.println("kitty");
        // 标准输出流不需要手动close()关闭
        // 我们可以改变标准输出流的输出方向吗? 可以
        // 标准输出流不再指向控制台,指向了“psFile”文件
        PrintStream printStream = null;
        try {
            printStream = new PrintStream(new FileOutputStream("psFile"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 修改输出方向,将输出方向修改到“psFile”文件
        System.setOut(printStream);
        // 再输出
        System.out.println("hello world");
        System.out.println("hello kitty");
    }
}
结果:
hello world
junker
kitty

 可以利用该标准输出流写日志

/*
    日志工具
     */
public class IO_Test09 {
        // 创建一个记录日志的方法
        public static void log(String msg){ // msg代表日志信息    // 此处可以写成Object类型
            try {
                // 指向一个日志文件
                PrintStream printStream =new PrintStream(new FileOutputStream("msgFile",true)); // 追加[不会清空内容]
                // 改变输出方向
                System.setOut(printStream);

                // 日期当前时间
                Date date =new Date();
                // 格式化时间
                SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH-mm-ss SSS");
                String strTime =simpleDateFormat.format(date);

                // 输出
                System.out.println(strTime +":" +msg);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }


class RiZhiTest {
    public static void main(String[] args) {
        // 测试日志
        IO_Test09.log("调用了Longer的方法");
        IO_Test09.log("登录了系统注册功能");
        IO_Test09.log("修改了密码");
        IO_Test09.log("注册账户失败了");
    }
}

以下是用PrintWriter 输出到 指定文件(和上面差不多,使用的构造方法不同)

/**
 * java.io.PrintWriter
 * 具有自动行
 *
 */
public class IO_Test11 {

    public static void main(String[] args) throws IOException {
/**
 * PW支持两个直接对文件写操作的构造方法:
 * PrintWriter(File f)传文件名
 * PrintWriter(String s)传路径
 * PrintWriter给人一种可以直接对文件进行操作的假象
 * PW是一个高级流
 * 实际上PW包装了字节流、字符流和字符缓冲流。
 * PW负责自动行刷新
 * bw负责提高效率
 * osw负责读字符
 * fos负责写字节
 * 最后PW能够按行很快地把一个字符串变成字节写在文件中
 */
        PrintWriter pw = new PrintWriter("pw.txt");

        pw.println("飞雪连天射白鹿");
        pw.println("金庸小说我都爱看");
        System.out.println("写出完毕!");
        pw.close();
    }
}
public class IO_Test12 {

    public static void main(String[] args) throws IOException {
/**
 * 使用流连接形式向文件中写出字符串
 */
//    FileOutputStream fos = new FileOutputStream("pw2.txt");
//
//    OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
//
//    BufferedWriter bw = new BufferedWriter(osw);
//
//    PrintWriter pw = new PrintWriter(bw);

        PrintWriter pw = new PrintWriter(
                new BufferedWriter(
                         new OutputStreamWriter(
                                new FileOutputStream("pw2.txt")
                                , "UTF-8")
                )
        );

        pw.println("你好");
        pw.println("我不好");
        pw.close();

    }
}

注意:如果没有文件是会直接在包路径(IO_Test)的下面创建的, 

java中io流写文件内容 java文件io流原理_字节流_03

public class IO_Test13 {
   /** 缓冲字符流中所有构造方法中第一个参数都是Reader或Writer对象。

    代码展示如下所示:
    PrintWriter创建时如果有参数true时,会有行刷新。PrintWriter调用println()方法有行刷新。

    当创建PW时第一个参数为一个流时,
            * 那么久可以再传入一个boolean值类型的参数,
            * 若该值为true,那么当前PW久具有自动行刷新的功能,
            * 即:每当使用println方法写出一行字符串后就会自动调用flush
        * 注:使用自动行刷新可以提高写出数据的即时性,
            * 但是由于会提高写出次数,必然会导致写效率降低。
    */
   public static void main(String[] args) throws IOException, FileNotFoundException{
       Scanner reader = new Scanner(System.in);
       System.out.println("请输入文件名:");
       String filename = reader.nextLine();

       PrintWriter pw = new PrintWriter(
               new BufferedWriter(
                       new OutputStreamWriter(
                               new FileOutputStream(filename),"UTF-8"
                       )
               ),true
       );
       System.out.println("请开始输入内容");
       while(true)
       {
           String line = reader.nextLine();
/**
 * 当创建PW时第一个参数为一个流时,
 * 那么久可以再传入一个boolean值类型的参数,
 * 若该值为true,那么当前PW久具有自动行刷新的功能,
 * 即:每当使用println方法写出一行字符串后就会自动调用flush
 * 注:使用自动行刷新可以提高写出数据的即时性,
 * 但是由于会提高写出次数,必然会导致写效率降低。
 */
           if("exit".equals(line))
           {
               break;
           }
//pw.write(line);
           pw.println(line);
//pw.flush();
       }
       pw.close();
       System.out.println("谢谢输入");
   }
}

File类:

1、File类和四大家族没有关系,所以File类不能完成文件的读和写。

        它属于:java.lo.File包下。
2、File对象代表什么?
    文件和目录路径名的抽象表示形式
    C:\Drivers这是一个File对象
    C: \Drivers\Lan\Realtek\Readme.txt也是File对象。
一个File对象有可能对应的是目录(文件夹),也可能是文件。
File只是一个路径名的抽象表示形式。
3、需要掌握File类中常用的方法

public class IO_Test10 {
    public static void main(String[] args) {
        File file = new File("D:\\file");
        //第一种   Boolean exists();
        // 判断指定的位置文件是否存在
        System.out.println(file.exists());
        // 第二种:如果C:/file不存在,则以文件的形式创建出来(不是文件夹)
        // if(!file.exists()){
        //     file.createNewFile();
        // }
        // 第三种:如果C:/file不存在,则以目录(文件夹)的形式创建出来
        if (!file.exists()){
            file.mkdir();
        }

        //  第四种:可以创建多重目录(文件夹)吗 ? 比如a\b\c\d\e
        File file1 =new File("D:\\a\\b\\c\\d\\e");
        // 如果该文件不存在,那么就多重创建
        if (!file1.exists()){
            // 多重目录的形式新建
            file1.mkdirs();
        }

        //  第五种:获取文件的父路径(两种获取方式)
        File file2 =new File("D:\\java course\\day01\\HelloWorld.java");
        String s =file2.getParent();
        System.out.println(s);  // D:\java course\day01

        File f =file2.getParentFile();
        System.out.println(f);  // D:\java course\day01

        // 第六种:获取绝对路径
        File f1 =file2.getAbsoluteFile();
        System.out.println(f1); // D:\java course\day01\HelloWorld.java
    }
}
package com.bjpowernode.java.io;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
File类常用方法
 */
 
public class FileTest02 {
    public static void main(String[] args) {
        File file =new File("D:\\java course\\renSheng\\House.java");
 
        // 获取文件名
        System.out.println(file.getName()); // House.java
 
        // 判断是否是一个目录(文件夹)
        System.out.println(file.isDirectory()); // false
 
        // 判断是否是一个文件
        System.out.println(file.isFile());  // true
 
        // 获取文件最后一次修改的时间
        long HaoMiao =file.lastModified();  // 这个毫秒是从1970年到现在的总毫秒数
        // 如何将毫秒转化成日期?
        Date date =new Date(HaoMiao);
        // 初始化日期
        SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String s =simpleDateFormat.format(date);
        System.out.println(s);  // 2022-02-22 17:17:43 783
 
        // 获取文件大小
        System.out.println(file.length());  // 93   93个字节
 
    }
}

  File中的listFiles方法,获取当前目录的子文件。

package com.bjpowernode.java.io;
import java.io.File;
 
// File中的listFiles方法
public class FileTest03 {
    public static void main(String[] args) {
        // File[] listFiles()方法
        // 获取当前目录(文件夹)下所有的子文件
        File file =new File("D:\\java course\\renSheng");
        File[] files=file.listFiles();
 
        // 对数组循环遍历
        for (File data : files){
            // 拿到绝对路径
            System.out.println(data.getAbsolutePath());
            // System.out.println(data.getName());  // 拿到名字
        }
    }
}

IO+Properties的联合使用:

 IO流:文件的读和写。

Properties:是一个Map集合,key和value都是String类型

public class IO_Properties {
    public static void main(String[] args) throws IOException {
        //新建一个输入流对象
        FileReader reader = new FileReader("userinfo");
        //新建一个Map集合
        Properties properties = new Properties();
        //调用Properties对象的load方法将文件的数据加载到Map集合中
        //文件=左边的做key,右边的做value
        properties.load(reader);
        //通过key获取value
        String username = properties.getProperty("username");
        System.out.println(username);
    }
}

 配置文件机制:

从IO+Properties的联合使用的例子,可以衍生出一个非常好的设计理念--配置文件

配置文件机制:

把经常改变的数据,可以单独放在一个文件中,使用程序动态读取。

将来只需要修改这个文件的内容,Java代码不需要改变,

不需要更新编译,服务器也不需要重启。就可以拿到动态的信息

当配置文件的内容格式为:

key1=value

key2=value

的时候,我们把这种配置文件叫做属性配置文件。


Java中规范要求:属性配置文件以“.properties”结尾,但不是必须的

这种以“.properties”结尾的文件在Java中被称为:属性配置文件。


其中Properties对象是专门存放属性配置文件内容的一个类。


注意:

1,如果key重复的话,value会被覆盖

2,=号左右两边最好不要有空格

3,key和value也可以用“:”连接的,但是一般不推荐使用这种方式。