Java BIO总结

1. 分类

1.1 按数据流的方向分为输入流、输出流

数据流向含义
输入流从别的地方(本地文件,网络上的资源等)获取资源 输入到 我们的程序中
输出流从我们的程序中 输出到 别的地方(本地文件), 将一个字符串保存到本地文件中,就需要使用输出流

1.2 按处理数据单位不同分为字节流、字符流

1字符 = 2字节 、 1字节(byte) = 8位(bit) 、 一个汉字占两个字节长度

数据处理单位含义对应类
字节流每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码输入 InputStream
输出 OutputStream
字符流每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文输入 Reader
输出 Writer

1.3 按功能不同分为介质流、装饰流、功能流

功能分类含义
介质流以从或向一个特定的地方(节点)读写数据。如FileInputStream
装饰流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如FilterInputStream。装饰流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装
功能流特定功能来处理流对象,如SequenceInputStream可合并流、BufferedInputStream可为输入流提供缓冲功能

2. 字节流与字符流

2.1 使用场景

IO流类型使用场景
字节流一般用来处理图像,视频,以及PPT,Word类型的文件
字节流可以用来处理纯文本文件,但是仅限于英文、数字等,由于底层基于字节数组byte[]实现,存储范围有限,无法处理复杂编码的文本如中文等
字符流一般用于处理纯文本类型的文件,如TXT文件等
字节流可以用来处理纯文本文件,但是字符流不能用于处理图像视频等非文本类型的文件

2.2 转换

转换流的作用,文本文件在硬盘中以字节流的形式存储时,通过InputStreamReader读取后转化为字符流给程序处理,程序处理的字符流通过OutputStreamWriter转换为字节流保存。

转换流作用
InputStreamReader字节到字符的桥梁,将字节流以字符流输入
OutputStreamWriter字符到字节的桥梁,将字节流以字符流输出

3. java.io包结构

java.io包中核心类结构如下:
在这里插入图片描述

3.1 File

File(文件特征与管理):File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。

3.2 InputStream

InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

3.2.1 ByteArrayInputStream

字节数组输入流,该类的功能就是从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去,我们拿也是从这个字节数组中拿。
在这里插入图片描述
如上图,ByteArrayOutputStream内部是一个字节数组byte[] buff作为缓冲区进行实现的,它的存储值范围是Byte的取值范围-128~127,因此能存储的格式有限,支持数字、英文字符,但是不支持中文,因此该字节流无法处理中文等复杂字符集,如下摘取了部分byte值与字符的映射表

byte值字符
480
491
502
513
524
97a
98b
99c

使用示例如下

/**
 * created by guanjian on 2021/1/6 15:08
 */
public class ByteArrayStreamTest {

    public static void main(String[] args) throws IOException {
        String text = "abc";
        ByteArrayOutputStream bOutput = new ByteArrayOutputStream(text.length());

        bOutput.write(text.getBytes("UTF-8"));

        byte b[] = bOutput.toByteArray();

        for (int x = 0; x < b.length; x++) {
            // 打印字符
            System.out.format((char) b[x] + "\n");
        }

        System.out.println("========转大写=========");
        int c;
        ByteArrayInputStream bInput = new ByteArrayInputStream(b);
        while ((c = bInput.read()) != -1) {
            System.out.println(Character.toUpperCase((char) c));
        }
    }
}
【控制台输出】
a
b
c
========转大写=========
A
B
C

3.2.2 PipedInputStream

管道字节输入流,它和PipedOutputStream一起使用,能实现多线程间的管道通信
在这里插入图片描述
如上图,PipedInputStream的底层也是基于字节数组byte[] buff进行实现的,它提供了对应的读写线程ReadThreadWriteThread对字节数组进行写入和读取操作,使用Object.wait()Object.notifyAll() 方法来协调读写线程,当字节数组中数据读取完毕时则阻塞读线程,自旋等待新字节写入后唤醒读线程重新进行读取。

