Java - IO流

目录

文件专属

java.io.FileInputStream

java.io.FileOutputStream

java.io.FileReader

java.io.FileWriter

缓存流和数据流

数据流

java.io.DataInputStream

java.io.DataOutputStream

标准输出流

java.io.printWriter

java.io.printStream

对象专属流

序列化及反序列化

File类

IO + Properties 的联合应用


1、IO流,什么是IO?
    I :Input ,O :Output
    通过IO可以完成硬盘文件的读和写。


2、IO流的分类?

    有多种分类方式:
        一种方式是按照流的方向进行分类:

            以内存作为参照物,
                往内存中去,叫做输入(Input),或者叫做读(Read)。
                从内存中出来,叫做输出(Output),或者叫做写(Write)。

        另一种方式是按照读取数据方式不同进行分类:

            有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件、图片、声音文件、视频文件等.....
                假设文件file.txt,采用字节流的话是这样读的:
                    a中国ba张三fe
                    第一次读:一个字节,正好读到'a'
                    第二次读:一个字节,正好读到‘中’字符的一半。
                    第三次读:一个字节,正好读到‘中’字符的另外一半。

            有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取文本文件,连word文件都无法读取。
                 假设文件file.txt,采用字符流的话是这样读的:
                    a中国ba张三fe
                    第一次读:‘a’字符(‘a’字符在Windows系统中占用1个字节。)
                    第二次读:‘中’字符(‘中’字符在Windows系统中占用2个字节。)

【综上所述】:流的分类
        输入流、输出流、字节流、字符流

3、java中所有的流都是在:java.io.*;下。

    java主要研究:
        怎么new流对象。调用流对象的哪个方法是读,哪个方法是写。

4、IO流共有四大类:(这四个都是抽象类【abstract class】)
        java.io.InputStream   字节输入流
        java.io.OutputStream  字节输出流

        java.io.Reader        字符输入流
        java.io.Writer        字符输出流

        所有的流都实现了:
            java.io.Closeable接口,都是可以关闭的,都是close()方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会浪费(占用)很多资源。养成好习惯,用完流一定要关闭。

        所有的输出流都实现了:
            java.io.Flushable接口,都是可刷新的,都有flush()方法,养成一个好习惯,输出流在最终输出之后,一定要及得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出数据强行输出完(清空管道!)。刷新的作用就是清空管道。
            【注意】:如果没有flush()可能会导致丢失数据。

【注意】:在java中只要“类名”以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流。

5、java.io包下需要掌握的流有16个: 

文件专属

java.io.FileInputStream

1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
2、字节的方式,完成输入的操作,完成读的操作(硬盘 --> 内存)

