Java教程

Java常用类库之IO

本文主要是介绍Java常用类库之IO,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

IO流

1. 概述

数据传输操作,可以看作一种数据的流动,按照流动的方向分为输入Input和输出Output
Java中的IO操作主要指的是java.io下的一些常用类的使用,通过这些类对数据进行读取(输入Input)和写入(输出Output)

2. 分类

按照流的方向分类:输入流和输出流

按照流动的数据类型,可以分为:字节流和字符流

字节流:

  • 输入流: InputStream()
  • 输出流: OutputStream()

字符流:

  • 输入流: Reader
  • 输出流: Writer

3. 字节流

一切皆字节,计算机中的任何数据,文本,图片,视频等都是以二进制存储的,8个二进制位为一个字节。在数据传输时也都是以二进制的形式存储的。

任何流在传输时底层都是二进制

3.1 OutputStream

close()关闭此输出流并释放与此流关联的所有系统资源,一定要关闭

flush()刷新此输出流并强制写出任何缓冲的输出字节

nullOutputStream()返回一个新的输出流,丢弃所有字节

write(byte[] b)将字节数组写入输出流

write(byte[] b, int off, int len)将从偏移量off开始的指定字节数组中的len长度写入此输入法

write(int b)将指定的字节写入此输出流(虽然传入的是int,但写入的是低8位(范围为0~255))

3.1.1 FileOutputStream(文件输出流)

