IO

IO

IO流是输入和输出的接触。

分类

1 输入流,输出流

输入流:只能读数据(**--->内存),不能写数据
输出流:只能写数据(内存 ---> 磁盘),不能读数据
划分原则:程序运行时所在的内存

截图7.PNG

截图8.PNG

2 字节流,字符流

字节流和字符流的操作基本一致,唯一的区别是操作的数据单元不同。
字节流——8位字节
字符流——16位字符

3 节点流,处理流

节点流:低级流
处理流:高级流,包装流

4 InputStream 和 Reader

InputStream 和 Reader 是所有输入流的抽象基类,本身不能常见实例来执行输入,但他们是所有输入流的模板


截图9.PNG
  • InputStream

    int read() 从输入流中获取单个字节,返回所取得的字节数
    int read(byte[] b) 从输入流中最多取读b.length个字节数据,并将其存储在字节数组b中,返回实际取读的字节数
    int read(byte[] b,int off,int len) 从输入流中最多取读len个字节的数据,并将其存储在字节数组b中,
    放入字节数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际取读的字节数

  • Reader

    int read(): 取读单个字符
    int read(char[] cbuf) 从输入流中最多取读cbuf.length个字符,并将其存储在字符数组cbuf中,返回实际读取的字符数
    int read(char[] cbuf,int off,int len) 从输入流中最多取读len个字符的数据,并将其存储在字符数组b中,
    放入字符数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际取读的字符数

public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
        byte[] bbuf = new byte[1024];
        int hasRead = 0;
        while ((hasRead = fis.read(bbuf)) > 0) {
            System.out.println(new String(bbuf, 0, hasRead));
        }
        fis.close();
    }
}
public class FileReaderTest {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("FileReaderTest.java");
        char[] bbuf = new char[32];
        int hasRead = 0;
        while ((hasRead = fr.read(bbuf)) > 0) {
            System.out.print(new String(bbuf, 0, hasRead));
        }
        fr.close();
    }
}

5 OutputStream 和 Writer

  • void write(int c) 将制定的字节、字符数出道输出流中
    void write(byte[]/char[] buf) 将字节、字符数组输出到指定的输出流中
    void write(byte[]/char[] buf,int off,int len) 将字节、字符数组从off位置开始,长度为len的字节
    、字符输出到输出流中。

Writer中还可以使用字符串代替字符数组

  • void write(String s) 将str中包含的字符输出到指定的输出流中
  • void write(String str,int off,int len) 将str字符串中从off位置开始,长度为len的字符输出到指定输出流中
