天天看点

java io流java io流

java io流

在总结io流前,也回顾一下File这个类,它也是在java.io包里的类。

我把它理解为映射一个文件或者文件夹的类,就是它提供了各种方法获得这个文件或文件夹的信息和对其进行删改查的操作,但是这些都只是对文件或文件夹,与其里面的内容没关系。

后面的流对象很多构造器都可以传进一个File对象进行初始化,所以在这里简单提一嘴File的初始化,它的其他方法就不在这说了

常见的创建File对象的方法

File a = new File("文件或文件夹路径");//直接初始化,可以是相对路径也可以是绝对路径,
    File b = new File("父目录","文件名或文件夹名");//方便在一个目录下,创建多个文件
    File c = new File(parent, "名");//parent是父目录的File对象
//一般上面的""的字符串都会用String代替,为了可读性,避免魔法值
           

还有个createNewFile():boolean方法比较常用,这个方法会抛出一个IOException

流的分类

按输入输出分:输入流和输出流

简单说一下输入流和输出流吧

输入流,就是将磁盘上的文件啊键盘啊什么的读到应用程序中

输出流,和输入流相反,将程序中的数据写到磁盘上或者显示到屏幕上

按输入输出数据类型分:字节流和字符流

字节流,顾名思义,流里面都是字节类型,适合读写图片、音频等以字节形式存储的文件

字符流,流里面是字符类型,其实底层也还是用到字节流,适合读写文本文件

按流读写的对象是否单一又分为:节点流和处理流

节点流,就是FileInputStream、CharInputStream、ByteInputStream这种,只会读取相应类型东西的流,比如说FileInputStream,它就只能读文件,读不了字符啊字符数组其他的,讲究专用性

而处理流呢,就不那么专一了,它可以读任何类型,只要构造的时候给我传一个相应的节点流,那我就能读那种类型的东西,而且处理流还定义了一些方法方便读写操作的,相当于给节点流做了一下包装。

java io流java io流

字节流常见类的体系图

java io流java io流

InputStream

InputStream是一个抽象类,主要是一个read()抽象方法和两个read方法的重载,还有一个close方法,其他的用到时在查手册就是了

read():int //抽象方法,返回下一个字节的data,是逐字节读取的,没有了就返回-1

read(byte[]):int //将读取的字节数据放到byte数组,返回读取字节长度,没了就-1

read(byte[], int, int):int //跟第二个差不多,第二个参数是写入字节数据到byte数组的开始下标,第三是写入最大数组长度,返回读到的长度,没有了就-1

close():void //关闭流

InputStream的常见子类

FileInputStream

FileInputStream是一个按字节读取文件的一个流,其常用的方法也就是父类InputStream提供的那么些方法,下面直接用代码演示一下吧

