Java IO(二)

1. 序列化:

序列化就是将一个对象转换成字节序列,方便存储和传输,主要完成:

  1. 永久性保存对象,保存对象的字节序列到本地文件或者数据库中; 、
  2. 通过序列化以字节流的形式使对象在网络中进行传递和接收;
  3. 通过序列化在进程间传递对象;

1.1 Serializable

序列化:ObjectOutputStream.writeObject()
反序列化:ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对class文件进行修改后若直接进行反序列化会报错,因为UID会被重新赋值
解决办法 在class文件中定义一个UID 这样保证永远不变

1.2 Externalizable

Externlizable接口继承了java的序列化接口,并增加了两个方法:

 void writeExternal(ObjectOutput out) throws IOException;
 void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

首先,在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
class Operate{
    //序列化方法
    public void serializable(Person person) throws FileNotFoundException, IOException{
        ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("E:\\IdeaProject\\IdeaProject\\Code_Test\\src\\a.txt"));
        outputStream.writeObject(person);
    }

    //反序列化的方法
    public Person deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\IdeaProject\\IdeaProject\\Code_Test\\src\\a.txt"));
        return (Person) ois.readObject();
    }
}
class Person implements Externalizable{
    private static final long serialVersionUID=1L;
    String username="阿泰斯特";
    String password;
    int age;

    public Person(){super();}
    public Person(String username,String password,int age){
        super();
        this.username=username;
        this.password=password;
        this.age=age;
    }


    //序列化操作扩展类
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //增加一个新的对象
        Date date=new Date();
        out.writeObject(username);
        out.writeObject(password);
        out.writeObject(date);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        username=(String)in.readObject();
        password=(String) in.readObject();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        Date date=(Date)in.readObject();
        System.out.println("反序列化后的日期"+sdf.format(date));
    }
    public String toString() {
        //注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的
        return "用户名:阿翔"+username+"  密 码:"+password+"  年龄:"+age;
    }
}
public class Test{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Operate operate=new Operate();
        Person person=new Person("","123456",20);
        System.out.println("为序列化之前的相关数据如下:\n"+person.toString());
        operate.serializable(person);
        Person newPerson=operate.deSerializable();
        System.out.println("-------------------------");
        System.out.println("序列化之后的相关数据如下:\n"+newPerson.toString());
    }
}

1.3 比较

Serializable接口是一个序列化Java类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是JVM内嵌的默认序列化方式,成本高、脆弱而且不安全。
Externalizable允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。

2. 打印流

在这里插入图片描述
在这里插入图片描述

3. 转换流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 缓冲流

关闭缓冲流自动关闭字节流
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. 网络操作:Socket

网络操作

6.新的输入/输出:NIO

流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。面向流的 I/O 通常相当慢。
面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。
I/O 包和 NIO 已经集成,java.io.* 以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。

6.1 通道和缓冲区

在这里插入图片描述

6.2 缓冲区状态变量

position 值跟踪从缓冲区中获取了多少数据。它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。position 总是小于或者等于 limit。
limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小

在这里插入图片描述
NIO快速复制实例

public static void fastCopy(String src, String dist) throws IOException {

    获得源文件的输入字节流 
    FileInputStream fin = new FileInputStream(src);

    获取输入字节流的文件通道
    FileChannel fcin = fin.getChannel();

    获取目标文件的输出字节流 
    FileOutputStream fout = new FileOutputStream(dist);

    获取输出字节流的文件通道 
    FileChannel fcout = fout.getChannel();

     为缓冲区分配 1024 个字节 
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    while (true) {
        从输入通道中读取数据到缓冲区中 
        int r = fcin.read(buffer);
        read() 返回 -1 表示 EOF 
        if (r == -1) {
            break;
        }
        切换读写 
        buffer.flip();
        把缓冲区的内容写入输出文件中 
        fcout.write(buffer);
        清空缓冲区 
        buffer.clear();
    }
}

6.3 选择器

NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。

NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。

通过配置 监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。对于 IO 密集型的应用具有很好地性能。只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能

1.创建选择器
Selector selector = Selector.open();

2. 将通道注册到选择器上
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,,
因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件

在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

它们在 SelectionKey 的定义如下:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
每个事件可以被当成一个位域,从而组成事件集整数。例如:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
3. 监听事件
int num = selector.select();
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。

4. 获取到达的事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key.isAcceptable()) {
        // ...
    } else if (key.isReadable()) {
        // ...
    }
    keyIterator.remove();
}

5. 事件循环
因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,
因此服务器端处理事件的代码一般会放在一个死循环内。

while (true) {
    int num = selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = keys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isAcceptable()) {
            // ...
        } else if (key.isReadable()) {
            // ...
        }
        keyIterator.remove();
    }
}

6.4 套接字NIO实例

public class NIOServer {

    public static void main(String[] args) throws IOException {

        Selector selector = Selector.open();

        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        ServerSocket serverSocket = ssChannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        serverSocket.bind(address);

        while (true) {

            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();

            while (keyIterator.hasNext()) {

                SelectionKey key = keyIterator.next();

                if (key.isAcceptable()) {

                    ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();

                    // 服务器会为每个新连接创建一个 SocketChannel
                    SocketChannel sChannel = ssChannel1.accept();
                    sChannel.configureBlocking(false);

                    // 这个新连接主要用于从客户端读取数据
                    sChannel.register(selector, SelectionKey.OP_READ);

                } else if (key.isReadable()) {

                    SocketChannel sChannel = (SocketChannel) key.channel();
                    System.out.println(readDataFromSocketChannel(sChannel));
                    sChannel.close();
                }

                keyIterator.remove();
            }
        }
    }

    private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder data = new StringBuilder();

        while (true) {

            buffer.clear();
            int n = sChannel.read(buffer);
            if (n == -1) {
                break;
            }
            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
}
public class NIOClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        String s = "hello world";
        out.write(s.getBytes());
        out.close();
    }
}

7. 内存映射文件

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。但向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。

下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。

MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值