构造器描述
FileOutputStream​(File file)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream​(FileDescriptor fdObj)创建要写入指定文件描述符的文件输出流,该文件描述符表示与文件系统中实际文件的现有连接。
FileOutputStream​(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。append为true指的是在原文件末尾加入,为false指的是删去原文,重新写入
FileOutputStream​(String name)创建文件输出流以写入具有指定名称的文件。
FileOutputStream​(String name, boolean append)创建文件输出流以写入具有指定名称的文件。append为true指的是在原文件末尾加入,为false指的是删去原文,重新写入
//写入的对象为fos.txt,如果计算机中没有该文件则创建该文件
FileOutputStream fos = new FileOutputStream("G:/test/fos.txt");
//写入65,即'A'
fos.write(65);
//关闭输出流,且关闭后无法再写入
fos.close();
System.out.println("已经写入");
//成功在fos.txt中写入A

此时重新创建该流,并写入字节数组。由于上面的构造函数没有指定append为true,重新创建输出流会覆盖原文件的内容,运行后文件内容为"ABCDEF"。

FileOutputStream fos = new FileOutputStream("G:/test/fos.txt");
byte[] bytes = {65, 66, 67, 68, 69, 70};
//也可以采用字符串转字节数组的方式:byte[] bytes = "ABCDEF".getBytes();
fos.write(bytes);
fos.close();
System.out.println("已经写出");

需要注意的是,上面重写文件内容只会出现在重建输出流时,如果在一个输出流关闭之前多次写入,还是会追加的。如下面程序运行后 fos.txt 内容为"ABCDEFABCDEF"。

FileOutputStream fos = new FileOutputStream("G:/test/fos.txt");
byte[] bytes = {65, 66, 67, 68, 69, 70};
fos.write(bytes);
byte[] bytes1 = {65, 66, 67, 68, 69, 70};
fos.write(bytes1);
fos.close();

write()还可以对任意范围的字符串写入

byte[] bytes = "ABCDEF".getBytes();
fos.write(bytes, 1, 2);
//从位置1索引开始,写入2个字节(“BC”)

3.2 InputStream

将硬盘中的文件中的内容读取到内存中

读方法描述
abstract int read()从输入流中读取下一个数据字节。只返回一个字节,所以int返回值为0~255,如果读取的字节为空,返回-1
int read​(byte[] b)从输入流中读取一些字节数并将它们存储到缓冲区数组b。返回值为被读取的字节数,0为没读取字节,-1为文章末尾没有可用字节。
int read​(byte[] b, int off, int len)从输入流 len最多 len字节的数据读入一个字节数组。

3.2.1 FileInputStream

read()单个字节读取

FileInputStream fis = new FileInputStream("g:/test/a.txt");
while(true){
    byte b = (byte) fis.read();
    if(b == -1)
        break;
    System.out.println((char)b);
}

read(byte[] b) 分组读取

byte[] bytes = new byte[10];
fis.read(bytes);
System.out.println(new String(bytes));
fis.read(bytes);
System.out.println(new String(bytes));
fis.read(bytes);
System.out.println(new String(bytes));
fis.close();
// abcdefghij
// klmnopqrst
// uvwxyzqrst
//最后一行出现问题,应该只输出6个但是却输出了10个,这是由于只有前6个字节被新字节覆盖

byte[] bytes = new byte[10];
int len = fis.read(bytes);
System.out.println(new String(bytes, 0, len));
len = fis.read(bytes);
System.out.println(new String(bytes, 0, len));
len = fis.read(bytes);
System.out.println(new String(bytes, 0, len));
fis.close();
//使用read的返回值可以得知有效读取的字节数目,进而只输出有效内容

通常使用一组输入的读取方式,减少IO调用的频率

但是汉字如何写入?
如果仍然以上的方法会出现乱码,因为汉字采用的时UTF-8编码,编码的长度是可变的,因此限制每组读取的长度可能会发现读取一个字的部分编码的情况。

解决方法:

  1. 使用较大的字节数组,将全部的UTF-8编码包括进来
  2. 使用下面的字符流,以字符为单位操作

4. 字符流

以字符为单位避免上面汉字读写出现乱码的情况,但字符流只能操作文字,而字节流能操作任何文件,因此应用较少。

4.1 Writer

4.1.1 FileWriter

FileWriter fw = new FileWriter("g:/test/a.txt", true);
fw.write('b');
fw.write("锄禾日当午,汗滴禾下土");
fw.append("汗滴禾下土");
//该方法会返回 fw.append("汗滴禾下土") 本身,也是writer类的对象,这意味着可以连续使用,即追加,如下所示:
fw.append("锄禾日当午").append(",").append("汗滴禾下土");
fw.close();

注意:字符流如果写入后没有运行fw.close(),则字符会存在缓冲区,等候后面的写入,所以不会成功写入文件,但是通过运行fs.flush()执行缓冲区刷新操作可以强制写入

4.2 Reader

4.2.1 FileReader

FileReader fr = new FileReader("g:/test/a.txt");
int c = fr.read();
System.out.println(c);
//38148
System.out.println((char)c);
//锄
fr.close();

为了将文件中的全部文字输出,可能会使用以下的方法,使用100长度的字符数组存储。但需要注意的是,在创建该数组时,实际上是由100个0组成,最后转换为字符串输出时也会输出100个字符(文件中的字符+空格)。

char[] chars = new char[100];
fr.read(chars);
String text = new String(chars);
System.out.println(text);
//锄禾日当午,汗滴禾下土                                                                                         
System.out.println(text.length());
//100
fr.close();

解决办法:与字节流类似,规定字符数组向字符串转换的长度 new String(chars[], offset, len)

5. 将字节流 ‘装饰’ 为字符流

  1. 将字节输入流,转换为字符输入流
FileInputStream fis = new FileInputStream("g:/test/a.txt");

//参数1,要转换的字节流
//参数2, 指定编码名称
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
while(true){
    int c = isr.read();
    if(c == -1){
        break;
    }
    System.out.println((char)c);
}
  1. 将字节输出流,转换为字符输出流
OutputStreamWriter osw = new OutputStreamWriter(fos);
osw.write("床前明月光");
osw.flush();
osw.close();

6. 字符输出(打印流)

6.1 字节打印流

PrintStream ps = new PrintStream("g:/test/a.txt");
ps.println("锄禾日当午");
ps.println("锄禾日当午");
ps.println("锄禾日当午");
//与在控制台的输出效果类似,在a.txt打印了三行文字
ps.close();

6.2 字符打印流

PrintWriter pw = new PrintWriter("g:/test/a.txt");
pw.println("锄禾日当午");
pw.println("锄禾日当午");
pw.println("锄禾日当午");
pw.flush();

与字符流相同,打印流必须使用flush()或close()强制输出缓冲内容,才能成功写入

6.2.1 字节转字符打印流

FileOutputStream fos = new FileOutputStream("g:/test/a.txt");
PrintWriter pw = new PrintWriter(fos);
pw.println("锄禾日当午");
pw.println("锄禾日当午");
pw.println("锄禾日当午");
pw.flush();

6.3 缓存读取流

将字符输入流转换成带有缓存的,可以一次读取一行的缓存字符读取流

FileReader fr = new FileReader("g:/test/a.txt");
BufferedReader br = new BufferedReader(fr);
String text = br.readLine();
System.out.println(text);

7 收集异常日志

try{
    String s = null;
    s.toString();
}catch(Exception e){
    PrintWriter pw = new PrintWriter("g:/test/bug.txt");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    pw.println(sdf.format(new Date()));
    e.printStackTrace(pw);
    pw.close();
}

8 properties类

存储在.properties文件中

Properties类基于Map接口,存储的是键值对

8.1 写入文件

向Properties类中加入内容仍然用 put(K key, V val),将Map存入文件用 store(FileWriter fw, String comment),结束后关闭字符输出流

Properties ppt = new Properties();
ppt.put("name", "xxx");
ppt.put("info", "xxxq");
FileWriter fw = new FileWriter("g:/test/book.properties");
ppt.store(fw, "kankan");
fw.close();

8.2 读取文件

读取Properties文件用 load(FileReader fr),获取Map中的内容,可以用 get(K key)getProperty(K key)

Properties ppt = new Properties();
Reader r = new FileReader("g:/test/book.properties");
ppt.load(r);
System.out.println(ppt.getProperty("name"));
System.out.println(ppt.getProperty("info"));

9 序列化及反序列化操作

9.1 序列化

将对象存储到文件中

public static void main(String[] args) throws IOException {
    Book b = new Book("金苹果","xxx");
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("g:/test/book.txt"));
    oos.writeObject(b);
    oos.close();
}