public class Test{
    public static void main(String[] args){
        FileInputStream fis = null;//如果在try块里声明直接赋值,那如果抛出异常,在finally里将无法关闭,已经不在作用域了
        byte[] buf = new byte[1024];//用来接收读到的字节数据,一般设1024长
        int readLen = 0;
        try{
            fis = new FileInputStream("a.txt");//这里会抛出FileNotFoundException
            while((readLen = fis.read(buf)) != -1){
                //循环读知道读到最后为止
                System.out.println(buf);
            }
        }catch(IOException e) {
            e.printStackTrace();
        }finally{
            try{
                if(fis != null)
                    fis.close();//close会抛出IOException
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
           

上面用到了read(byte[]):int方法,其实这里用read(byte,int,int):int也是一样的,第二个参数传0,第三个传1024就一样了,而第一个就有点不同

try(FileInputStream fis = new FileInputStream("a.txt")){
    //这样写就不用担心像上面那种问题了,在出try块前会自动帮我们关闭资源
        int data = 0;
        while((data = fis.read()) != -1){
            System.out.println((char)data);//转成char为了显示
            //其实是读一个byte向上转为int,但是值是0-255的
        }
        
    }catch(...){...}
           

BufferedInputStream

BufferedInputStream继承了FilterInputStream这个抽象类,常用的方法还是跟FileInputStream一样,只是构造时不太一样

//构造BufferedInputStream
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"),1024);
    //需要传一个节点流对象,而第二个参数是保存读到数据的字节数组的size
    //只要是IO对象被创建都会抛出FileNotFoundException,这两个地方
           

创建完后其他操作都差不多,关闭时,只需要关闭外围流就行了,不用关里边的流,因为外围流的close会帮你做这件事

bis.close();//这样就行了
	//同样,所以IO流的close都会抛IOException
           

ObjectInputStream

ObjectInputStream也是一个处理流,具体放到下面和ObjectOutputStream一起讲吧

OutputStream

OutputStream也是一个抽象类,实现了Flushable接口和Closeable接口,只有五个方法

flush():void //来自Flushable接口,在OutputStream中不做任何操作,功能就是将流里边缓冲到的字节数据交给底层,让底层做真正的写入物理设备

close():void //关闭流,来自Closeable接口

write(int):void //输出一个字节的数据

write(byte[]):void //输出一个字节数组的数据

write(byte[] buf, int offset, int len):void //输出buf字节数组从offset下标开始,长度为len的数据

注意:

  • 字接流是直接对字节进行输出,不需要flush(),而字符流的输出需要flush()。因为字符流写入文本数据时,需要先将文本数据写入字符流中,对照编码表转换成字节数据,然后flush进目的地。字节流中是字节数据不需要转换,所以可以直接写入目的地。
  • close里面有调用flush

OutputStream的常见子类

  1. FileOutputStream

    FileOutputStream常用的构造器有四个,第一个参数不是File就是String,意思差不多,有两个有第二个参数,是boolean,传进true就走文件的最后开始写,不会覆盖文件原来的内容,没有第二个参数的构造器的输出流默认覆盖原内容的。

    FileOutputStream的使用其实也很简单,三个write基本跟read是相应的

    public class CopyPic {
        public static void main(String[] args) {
            FileInputStream fileInputStream = null;
            FileOutputStream fileOutputStream = null;
            byte[] buf = new byte[1024];
            int readLen = 0;
            try {
                fileInputStream = new FileInputStream("a.jpg");
                fileOutputStream = new FileOutputStream("b.jpg");
                while((readLen = fileInputStream.read(buf)) != -1){
                    fileOutputStream.write(buf, 0, readLen);
                    //这不要直接write(buf)
                }
                System.out.println("successfully");
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    if (fileInputStream != null){
                        fileInputStream.close();
                    }
                    if (fileOutputStream != null){
                        fileOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
               

    上面就是一个简单的对一个图片进行复制的程序

    注意上面的注释,如果使用了write(buf)会怎么样?

    ​ 复制到的图片将不会是原来的图片,因为最后一次的输入的buf数组可能没有满,后边最后一次没输入的byte本不该输出,但是write会将它们输出,导致最后系统写入数据的字节文件跟本来图片的字节数据不一样了

  2. BufferedOutputStream

    BufferedOutputStream的使用我就不在多说了,参照FileOutputStream和BufferedInputStream

  3. ObjectOutputStream

    这个我就合着ObjectInputStream一起总结吧

ObjectInputStream和ObjectOutputStream

对象流只有字节流有,而且也是处理流,构造和关闭都跟其他处理流差不多

之前见到的流都是直接将数据write进文件的,write(a),如果a = 100,那文件里面就是100,这样是不知道这个100是String还是int还是别的什么,而对象流就解决了这样的问题

将对象输出写入文件的过程称为序列化对象,反过来将对象从文件中读取输入称为反序列化

序列化的对象一定要实现Serializable或者externalizable,一般实现前者,因为前者不需要重写方法,对象的属性也要实现Serializable

序列化接口是可继承:父类实现了,子类就不用实现了

ObjectOutputStream 将数据写入文件时用writeInt、writeChar……不是直接write,不然是直接写入值,没有数据类型,且写入最好都是public类的对象,不然反序列化时就不好类型转换。(writeUTF是写字符串)

反序列时,即用ObjectInputStream对象读取文件时,read的顺序一定要跟写入的顺序一样,且都是readInt啊什么的,不是read

public class ObjectStream_ {
    public static void main(String[] args) {
       //序列化
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\冫\\Desktop\\a.txt", true));
            oos.writeInt(100);
            oos.writeChar('a');
            oos.writeUTF("hello");
            oos.writeObject(new Person("Tim", 10, "man", 1));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

//反序列化
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("C:\\Users\\冫\\Desktop\\a.txt"));
            int a = ois.readInt();
            char c = ois.readChar();
            String s = ois.readUTF();
            Object person = ois.readObject();
            System.out.println(person.getClass());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
           

输出包括包名的Person类

还需要注意,序列化对象时,被static和transient修饰的成员不会被序列化;序列化的类中建议加个serialVersionUID属性,提高版本兼容性,加了后如果你对类中的一些东西改动了,还是可以反序列化出来。

字符类中常用类的体系图

java io流java io流
java io流java io流

Reader

Reader是一个抽象类,实现了Readable接口和Closeable接口,主要方法有下面几种

read():int

read(char[]):int

read(char[] cbuf, int off, int len):int

ready():boolean //字符流是否准备好读

可以看到基本跟InputStream一样,只是byte数组换成了char数组,其实前两个read都是调用了第三个实现的。但是要注意第一个read,返回的i数据是int型,不要用char接,用int接完转成char就行了,或者直接转也行

Reader的常用子类

  1. InputStreamReader

    这个类是字节流转化为字符流的桥梁,后面跟OutputStreamWrite一起总结

  2. FileReader

    FileReader继承了InputStreamReader,自身只有三个构造器,常用的就两个,FileReader(String)和FileReader(File),读的操作跟FileInputStream差不多,只是变成按字符读取

  3. BufferedReader

    BufferedReader基本和BufferedInputStream用法一样,连两个构造器都传参都差不多,只不过前者传的是Reader的子类。

    不过BufferedReader扩展了一些方法,常用的有readLine():String,读取整一行,返回一个String字符串。

Writer

抽象类,实现了Appendable接口、Flushable接口和Closeable接口

主要方法有五个write:

write(int):void

write(char[]):void

write(char[], int, int):void

write(String):void

write(String, int, int):void

都跟OutputStream的write用法差不多,只是byte[]变成了char[]或者String

五个中write(char[], int, int):void最重要,其他四个都是基于它实现的

注意:字符输出流用完一定要关闭,且可以输出一些就调用flush方法,不然是没有真正输出的。

Writer常用子类

  1. OutputStreamWriter

    跟InputStreamReader相对应

  2. FileWriter

    继承了InputStreamWriter,跟FileOutputStream差不多用法,这就不细说了

  3. BufferedWriter

    这个也跟BufferedOutputStream基本一样,但多个newLine(),作用是一个输出换行,通信时可以用作传输结束的标志,其他不说了

InputStreamReader和OutputStreamWriter

InputStreamReader和OutputStreamWriter两个类是从字节流到字符流的桥接器:它使用指定的字符集读取字节并将它们解码为字符。

它们都是处理流,所以创建对象和关闭资源跟其他处理流基本一致,不过构造器第二个参数不再是size,是String或Charset,用来设置字节转换为字符的字符集的

它们使用的字符集可以通过名称指定(String),也可以明确指定(Charset),或者接受平台的默认字符集(没有第二个参数)。

每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节,为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节,为了获得最高效率,请考虑在BufferedReader中包装InputStreamReader

OutStreamWriter输出写入文件可以指定的编码方式写入。

其实这两个类用起来都不难,创建对象是指定个字符集,其他跟别的处理流也差不多,不过它们两个的底层实现是比较有趣的,基本都是通过StreamDecoder实现的,可以看一下这个博主写的这两篇文章

JAVA基础知识之InputStreamReader流

JAVA基础知识之StreamDecoder流

标准输入输出流

标准输入流:System.in, 是System类里面的一个静态属性,类型为 InputStream,但是真正运行类型是BufferedInputStream,默认 设备是键盘。

标准输出流:System.out,也是System的一个静态属性,类型为 PrintStream(FilterOutputStream的一个子类),默认设备是显 示器

System.out我们平时就用的很多了;System.in在创建Scanner的时候就需要,其实就是传进一个BufferedInputStream,为了读出我们键盘输入的东西。

打印流

PrintStream和PrintWriter,一个字节流,一个字符流,打印流只有输出流有,也都是处理流,用法跟BufferedOutputStream和BufferedWriter差不多,只是用的write方法变成了print方法,其实print方法还是靠调用write方法实现的,不过扩展了很多一些println、format等一些方法。

Properties

Properties是Map体系下的一个类,是HashTable的一个子类,是一个用于存储配置参数的散列表,一般配合IO流使用。

常用方法有

Properties() //构造器,也有个传Properties对象的有参构造器,这就不说了

load(Reader):void

//传入一个字符输入流,读取文件,并将‘=’左边的作为key,右边的作为value存储,所以读的文件格式是有要求格式的,根据规定的格式解析具体看load0方法,而且都是以String形式存储

load(InputStream):void //跟上面一样,不过是传入字节流

setProperty(String, String):Object

//设置键值对的值,返回原来的值,原来没有就返回null,这里用Object是因为设置是调用的父类的put,因为value是泛型所以返回Object

getProperty(String[, String]):String

//获取对应key的value

store(Writer,String):void 和 store(OutputStream,String):void

//将Properties对象存的东西输出到一个地方(如文件),第二个参数是注释

public class Properties_ {
    public static void main(String[] args) throws IOException {
        String path1 = "hello1.txt";
        String path2 = "hello2.txt";
        Properties properties = new Properties();
        properties.load(new FileReader(path));//读出,load,如果配置文件有文字要用字符流输入,不然会有乱码
        System.out.println("name = " + properties.get("name"));
        properties.setProperty("id", "01");
        properties.store(new FileOutputStream(path2),"user-messages");//第二个参数是对属性的描述,字符串类型,会在文件里头部以注释形式显示
    }
}