public class FileOutPutStreamTest {
    public static void main(String[] args) {
        try (
            FileInputStream fis =
                new FileInputStream("FileOutPutStreamTest.java");
            FileOutputStream fos = new FileOutputStream("newFile.txt")) {

            byte[] bbuf = new byte[32];
            int hasRead = 0;
            while ((hasRead = fis.read(bbuf)) > 0) {
                fos.write(bbuf, 0, hasRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```java
public class FileWriterTest {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("pome.txt")) {
            fw.write("锦瑟-李商隐\r\n");
            fw.write("锦瑟无端五十弦,一弦一柱思华年\r\n");
            fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃\r\n");
            fw.write("沧海月明珠有泪,蓝田日暖玉生烟\r\n");
            fw.write("此情可待成追忆,只是当时已惘然\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出:

锦瑟-李商隐
锦瑟无端五十弦,一弦一柱思华年
庄生晓梦迷蝴蝶,望帝春心托杜鹃
沧海月明珠有泪,蓝田日暖玉生烟
此情可待成追忆,只是当时已惘然

6 处理流

处理流可以隐藏底层设备上节点流的差异,对外提供更加方便的输入,输出方法
识别处理流:只要文件流的构造器参数不是一个物理节点而是一个已存在的流,那么这个流一定是处理流。
所有的子节点都是直接以物理IO最为构造器参数的。

public class PrintStreamTest {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) {
            ps.println("普通字符串");
            ps.println(new PrintStreamTest());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在使用处理刘包装了底层节点流后,关闭资源时,可以之关闭最上层的处理流即可

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

序列化

序列化的目标是将对象保存到磁盘中,或允许在网络中传输对象。
对象序列化机制允许把内容中java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保留在磁盘上
通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这个二进制流(无论是从磁盘还是网络),
都可以讲这种二进制流恢复到原来的java文件。

序列化机制是的对象可以脱离程序的运行而独立存在。

对象的序列化(Serialize) 指将一个Java 对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)
则指从IO流中恢复该java对象。

序列化对象

  • 创建一个ObjectOutputStream,这个输出是一个处理流,所以必须建立才其他节点流的基础上
ObjectOutputStream oos = new OjectOutputStream(new FileOutputStream("XXX"));
  • 调用ObjectOuputStream对象的writeObject()方法即可输出徐硫化对象
oos.writeObject(***);

样例

public class WriteObject {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
            Person person = new Person("孙悟空", 5000);
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出文件中的内容
�� �sr �com.huawei.kemuer.Person�#��JR`Z� �I �ageL �namet �Ljava/lang/String;xp ��t 孙悟空

反序列化步骤

  • 创建一个ObjectInputStream 输入流,处理流
ObjectInputStram ois = new ObjectInputStream(new FileInputStream(***));
  • 调用ObjectInputStream对象的readObject()方法取读流中的对象,该方法返回一个Object类型的java对象,可以强转
    该对象的真实类型
*** p = (***)ois.readObject();
public class ReadObject {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));) {
            Person p = (Person) ois.readObject();
            System.out.println("名字:" + p.getName() + " 年龄:" + p.getAge());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出:
名字:孙悟空 年龄:5000
当反序列化取读java对象,无需通过构造器来初始化对象

对象引用序列化

如果某个类没有序列化,那么拥有这个类作为成员变脸的类也是不能序列化的

public class WriteTeacher {
    public static void main(String[] args) {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Teacher.txt"))) {
            Person p = new Person("孙悟空", 5000);
            Teacher t1 = new Teacher("唐僧", p);
            Teacher t2 = new Teacher("菩提老祖", p);
            out.writeObject(p);
            out.writeObject(t1);
            out.writeObject(t2);
            out.writeObject(t1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多次序列化同一个对象时,只有第一次序列化时,才会把该java对象转换成字节序序列并输出。
当序列化一个可变对象时,只有第一次使用writeObject()方法输出是才会将该对象转换成字节序列并输出
当程序再次调用wirteObject()时,程序只是输出前面的序列化编号,及时后面该对象的实例变量值已经被改变
改变的实例变量值也不会被输出。

public class SerializeMutable {
    public static void main(String[] args) {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mutable.txt"));
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("mutable.txt"))) {
            Person p = new Person("孙悟空", 5000);
            out.writeObject(p);
            p.setName("猪八戒");
            out.writeObject(p);
            Person p1 = (Person) in.readObject();
            Person p2 = (Person) in.readObject();
            System.out.println(p1 == p2);
            System.out.println(p2.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出:

有参数构造器
true
孙悟空

自定义序列化

敏感信息不可以序列化
在敏感信息前加transient关键字,保证字段不被序列化

使用transient关键字修饰变量虽然简单、方便,但被transient修饰的实例变量江北完全隔离在序列化机制之外
这样导致在反序列化恢复java对象时,无法取得该实例变量值。

NIO

为了解决传统IO效率不高的问题而出现的新模式的IO

新IO的主要目的也是用于输入/输出,但新IO使用了不同的方式来处理输入/输出,新IO采用内存映射文件的方式来处理
输入/输出。新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(模拟操作系统的虚拟内存)
,通过这种方式来进行输入/输出比传统的输入/输出要快的多。

java中与新IO有关的包

  • java.nio: 主要包含各种与buffer相关的类
  • java.nio.channels:主要包含于Channel和Selector相关的类
  • java.nio.charset: 主要包含与字符集相关的类
  • java.nio.channels.spi:主要包含与Channel相关服务提供者接口编程
  • java.nio.charset.spi:主要包含与字符集相关的服务提供者编程接口。

Channel(通道) 和 Buffer(缓冲)是新IO中两个核心对象,Channel是对传统输入/输出系统的模拟,在新IO
系统中所有的数据都需要通过通道传输。Channel与传统的InputStream和OutputStream最大的区别就是他提供了一个
map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则新IO
则是面向块的处理

Buffer

Buffer可以理解成一个容器,他本质是一个数组,发送到channel中的所有对形象都必须放到Buffer中,而Channel中读取的
数据也要先放到Buffer中,此处的Buffer有点类似于竹筒。


截图10.PNG

Buffer是个抽象类
最常用的子类是ByteBuffer。
Buffer类没有构造器,通过下面的方法来获取一个Buffer对象

  • static XXXBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象

在Buffer中有三个比较重要的概念:

  • 容量(Capacity):缓冲区的容量(capacity)表示改Buffer的最大数据容量,最多可以存储多少数据。
    不能为负值,创建以后不能改变

  • 界限(limit):第一个不应该被读出或者写入缓存区位置索引,也就是说位于limit后的数据及不可以被读,也不可以被写

  • 位置(position):用于志明下一个可以被读出的或者写入的缓存区位置索引(类似于IO流中的记录指针)

0 <= mark <= position <= limit <= capacity

Buffer的主要作用是装入数据然后输出数据
最开始数据的limit为capacity,position为0,装入数据后,position向后移动一些位置

  • flip():从Buffer中取出数据做好准备
    limit设置为position所在的位置,并将position设置为0
  • clear():再次向Buffer中装入数据
    position设置为0,将limit设置为capacity
public class BufferTest {
    public static void main(String[] args) {
        // 创建Buffer
        CharBuffer buffer = CharBuffer.allocate(8);
        System.out.println("capacity: " + buffer.capacity());
        System.out.println("limit: " + buffer.limit());
        System.out.println("position: " + buffer.position());
        buffer.put('a');
        buffer.put('b');
        buffer.put('c');
        System.out.println("加入三个元素后,position: " + buffer.position());
        buffer.flip();
        System.out.println("执行flit()后,limit: " + buffer.limit());
        System.out.println("position: " + buffer.position());
        System.out.println("第一个元素(position=0):" + buffer.get());
        System.out.println("取出一个元素后,position: " + buffer.position());
        buffer.clear();
        System.out.println("执行clear()后,limit: " + buffer.limit());
        System.out.println("执行clear()后,position: " + buffer.position());
        System.out.println("执行clear()后,缓存区内容并没有被清除,第三个元素为" + buffer.get(2));
        System.out.println("执行绝对取读后,position:" + buffer.position());
    }
}

输出结果

capacity: 8
limit: 8
position: 0
加入三个元素后,position: 3
执行flit()后,limit: 3
position: 0
第一个元素(position=0):a
取出一个元素后,position: 1
执行clear()后,limit: 8
执行clear()后,position: 0
执行clear()后,缓存区内容并没有被清除,第三个元素为c
执行绝对取读后,position:0

Channel

channel类似于传统流对象,但与传统流对形象有两个主要的区别。

  • Channel可以直接将制定文件的部分或全部直接映射成Buffer
  • 程序补鞥呢直接访问Channel中的数据,包括取读,写入都不行,Channel只能与Buffer进行交互。
    要从Channel中读取数据,先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;
    如果要将程序中的数据写入Channel,一样先让程序将数据放入buffer中,在将Buffer里面的数据写入channel

Pipe.SinkChannel Pipe.SourceChannel适用于支持线程之间通信的管道Channel
ServerSocketChannel SocketChannel适用于支持TCP网络通信的Channel
DatagramChannel适用于支持UDP网络通信的Channel

所有的Channel不通过构造器来直接创建,而是通过传统的节点InputStream,
OutPutStream的getChannel()方法

Channel中最常用的方法:
map(): 用于将Channel对应的部分或全部数据英社称ByteBuffer
read() 或 write()都有一定的重载形式。用于从Buffer中读取或写入数据。

public class FileChannelTest {
    public static void main(String[] args) {
        File f = new File("FileChannelTest.java");
        try (FileChannel inChannel = new FileInputStream(f).getChannel();
            FileChannel outChannel = new FileOutputStream("a.txt").getChannel();) {
            // 将FileChannel里面的全部数据英社称ByteBuffer
            MappedByteBuffer byteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
            // 使用GBK的字符集来创建解码器
            Charset charSet = Charset.forName("GBK");
            // 直接将Buffer中的数据全部输出
            outChannel.write(byteBuffer);
            // 再次调用buffer的clear方法,复原limit和position的位置
            byteBuffer.clear();
            // 创建解码器
            CharsetDecoder decoder = charSet.newDecoder();
            // 使用解码器将byteBuffer转换成charbuffer
            CharBuffer chBuffer = decoder.decode(byteBuffer);
            System.out.println(chBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符集合Charset

截图11.PNG
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Java I/O流 Java的IO通过Java.io包下的类和接口来支持,在Java.io包下主要输入、输出两种I...
    So_ProbuING阅读 3,126评论 0 4
  • File类 功能与作用 java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关 File能新建、...
    zqyadam阅读 2,087评论 0 0
  • 【020 标记接口】 标记接口没有任何成员和方法,表明的是能够具备某种功能,通常是在编译器进行类型校验。 ① Cl...
    小笨特阅读 4,066评论 0 1
  • 1、Java 中有几种类型的流? 按数据流向:输入流(inputStream)和输出流(outputStream)...
    执着的逗比阅读 5,993评论 0 2
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 11,277评论 0 4