//需要继承Serializable接口
static class Book implements Serializable {
    private String name;
    private String info;

    public Book(String name, String info) {
        this.name = name;
        this.info = info;
    }

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

注意:当类中的属性没有被序列化标记,则该类实现的对象也不能被序列化(String类时被序列化的)

9.2 反序列化

将写在文件中的对象输出

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("g:/test/book.txt"));
Object o = ois.readObject();
System.out.println(o);
Book b1 = (Book) ois.readObject();
System.out.println(b1.getInfo());

9.3 部分属性的序列化和反序列化

  1. transient 修饰符
private transient String number;
//number不会被序列化
  1. static 修饰符
private static String number;
//number不会被序列化
  1. writeObject和readObject方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    out.writeObject(number);
    out.writeObject(name);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    number = (String) in.readObject();
    name = (String) in.readObject();
}
  1. Externalizable 实现部分序列化
    Externalizable 继承自Serializable,使用Externalizable接口需要实现readExternal方法和writeExternal方法来实现序列化和反序列化。

相比于Serializable,Externalizable的实现比较复杂,需要开发者自己完成,但是速度提升,存储空间减少

public class Person implements Externalizable {
    private String name;
    private String age;

//可以在writeExternal和readExternal方法中自定义想要序列化和反序列化的方法
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = (String) in.readObject();
    }
}

10 try-with-resources

操作需要关闭的资源时(输入输出流),经常需要释放资源。

使用try-with-resources,只要try()中的内容继承了Closeable()或AutoCloseable()类,那么该对象一定可以通过close()关闭,也就一定会在finally自动关闭。

10.1 JDK1.7方法

try(FileReader fr = new FileReader("g://test/d16.txt")) {
    int c = fr.read();
    System.out.println((char)c);
} catch (IOException e) {
    e.printStackTrace();
}

10.2 JDK1.9方法

FileReader fr = new FileReader("g://test/d16.txt");
PrintWriter pw = new PrintWriter("g://test/d16.txt");
try(fr;pw){
    int c = fr.read();
    System.out.println((char)c);
}catch(IOException e){
    e.printStackTrace();
}

JDK1.9方法

FileReader fr = new FileReader("g://test/d16.txt");
PrintWriter pw = new PrintWriter("g://test/d16.txt");
try(fr;pw){
    int c = fr.read();
    System.out.println((char)c);
}catch(IOException e){
    e.printStackTrace();
}
这篇关于Java常用类库之IO的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!