使用示例如下:

/**
 * created by guanjian on 2021/1/6 16:26
 */
public class PipedStreamTest {

    public static void main(String[] args) throws InterruptedException {
        Sender sender = new Sender();
        Receiver receiver = new Receiver();

        PipedOutputStream outStream = sender.getOutStream();
        PipedInputStream inStream = receiver.getInStream();

        try {
            inStream.connect(outStream); // 与下一句一样
            //outStream.connect(inStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        sender.start();
        receiver.start();
    }


    static class Sender extends Thread {
        private PipedOutputStream outStream = new PipedOutputStream();

        public PipedOutputStream getOutStream() {
            return outStream;
        }

        @Override
        public void run() {
            IntStream.rangeClosed(0, 10).forEach(x -> {
                String info = "hello, receiver";

                try {
                    outStream.write(info.getBytes());

                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

            try {
                outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    static class Receiver extends Thread {
        private PipedInputStream inStream = new PipedInputStream();

        public PipedInputStream getInStream() {
            return inStream;
        }

        @Override
        public void run() {
            byte[] buf = new byte[1024];

            try {
                int len;
                while ((len = inStream.read(buf)) != -1) {

                    System.out.format("receive message from sender : %s \n" , new String(buf, 0, len));
                }
                inStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}
【控制台输出】
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 
receive message from sender : hello, receiver 

3.2.3 SequenceInputStream

在这里插入图片描述
如上图,SequenceInputStream相当于合并了输入流,支持所有实现InputStream的输入流子类,通过实现了Enumeration接口的容器类进行存储遍历合并。

使用示例如下:

/**
 * created by guanjian on 2021/1/6 17:42
 */
public class SequenceStreamTest {

    public static void main(String[] args) throws IOException {
        Vector<ByteArrayInputStream> inputStreams = new Vector<>();
        inputStreams.addElement(new ByteArrayInputStream("abc".getBytes()));
        inputStreams.addElement(new ByteArrayInputStream("def".getBytes()));
        inputStreams.addElement(new ByteArrayInputStream("ghi".getBytes()));

        SequenceInputStream sis = new SequenceInputStream(inputStreams.elements());

        int c = 0;
        while ((c = sis.read()) != -1) {
            System.out.print((char)c);
        }
    }
}
【控制台输出】
abcdefghi

3.2.4 FileInputStream

提供系统文件的输入流方式,底层依赖系统实现。

3.2.5 FilterInputStream

在这里插入图片描述
FilterInputStream在装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。
FilterInputStream 的作用是用来封装其它的输入流,并为它们提供额外的功能。它主要包括BufferedInputStream、DataInputStream

  • BufferedInputStream 为输入流提供缓冲功能,减少底层IO交互,预先批量读取。
  • DataInputStream 允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型。
3.2.5.1 BufferedInputStream

在这里插入图片描述
如上图,读取字节流时不是直接从处理流(这里用ByteArrayInputStream来作为实际的介质流)获取,而是从BufferedInputStream装饰流中的缓冲区获取
在这里插入图片描述
普通的介质流是单个顺序读取的,每次获取一个容量单位的字节,我们知道的是在操作系统中文件是也是按照字节进行存储的,读取系统文件每次读一次就进行一次操作系统和应用之间的IO交互是非常耗时的,而BufferedInputStream的作用就是增加一层缓冲区域,它的处理逻辑不是一个个读取,而是每次读多个先缓存到缓冲区,将多次IO降低,外部不需要和底层直接交互IO,这是典型的空间换时间的思想,增加BufferedInputStream的缓冲区空间提前批量读取来避免频繁底层IO提高性能

3.2.6 ObjectInputStream

ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流,只不过他继承InputStream而不是FilterInputStream。它的作用是写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化,进行持久化的类必须要实现Serializable接口,这是Java提供的序列化方式。

  • 底层依然使用字节数组byte[] 进行存储
  • 定义了序列化的格式,classDesc、class、handler等
  • 写入(序列化) 是按照顺序存放的,因此写出(反序列化) 也要按照顺序读取
  • transient 关键字修饰的类字段不会参与序列化,会被忽略

使用示例如下:

/**
 * created by guanjian on 2021/1/6 18:18
 */
public class ObjectStreamTest {

    private static final String TMP_FILE = "person.tmp";

    public static void main(String[] args) {
        testWrite();
        testRead();
    }

    private static void testWrite() {
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //1、基本数据类型
            out.writeBoolean(true);
            out.writeByte((byte) 65);
            out.writeChar('a');
            out.writeInt(20131015);
            out.writeFloat(3.14F);
            out.writeDouble(1.414D);
            out.writeUTF("我是字符串");
            //2、复杂数据结构
            Map<String, String> map = new HashMap<String, String>();
            map.put("one", "red");
            map.put("two", "green");
            map.put("three", "blue");
            out.writeObject(map);
            //3、写入自定义的Person对象,实现了Serializable接口
            Person person = new Person("zhangsan", 30, true, 'm');
            out.writeObject(person);

            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * ObjectInputStream 测试函数
     */
    private static void testRead() {
        try {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //1、基本数据类型
            System.out.printf("boolean:%b\n", in.readBoolean());
            System.out.printf("byte:%d\n", (in.readByte() & 0xff));
            System.out.printf("char:%c\n", in.readChar());
            System.out.printf("int:%d\n", in.readInt());
            System.out.printf("float:%f\n", in.readFloat());
            System.out.printf("double:%f\n", in.readDouble());
            System.out.printf("String:%s\n", in.readUTF());
            //2、复杂数据结构
            HashMap map = (HashMap) in.readObject();
            Iterator<?> iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                System.out.printf("%-6s -- %s\n", entry.getKey(), entry.getValue());
            }
            //3、读取Person对象,实现了Serializable接口
            Person person = (Person) in.readObject();
            System.out.println("Person: " + person);

            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class Person implements Serializable {

        private static final long serialVersionUID = 5512460159096320796L;

        private String name;
        private int age;
        private boolean isAlive;
        private char gender;

        public Person(String name, int age, boolean isAlive, char gender) {
            this.name = name;
            this.age = age;
            this.isAlive = isAlive;
            this.gender = gender;
        }

        public Person() {
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", isAlive=" + isAlive +
                    ", gender=" + gender +
                    '}';
        }
    }
}

3.3 OutputStream

OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

关于OutputStream的子类都是对应以上InputStream的子类,这里不再赘述。

3.4 Reader

Reader(文件格式操作):抽象类,基于字符的输入操作。

3.4.1 StringReader

在这里插入图片描述
如上图,StringReader中使用String来进行存储,读取时候会将String转换为char字符读取

3.4.2 CharArrayReader

在这里插入图片描述
如上图,CharArrayReader的底层是采用字符数组char[],char类型在内存中占2个字节-16位,其值的范围在0-65535之间

3.4.3 BufferedReader

参考BufferedInputStream部分,原理类似

3.4.4 PipedReader

参考PipedInputStream部分,原理类似

3.5 Writer

Writer(文件格式操作):抽象类,基于字符的输出操作。关于Writer的子类都是对应以上Reader的子类,这里不再赘述。

3.6 RandomAccessFile

RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

限制流向读写
InputStream字节流(字符流可转字节流)游标顺序
OutputStream字节流(字符流可转字节流)游标顺序
Reader字符流(字节流可转字符流)游标顺序
Writer字符流(字节流可转字符流)游标顺序
RandomAccessFile文件随机、指定位置读、写

4. 实例

4.1 Socket连接

单线程服务端Socket实现

服务端
/**
 * 每次只能处理一个连接请求,其他连接请求会被阻塞
 * created by guanjian on 2021/1/12 9:09
 */
public class SingleThreadBIOSocketServer {

    private static byte[] bytes = new byte[1024];

    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket socketServer = new ServerSocket();
        socketServer.bind(new InetSocketAddress(9090));

        System.out.println("Server starting ...");

        while (true) {

            System.out.println("Server waiting connect ...");
            //accept方法会阻塞
            Socket socket = socketServer.accept();
            
            long start = System.currentTimeMillis();
            System.out.println("Server begin receive data ...");
            //read方法会阻塞
            socket.getInputStream().read(bytes);
            long end = System.currentTimeMillis();
            System.out.format("Server finish receive data: %s , cost: %s \n", new String(bytes), end - start);
        }
    }
}
客户端
/**
 * 单独一个socket请求
 * created by guanjian on 2021/1/12 9:09
 */
public class SingleThreadBIOSocketClient {

    public static void main(String[] args) throws IOException {
        Socket socketClient = new Socket();
        socketClient.connect(new InetSocketAddress("127.0.0.1", 9090));
        System.out.println("Client connected ...");
		//会阻塞socket等待输入
        Scanner scanner = new Scanner(System.in);

        String input = scanner.next();

        socketClient.getOutputStream().write(input.getBytes());

        System.out.format("Client sending content:%s \n", input);
        
    }
}

多线程服务端Socket实现

服务端
/**
 * 开启多线程来接收处理Socket请求
 * 不会被阻塞socket请求,但是会耗费大量线程资源
 *
 * created by guanjian on 2021/1/12 9:09
 */
public class MultiThreadBIOSocketServer {

    private static byte[] bytes = new byte[1024];

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

        ServerSocket socketServer = new ServerSocket();
        socketServer.bind(new InetSocketAddress(9090));
        System.out.println("Server starting ...");

        while (true) {
            System.out.println("Server waiting connect ...");
            //accept方法会阻塞
            Socket socket = socketServer.accept();
            //接收请求后开启线程来处理,避免accept阻塞
            if (null != socket) {
                new SockectServerHandler(socket).start();
            }
        }
    }

    static class SockectServerHandler extends Thread {
        private Socket socket;

        public SockectServerHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                long start = System.currentTimeMillis();
                System.out.println("Server begin receive data ...");
                //read方法会阻塞
                socket.getInputStream().read(bytes);
                long end = System.currentTimeMillis();
                System.out.format("Server finish receive data: %s , cost: %s \n", new String(bytes), end - start);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
客户端
/**
 * 模拟多线程并发socket请求
 * created by guanjian on 2021/1/12 9:09
 */
public class MultiThreadBIOSocketClient {
    private static CyclicBarrier cb = new CyclicBarrier(10, () -> {
        System.out.println("===== Client Multi Threads Begin Request =====");
    });

    public static void main(String[] args) throws IOException {
        IntStream.range(0, 10).forEach(t -> {
            new SockectClient(t).start();
        });
    }

    static class SockectClient extends Thread {
        private int seq;

        public SockectClient(int seq) {
            this.seq = seq;
        }

        @Override
        public void run() {
            Socket socketClient = new Socket();
            try {
                cb.await(5, TimeUnit.SECONDS);

                socketClient.connect(new InetSocketAddress("127.0.0.1", 9090));
                System.out.println("Client connected.");

                String sendMsg = "你好 " + seq;

                System.out.format("Client sending content:%s \n", sendMsg);
                socketClient.getOutputStream().write(sendMsg.getBytes());
                socketClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

总结

通过BIO进行Socket通信会产生阻塞,阻塞点如下:

  • socket.accept() 会阻塞
  • socket.getInputStream().read(bytes) 会阻塞

4.2 File读取

5. 参考

https://blog.csdn.net/sinat_37064286/article/details/86537354
https://blog.csdn.net/ro_wsy/article/details/8207527
https://blog.csdn.net/weixin_30239339/article/details/99439679
https://www.cnblogs.com/myseries/p/10757033.html
https://blog.csdn.net/qq_40208605/article/details/80647803

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大摩羯先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值