Java学习笔记——IO流

2022-06-23 12:26:33 浏览数 (1)

文章目录
  • 1 File
  • 2 IO流
    • 2.1 Java IO流原理
    • 2.2 分类
  • 3 节点流(文件流)
    • 3.1 FileInputStream & FileOutputStream
    • 3.2 FileReader & FileWriter
  • 4 处理流
    • 4.1 缓冲流
    • 4.2 转换流
    • 4.3 标准输入输出流
    • 4.4* 打印流
    • 4.5* 数据流
    • 4.6 对象流
    • 4.7 RandomAccessFile
  • 5 总结
  • 6 疑问

凡是与输入、输出相关的类、接口等都定义在java.io包下。

1 File

文件和目录路径名的抽象表示形式,与平台无关。

File中的方法,仅涉及到如何创建、删除、重命名等等。只要涉及文件内容的,File是无能为力的,必须由io流来完成。

File类的对象常作为io流的具体类的构造器的形参。

常见方法:

2 IO流

2.1 Java IO流原理

水流、电流、数据流。

IO流用来处理设备之间的数据传输

Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行。

java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

  • 输入input 读取外部数据(磁盘、光盘等存储设备中的数据)到程序(内存)中。
  • 输出output 将程序(内存)数据写入到磁盘、光盘等存储设备中。

2.2 分类

  • 按操作数据单位不同分为:字节流 (以byte为单位,8 bit),字符流 (以char为单位,16 bit)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流
    • 节点流可以从一个特定的数据源读写数据。
    • 处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

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

(抽象基类)

字节流

字符流

输入流

InputStream

Reader

输出流

OutputStream

Writer

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

3 节点流(文件流)

写入一个文件时,如果目录下有同名文件将被覆盖。 在读取文件时,必须保证该文件已存在,否则出异常。

3.1 FileInputStream & FileOutputStream

  • FileInputStream