FileInputStream fis = null;
try {
    fis = new FileInputStream("src/2.txt");

    // 开始读
    int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。
    System.out.println(readData);  // 97
    System.out.println(fis.read());  // 98
    System.out.println(fis.read());  // 99

    // 已经读取到文件的末尾了,再读的时候取不到任何数据,返回-1
    System.out.println(fis.read());  // -1
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 在finally语句块当中确保流一定关闭!!!
    if (fis != null) {  // 避免空指针异常!!!
        // 关闭流的前提是:流不是空。流是null的时候没必要关闭。
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

解析:上面2.txt文件,只包括abc这三个字母。

abc

        read();这个方法的返回值是:读取到的“字节”本身。读取到文件的末尾了,再读的时候取不到任何数据,返回-1。记住要关闭流,避免浪费资源。

读取数据时,可以使用while语句改进上面的程序

int readData = 0;
while ((readData = fis.read()) != -1) {
    System.out.println(readData);
}

分析上面程序存在的缺点
        一次读取一个字节byte,这样内存和硬盘交互太频繁。基本上时间/资源都耗费在交互上面了。可以的一次读取多个字节。

int read(byte[] b);   一次最多读取 b.length 个字节。
        减少硬盘和内存的交互,提高程序的执行效率。往byte[]数组当中读。

FileInputStream fis = null;
try {
    fis = new FileInputStream("src/2.txt");
    // 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。
    byte[] bytes = new byte[2];  // 准备一个2个长度的byte数组,一次最多读取2个字节。

    // 这个方法的返回值是:读取到的字节数量。(不是字节本身。)
    int readCount = fis.read(bytes);
    System.out.println(readCount);  // 2 第一次读到了2个字节。
    // 不应该全部转换,应该是读取了多少个字符,转换多少个。
    System.out.println(new String(bytes, 0, readCount)); // ab

    System.out.println(fis.read(bytes));  // 1
    System.out.println(new String(bytes, 0, readCount)); // c
    // 1个字节都没有读取到返回-1
    System.out.println(fis.read(bytes));  // -1
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

【注意】:将数组中字节转换的时候,不能new String(bytes)全部转换,应该是读取到多少字节转换多少。

 读取数据时,可以使用while语句改进上面的程序

int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
    System.out.print(new String(bytes, 0, readCount));
}

附加内存图

 FileInputStream类的其他常用方法:
        int available(); 返回流当前中剩余的没有读到的字节数量
        long skip(long n);  跳过几个字节不读。

java.io.FileOutputStream

文件字节输出流,负责写。从内存到硬盘。

FileOutputStream fos = null;
try {
    // 已追加的方式在文件末尾写入,不会清空文件内容。
    fos = new FileOutputStream("src/myfile", true);
    byte[] bytes = {97, 98, 99, 100};
    // 将byte数组全部写出!
    fos.write(bytes); // abcd
    // 将byte数组的一部分写出!
    fos.write(bytes, 0, 2); // ab

    String s = "我是一个中国人!";
    // 将字符串转换为byte数组
    byte[] bs = s.getBytes();
    fos.write(bs); // 写
    fos.flush();// 写完之后,最后一定要刷新!
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

也可以使用下面这种方式new FileOutputStream对象

FileOutputStream fos = new FileOutputStream("src/myfile");

但是不推荐使用,这种方式会先将原文件清空,然后重新输入!myfile文件不存在的时候会自动新建!

【注意】:写完之后,最后一定要刷新!

java.io.FileReader

文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷。

FileReader reader = null;
try {
    // 创建文件字符输入流
    reader = new FileReader("src/2.txt");
    char[] chars = new char[2];  // 一次读取2个字符
    int readCount = 0;
    while ((readCount = reader.read(chars)) != -1) {
        System.out.print(new String(chars, 0, readCount));
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

read(chars); 往char数组中读,按照字符的方式读取:第一次a,第二次b

java.io.FileWriter

 文件字符输出流,写。只能输出普通文本。

FileWriter out = null;
try {
    // 创建文件字符输出流对象       
    out = new FileWriter("src/myfile", true);
    out.write("我是好人!"); // 开始写        
    out.flush(); // 刷新
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (out != null) {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓存流和数据流

    转换流:(将字节流转换成字符流)
        java.io.InputStreamReader
        java.io.OutputStreamWriter

    缓冲流专属:
        java.io.BufferWriter

带缓冲的字符输出流

public class BufferedWriterTest {
    public static void main(String[] args) {
        BufferedWriter out = null;
        try {
            // 带缓冲区的字符输出流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("src/myfile")));
            // 开始写。
            out.write("hello world!");
            out.write('\n');
            out.write("hello kitty!");             
            out.flush(); // 刷新
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

        java.io.BufferReader

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

public class BufferedReaderTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream("src/2.txt")));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

解析:当一个流的构造方法需要一个流的时候,这个被传进来的流叫做:节点流
           外部负责包装的这个流,叫做:包装流,还有一个名字叫做,处理流。

对于上面代码来说,可以拆分为

FileInputStream fis = new FileInputStream("src/2.txt");         
InputStreamReader reader = new InputStreamReader(fis); // fis是节点流,reader是包装流。
br = new BufferedReader(reader);// reader是节点流,br是包装流。

fis是节点流,br是包装流,而reader是包装流也是节点流。

        java.io.BufferInputStream
        java.io.BufferOutputStream

数据流

java.io.DataInputStream

数据字节输入流。DataOutputStream写的文件,只能使用DataOutputStream去读,并且读的时候需要提前知道写入的顺序。读的顺序需要和写的顺序一致,才可以正常读取出数据。

public class DataInputStreamTest {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("src/data"));
            // 开始读
            byte b = dis.readByte();
            short s = dis.readShort();
            int i = dis.readInt();
            long l = dis.readLong();
            float f = dis.readFloat();
            boolean sex = dis.readBoolean();
            char c = dis.readChar();

            System.out.println(b);
            System.out.println(s);
            System.out.println(i);
            System.out.println(l);
            System.out.println(f);
            System.out.println(sex);
            System.out.println(c);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

java.io.DataOutputStream

 数据字节输出流,这个流可以将数据连同数据的类型一并写入文件。
【注意】:这个文件不是普通文本文件。(这个文件使用记事本打不开。)

public class DataOutputStreamTest {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("src/data"));
            // 写数据
            byte b = 100;
            short s = 200;
            int i = 300;
            long l = 400L;
            float f = 3.0F;
            boolean sex = false;
            char c = 'c';
            // 写
            dos.writeByte(b); // 把数据以及数据的类型一并写入到文件中。
            dos.writeShort(s);
            dos.writeInt(i);
            dos.writeLong(l);
            dos.writeFloat(f);
            dos.writeBoolean(sex);
            dos.writeChar(c);

            dos.flush(); // 刷新
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close(); // 关闭最外层
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

标准输出流

java.io.printWriter

java.io.printStream

标准的字节输出流,默认输出到控制台,可以改变标准输出流的输出方向!

System.out.println("hello world!");

拆开写

PrintStream ps = System.out;
ps.println("hello world!");

【注意】:标准输出流不需要手动close()关闭。

标准输出流不再指向控制台,指向“myfile”文件

PrintStream printStream = null;
try {
    printStream = new PrintStream(new FileOutputStream("sec/myfile"));
    // 修改输出方向,将输出方向修改到“myfile”文件。
    System.setOut(printStream);
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
// 输出
System.out.println("hello world!");

模拟日志工具

public class Logger {
    public static void log(String msg){
        PrintStream printStream = null;
        try {
            // 指向一个日志文件
            printStream = new PrintStream(new FileOutputStream("E:/Java/T/log.txt", true));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 改变输出方向
        System.setOut(printStream);
        // 日期当前时间
        Date nowDate = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(nowDate);
        System.out.println(strTime + " : " + msg);

    }
}

测试

Logger.log("调用了UserService的doSome()方法");

对象专属流

序列化及反序列化

1、java.io.NotSerializableException:对象未序列化!

2、参与序列化和反序列化的对象,必须实现Serializable接口。

3、注意:通过源代码发现,Serializable接口只是一个标志接口:

public interface Serializable {

}

这个接口当中什么代码都没有。那么它起什么作用呢?
        起到标志作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。Serializable这个标志接口是给Java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。

4、序列化版本号有什么作用呢?

        java语言中要是采用什么机制来区分类的?
                第一:首先通过类名进行比对的,如果类名不一样,肯定不是同一个类。
                第二:如果类名一样,接下来靠序列化版本号进行区分。

        不同的人编写了同一个类,但是“这两个类确实不是同一个类”。这时候序列化版本就起上作用了。对于java虚拟机来说,java虚拟机是可以区分这两个类的,因为这两个类都是实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样,所以区分开了。(这是自动生成序列化版本号的好处。)

        这种自动生成的序列化版本号有什么缺陷?
                 这种自动生成的序列化版本号的缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)

【结论】:
        凡是一个类实现了Serializable接口,建议给该类提供一个固定的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

public class Student implements Serializable {
    private static final long serialVersionUID = 1L; // java虚拟机识别一个类的时候先通过类名,如果类名一致,再通过序列化版本号。

    private int in;
    private String name;
    public Student() {
    }
    public Student(int in, String name) {
        this.in = in;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "in=" + in +
                ", name='" + name + '\'' +
                '}';
    }
}

java.io.ObjectInputStream

反序列化

public class ObjectInputStreamTest {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("src/myfile"));
            // 开始序列化,读
            Object obj = ois.readObject();
            // 反序列化回来是一个学生对象,所以会调用学生对象的toString()方法。
            System.out.println(obj);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

java.io.ObjectOutputStream

序列化

public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        // 创建java对象
        Student s = new Student(1111, "zhangsan");
        // 序列化
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("src/myfile"));
            oos.writeObject(s); // 序列化对象
            oos.flush(); // 刷新
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();// 关闭
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

一次序列化多个对象呢?可以,可以将对象放到集合当中,序列化集合

提示:参与序列化的ArrayList集合以及集合中的元素User都需要实现java.io.Serializable接口。

附加序列化及反序列化内存图

File类

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

2、File对象代表什么?
        文件的目录路径名的抽象表示形式。
        一个Fiel对象有可能对应的是目录,也有可能是文件。File只是一个路径名的抽象表示形式。

3、需要掌握File类中的常用的方法。
        boolean exists()  测试此抽象路径名表示的文件或目录是否存在。

File f1 = new File("E:\\Java\\J");
System.out.println(f1.exists()); // false

        boolean createNewFile()  有该名称的文件不存在时,创建一个由该抽象路径名命名的新的空文件。

f1.createNewFile();

        boolean mkdir()  创建由此抽象路径名命名的目录。

f1.mkdir();

        boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。(多重目录。)

File f2 = new File("E:/Java/a/b/c/d");
if (!f2.exists()) {
    f2.mkdirs();
}

        String getParent() 返回此抽象路径名的父(上一级)的路径名字符串,如果此路径名未命名为父目录,则返回null。

File f3 = new File("E:\\Java\\T\\2.txt");
System.out.println(f3.getParent()); // E:\Java\T

        File getParentFile() 返回此抽象路径名的父(上一级)或抽象路径名,如果此路径名没有指定父目录。

File parentFile = f3.getParentFile();
System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());  // 获取绝对路径:E:\Java\T

        String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串

File f4 = new File("src/myfile");
System.out.println("获取绝对路径:" + f4.getAbsolutePath()); // 获取绝对路径:E:\Java\IO流\src\myfile

        String getName() 返回由此抽象路径名表示的文件或目录的名称。

File f1 = new File("E:\\Java\\T\\2.txt");
System.out.println("文件名:" + f1.getName()); //文件名:2.txt

        boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。

System.out.println(f1.isDirectory()); // false

        boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。

System.out.println(f1.isFile()); // true

        long lastModified() 返回此抽象路径名表示的文件上次修改的时间。

long haoMiao = f1.lastModified();  // 这个毫秒是从1970年到现在的总毫秒数。
Date time = new Date(haoMiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println(strTime);

这个方法获取的是毫秒数,从1970年到现在的总毫秒数。

        long length() 返回由此抽象路径名表示的文件的长度。

System.out.println(f1.length()); 

返回的是文件的字节长度。

        File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。

File f = new File("E:\\Java\\example");
File[] files = f.listFiles();

IO + Properties 的联合应用

以后进程改变的数据,可以单独写到一个文件中,是程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启,就可以拿到动态的信息。类似于以上机制的这种文件被称为配置文件
        并且当配置文件中的内容格式是:
                key1=value
                key2=value
        的时候,我们把这种配置文件叫做属性配置文件

java规范中有要求:属性配置文件建议以.properties结尾,但是这不是必须的。这种以.properties结尾的文件在java中被称为:属性配置文件。其中Properties是专门存放属性配置文件内容的一个类。

创建userinfo.properties文件

username=admin

格式:建议key和value之间使用=的方式。在属性配置文件中井号(#)是注释。

测试

public class IoPropertiesTest {
    public static void main(String[] args) {
        // 新建一个输入流对象
        FileReader reader = null;
        try {
            reader = new FileReader("src/userinfo.properties");
            // 新建一个Map集合
            Properties pro = new Properties();
            pro.load(reader);
            // 通过key来获取value
            String username = pro.getProperty("username");
            System.out.println(username);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

【注意】:Properties是一个Map集合,key和value都是String类型。想将userinfo文件中的数据加载到Properties对象当中。

Properties对象的load方法将文件中的数据加载到Map集合中,其中等号=左边做key,右边做value。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值