代码语言:javascript复制
// 1.创建一个File类的对象
File file = new File("hello.txt");
// 2.创建一个FileInputStream类的对象
FileInputStream fs = null;
try {
    fs = new FileInputStream(file);
    // 3.调用FileInputStream的方法,实现File文件的读写
    // 方式一:每次读一个字节
    int b;
    while ((b = fs.read()) != -1) {
        System.out.println((char)b);
    }
    // 方式二:每次读取一个字节数组(适当调节数组大小可使效率更高)
    byte[] b = new byte[5]; // 读取到的数据要写入的数组
    int len; // 每次读入到byte中的字节长度
    while((len = fs.read(b)) != -1) {
        // for (int i = 0; i < b.length; i  ) {
		//	System.out.println((char)b[i]);
		// }
        String str = new String(b, 0, len);
        System.out.print(str);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally { // 流的关闭操作一定可以执行
    // 4.关闭相应的流
    if (fs != null) {
        try {
            fs.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • FileOutputStream
代码语言:javascript复制
// 1.创建一个File对象,表明要写入的文件位置。
// 输出的物理文件可以不存在,当执行过程中,若文件不存在,会自动创建,若已存在,则新写入的会覆盖原有文件
File file = new File("hello2.txt");
// 2.创建一个FileOutputStream的对象,将file的对象作为形参传递给FileOutputStream的构造器中
FileOutputStream fos = null;
try {
    fos = new FileOutputStream(file);
    // 3.写入的操作
    fos.write(new String("I love you!").getBytes());
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}finally {
    // 4.关闭输出流
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 文件复制
代码语言:javascript复制
// 1.提供读入/写出的文件
File file1 = new File("hello1.txt");
File file2 = new File("hello2.txt");
// 2.提供相应的流
FileInputStream fis = null;
FileOutputStream fos = null;
try {
    fis = new FileInputStream(file1);
    fos = new FileOutputStream(file2);
    // 3.实现文件的复制
    byte[] b = new byte[20];
    int len;
    while((len = fis.read(b)) != -1) {
        // fos.write(b); fos.write(b, 0, b.length); // 两种错误的写法
        fos.write(b, 0, len);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}finally {
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

String Byte[]之间的转换:

代码语言:javascript复制
// String 转 Byte[]
String str = "hello";
Byte[] bytes = str.getBytes();

// Byte[] 转 String
String str1 = new String(bytes);

3.2 FileReader & FileWriter

使用FileReader、FileWriter可以实现文本文件的读写。

对于非文本文件(视频文件、音频文件、图片等),只能使用字节流!

代码语言:javascript复制
FileReader fr = null;
FileWriter fw = null;
try{
    //1.输入流对应的文件src一定要存在,否则抛异常。输出流对应的文件dest可以不存在,执行过程中会自动创建
    File src = new File("dbcp.txt");
    File dest = new File("dbcp1.txt");
    //2.
    fr = new FileReader(src);
    fw = new FileWriter(dest);
    //3.
    char[] c = new char[24]; // 这里是与字节流的区别之处
    int len;
    while((len = fr.read(c)) != -1){
        fw.write(c, 0, len);
    }
}catch(Exception e){
    e.printStackTrace();
}finally{
    if(fw != null){
        try {
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(fr != null){
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4 处理流

4.1 缓冲流

为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组。

缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。

对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出。

  • 字节流
代码语言:javascript复制
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
    //1.提供读入、写出的文件
    File file1 = new File("1.jpg");
    File file2 = new File("2.jpg");
    //2.创建相应的节点流:FileInputStream、FileOutputStream
    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    //3.将创建的节点流的对象作为形参传递给缓冲流的构造器中
    bis = new BufferedInputStream(fis);
    bos = new BufferedOutputStream(fos);
    //4.具体的实现文件复制的操作
    byte[] b = new byte[1024];
    int len;
    while((len = bis.read(b)) != -1){
        bos.write(b, 0, len);
    }
    bos.flush(); // 刷新缓冲区
}catch (IOException e) {
    e.printStackTrace();
}finally{
    //5.关闭相应的流
    if(bos != null){
        try {
            bos.close(); // //关闭过滤流时,会自动关闭它所包装的底层节点流
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    if(bis != null){
        try {
            bis.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}
  • 字符流
代码语言:javascript复制
BufferedReader br = null;
BufferedWriter bw = null;
try {
    File file = new File("dbcp.txt");
    File file1 = new File("dbcp3.txt");
    FileReader fr = new FileReader(file);
    FileWriter fw = new FileWriter(file1);
    br = new BufferedReader(fr);
    bw = new BufferedWriter(fw);

    // 方式一:
    char[] c = new char[1024];
    int len;
    while((len = br.read(c))!= -1){
        String str = new String(c, 0, len);
        System.out.print(str);
    }
    // 方式二:
    String str;
    while((str = br.readLine()) != null){
        bw.write(str); // 一次写入一行字符串
        bw.newLine(); // 写入行分隔符
    }
    bw.flush();
}catch (IOException e) {
    e.printStackTrace();
}finally{
    if(bw != null){
        try {
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(br != null){
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

总结:只是将节点流或其他处理流作为缓冲流构造器的参数,创建出一个缓冲流,其他操作与一般流类似,只不过在写出时多了flush操作,并且其对应的字符流还多了一个readLine()方法以读取一行字符,可使用newLine()换行。

4.2 转换流

转换流提供了在字节流和字符流之间的转换。

字节流中的数据都是字符时,转成字符流操作更高效。

  • InputStreamReader 用于将字节流中读取到的字节按指定字符集解码成字符。需要和InputStream“套接”。 public InputStreamReader(InputStream in) public InputSreamReader(InputStream in,String charsetName)
  • OutputStreamWriter 用于将要写入到字节流中的字符按指定字符集编码成字节。需要和OutputStream“套接”。 public OutputStreamWriter(OutputStream out) public OutputSreamWriter(OutputStream out,String charsetName)
代码语言:javascript复制
BufferedReader br = null;
BufferedWriter bw = null;
try {
    //解码
    File file = new File("dbcp.txt");
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis, "GBK");
    br = new BufferedReader(isr);
    //编码
    File file1 = new File("dbcp4.txt");
    FileOutputStream fos = new FileOutputStream(file1);
    OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
    bw = new BufferedWriter(osw);
    String str;
    while((str = br.readLine()) != null){
        bw.write(str);
        bw.newLine();
        bw.flush();
    }
}catch (IOException e) {
    e.printStackTrace();
}finally{
    if(bw != null){
        try {
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(br != null){
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

总结:使用InputStreamReader相当于从一个字节流读取字符,构造好之后可以当成节点流中的字符流使用,也可以当做BufferedReader构造器的参数再嵌套一层,变成效率更高的缓冲流;使用OutputStreamWriter,相当于往字节流中写入字符流,使用同理。

4.3 标准输入输出流

标准的输出流:System.out (类型是InputStream)

标准的输入流:System.in (类型是PrintStream,是OutputStream的子类FilterOutputStream 的子类)

默认输入设备是键盘,输出设备是显示器。

通过System类的setIn,setOut方法对默认设备进行改变:

public static void setIn(InputStream in) public static void setOut(PrintStream out)

代码语言:javascript复制
// 以下省略异常处理
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
// 读取用户输入的一行数据 --> 阻塞程序
String str = br.readLine();
System.out.println(str);

br.close();

总结:标准的输入输出流是事先定义好的,默认输入设备是键盘,输出设备是显示器,可通过System类的setIn,setOut方法对默认设备进行改变,之后对其进行的读写操作与一般流同理。

4.4* 打印流

  • 在整个IO包中,打印流是输出信息最方便的类。
  • PrintStream(字节打印流)和PrintWriter(字符打印流)
    • 提供了一系列重载的print和println方法,用于多种数据类型的输出
    • PrintStream和PrintWriter的输出不会抛出异常
    • PrintStream和PrintWriter有自动flush功能
    • System.out返回的是PrintStream的实例
代码语言:javascript复制
FileOutputStream fos = null;
try {
    fos = new FileOutputStream(new File("print.txt"));
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 'n' 时都会刷新输出缓冲区)
PrintStream ps = new PrintStream(fos, true);
if (ps != null) { // 把标准输出流(控制台输出)改成文件
    System.setOut(ps);
}
for (int i = 0; i <= 255; i  ) { // 输出ASCII字符
    System.out.print((char) i);
    if (i % 50 == 0) { // 每50个数据一行
        System.out.println(); // 换行
    }
}
ps.close();

4.5* 数据流

为了方便地操作Java语言的基本数据类型的数据,可以使用数据流。

可以对八种基本数据类型、String、字节数组进行读写操作。

  • DataInputStream中的主要方法 ​ boolean readBoolean() byte readByte() ​ char readChar() float readFloat() ​ double readDouble() short readShort() ​ long readLong() int readInt() ​ String readUTF() void readFully(byte[] b)
  • DataOutputStream中的将上述的方法的read改为相应的write即可。
代码语言:javascript复制
DataOutputStream dos = DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("啦啦啦你好啊hh");  // 写UTF字符串
dos.writeBoolean(false);  // 写入布尔值
dos.writeLong(1234567890L);  // 写入长整数

dos.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流

4.6 对象流

ObjectInputStream和OjbectOutputSteam,用于存储和读取对象的处理流。

  • 序列化(Serialize):用ObjectOutputStream类将一个Java对象写入IO流中
  • 反序列化(Deserialize):用ObjectInputStream类从IO流中恢复该Java对象
  • ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。

如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该和所有类属性所属的类必须实现如下两个接口之一:

  • Serializable
  • Externalizable

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

代码语言:javascript复制
private static final long serialVersionUID;

serialVersionUID用来表明类的不同版本间的兼容性。

如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的源代码作了修改,serialVersionUID 可能发生变化。故建议,显示声明。

显示定义serialVersionUID的用途:

  • 希望类的不同版本对序列化兼容,因此需确保类的不同版本具有相同的serialVersionUID
  • 不希望类的不同版本对序列化兼容,因此需确保类的不同版本具有不同的serialVersionUID
代码语言:javascript复制
/*
 * 要实现序列化的类: 
 * 1.要求此类是可序列化的:实现Serializable接口
 * 2.要求类的属性同样的要实现Serializable接口
 * 3.提供一个版本号:private static final long serialVersionUID
 * 4.使用static或transient修饰的属性,不可实现序列化
 */
class Person implements Serializable {
	private static final long serialVersionUID = 23425124521L;
	static String name;
	transient Integer age;
	Pet pet;
	public Person(String name, Integer age, Pet pet) {
		this.name = name;
		this.age = age;
		this.pet = pet;
	}
	@Override
	public String toString() {
		return "Person [name="   name   ", age="   age   ", pet="   pet   "]";
	}
}
class Pet implements Serializable{
	String name;
	public Pet(String name){
		this.name = name;
	}
	@Override
	public String toString() {
		return "Pet [name="   name   "]";
	}
}
代码语言:javascript复制
// 对象的序列化过程:将内存中的对象通过ObjectOutputStream转换为二进制流,存储在硬盘文件中
@Test
public void testObjectOutputStream() {
    Person p1 = new Person("小米", 23,new Pet("花花"));
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream("person.txt")); // 创建一个 ObjectOutputStream
        oos.writeObject(p1); // 输出可序列化对象
        oos.flush(); // 写出一次,操作flush()
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (oos != null) {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
代码语言:javascript复制
// 对象的反序列化过程:将硬盘中的文件通过ObjectInputStream转换为相应的对象
@Test
public void testObjectInputStream() {
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("person.txt")); // 创建一个 ObjectInputStream
        Person p1 = (Person)ois.readObject(); // 读取流中的对象
        System.out.println(p1);
    }catch (Exception e) {
        e.printStackTrace();
    }finally{
        if(ois != null){
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

总结:作为数据流的补充,对象流可以用于存储和读取对象,可操作更复杂的数据类型。

4.7 RandomAccessFile

RandomAccessFile支持随机访问文件。

  • 既可以充当一个输入流,又可以充当一个输出流
  • 支持从任意位置的读取、写入(插入)

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:

  • r:以只读方式打开
  • rw:打开以便读取和写入
  • rwd:打开以便读取和写入;同步文件内容的更新
  • rws:打开以便读取和写入;同步文件内容和元数据的更新
代码语言:javascript复制
// 实现向文件插入字符的效果
RandomAccessFile raf = null;
try {
    raf = new RandomAccessFile(new File("hello1.txt"),"rw");
    // 先把插入位置之后的内容读出来
    raf.seek(4);
    byte[] b = new byte[10];
    int len;
    StringBuffer sb = new StringBuffer();
    while((len = raf.read(b)) != -1){
        sb.append(new String(b,0,len));
    }
    // 在插入位置插入字符
    raf.seek(4);
    raf.write("xy".getBytes());
    // 把原来的内容加回去
    raf.write(sb.toString().getBytes());
}catch (IOException e) {
    e.printStackTrace();
}finally{
    if(raf != null){
        try {
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5 总结

最基本的几个IO流是节点流(File…),其他处理流都是在这几个流的基础上进行进一步包装,处理流的构造函数的参数基本都是节点流。使用缓冲流可以加快读写速度。

IO流类的继承关系:

  • java.io.InputStream
    • java.io.FileInputStream
    • java.io.FilterInputStream
      • java.io.BufferedInputStream
      • java.io.DataInputStream
    • java.io.ObjectInputStream
  • java.io.OutputStream
    • java.io.FileOutputStream
    • java.io.FilterOutputStream
      • java.io.BufferedOutputStream
      • java.io.DataOutputStream
      • java.io.PrintStream
    • java.io.ObjectOutputStream
  • java.io.Reader
    • java.io.BufferedReader
    • java.io.InputStreamReader
      • java.io.FileReader
  • java.io.Writer
    • java.io.BufferedWriter
    • java.io.OutputStreamWriter
      • java.io.FileWriter
    • java.io.PrintWriter
  • java.io.RandomAccessFile

按功能分类:

  • 字节流-缓冲流(重点)
    • 输入流InputStream-FileInputStream-BufferedInputStream
    • 输出流OutputStream-FileOutputStream-BufferedOutputStream
  • 字符流-缓冲流(重点)
    • 输入流Reader-FileReader-BufferedReader
    • 输出流Writer-FileWriter-BufferedWriter
  • 转换流 InputSteamReader和OutputStreamWriter
  • 对象流ObjectInputStream和ObjectOutputStream(难点)
    • 序列化
    • 反序列化
  • 随机存取流RandomAccessFile(掌握读取、写入)

6 疑问

为什么先关输出流,再关输入流?

缓冲流的flush是每次写入都要调用,还是最后一次写入之后调用即可?

以上笔记参考自尚硅谷

0 人点赞