Java I/O流(1)-- BIO

I/O的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。

基于不同的IO抽象模型和交互方式,可以对IO进行简单区分:

* 传统的IO基于流模型实现,交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。也称为BIO。

* 在java1.4中引入了NIO框架,提供了Channel、Selector、Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO。

* 在Java7中,NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式,也叫AIO(Asynchronous IO)。异步IO操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

BIO

在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。

IO流的分类

根据操作对象可分为:

磁盘操作:File

字节操作:InputStream 和OutputStream

字符操作:Reader 和Writer

对象操作:Serializable

网络操作:Socket

根据处理数据类型的不同分为:字符流和字节流,字节流一次读入或读出是8位二进制,字符流一次读入或读出是16位二进制。

根据数据流向不同分为:输入流和输出流,输入流只能进行读操作,输出流只能进行写操作。

结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类:

1)节点流:直接与数据源相连,读入或读出。

常用节点流:

父 类:InputStream 、OutputStream、 Reader、Writer

文 件:FileInputStream 、FileOutputStrean 、FileReader 、FileWriter 文件进行处理的节点流

数 组:ByteArrayInputStream、ByteArrayOutputStream、 CharArrayReader 、CharArrayWriter对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)

字符串:StringReader、 StringWriter 对字符串进行处理的节点流

管 道:PipedInputStream 、PipedOutputStream、PipedReader 、PipedWriter对管道进行处理的节点流

2)处理流:直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

常用处理流:

缓冲流:BufferedInputStrean 、BufferedOutputStream、 BufferedReader、BufferedWriter增加缓冲功能,避免频繁读写硬盘。可修饰所有从节点流父类继承的对应节点流。

转换流:InputStreamReader 、OutputStreamReader实现字节流和字符流之间的转换。

数据流:DataInputStream 、DataOutputStream等-提供将基础数据类型写入到文件中,或者读取出来。

什么时候使用转换流?

1)源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。提高对文本操作的便捷。

2)一旦操作文本涉及到具体的指定编码表时,必须使用转换流。

IO流的交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,他们之间的调用是可靠的线性顺序。

BIO常见操作示例:

1)磁盘操作

File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。

递归地列出一个目录下所有文件:

public static void listAllFiles(File dir) {

    if (dir == null ||!dir.exists()) {

        return;

    }

    if (dir.isFile()) {

       System.out.println(dir.getName());

        return;

    }

    for (File file :dir.listFiles()) {

        listAllFiles(file);

    }

}

从 Java7 开始,可以使用 Paths 和 Files 代替 File。

2字节操作

实现文件复制

public static void copyFile(String src, String dist) throwsIOException {

    FileInputStream in = newFileInputStream(src);

    FileOutputStream out =new FileOutputStream(dist);

    byte[] buffer = newbyte[20 * 1024];

    int cnt;


    // read()最多读取 buffer.length 个字节

    //返回的是实际读取的个数

    //返回 -1 的时候表示读到 eof,即文件尾

    while ((cnt =in.read(buffer, 0, buffer.length)) != -1) {

        out.write(buffer, 0,cnt);

    }


    in.close();

    out.close();

}


装饰者模式

Java I/O 使用了装饰者模式来实现。以 InputStream 为例,

InputStream 是抽象组件;

FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;

FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。

实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。

FileInputStream fileInputStream = new FileInputStream(filePath);

BufferedInputStream bufferedInputStream = newBufferedInputStream(fileInputStream);

DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。

3)字符操作

编码与解码

编码就是把字符转换为字节,而解码是把字节重新组合成字符。

如果编码和解码过程使用不同的编码方式那么就出现了乱码。

GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;

UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;

UTF-16be 编码中,中文字符和英文字符都占 2 个字节。

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。

Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。

String 的编码方式

String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。

String str1 = "中文";

byte[] bytes = str1.getBytes("UTF-8");

String str2 = new String(bytes, "UTF-8");

System.out.println(str2);

在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。

byte[] bytes = str1.getBytes();

Reader Writer

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。

InputStreamReader实现从字节流解码成字符流;

OutputStreamWriter实现字符流编码成为字节流。

实现逐行输出文本文件的内容

public static void readFileContent(String filePath) throwsIOException {

    FileReader fileReader =new FileReader(filePath);

    BufferedReaderbufferedReader = new BufferedReader(fileReader);


    String line;

    while ((line =bufferedReader.readLine()) != null) {

       System.out.println(line);

    }


    //装饰者模式使得 BufferedReader 组合了一个 Reader 对象

    //在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法

    //因此只要一个 close() 调用即可

    bufferedReader.close();

}


4)对象操作

序列化

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

序列化:ObjectOutputStream.writeObject()

反序列化:ObjectInputStream.readObject()

不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。


Serializable

序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。

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

    A a1 = new A(123,"abc");

    String objectFile = "file/a1";


    ObjectOutputStreamobjectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));

   objectOutputStream.writeObject(a1);

   objectOutputStream.close();


    ObjectInputStreamobjectInputStream = new ObjectInputStream(new FileInputStream(objectFile));

    A a2 = (A)objectInputStream.readObject();

   objectInputStream.close();

    System.out.println(a2);

}


private static class A implements Serializable {

    private int x;

    private String y;


    A(int x, String y) {

        this.x = x;

        this.y = y;

    }


    @Override

    public String toString(){

        return "x =" + x + "  " + "y =" + y;

    }

}


transient

transient 关键字可以使一些属性不会被序列化。

ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。

private transient Object[] elementData;


5)网络操作

Java中的网络支持:

InetAddress:用于表示网络上的硬件资源,即IP地址;

URL:统一资源定位符;

Sockets:使用TCP协议实现网络通信;

Datagram:使用UDP协议实现网络通信。


InetAddress

没有公有的构造函数,只能通过静态方法来创建实例。

InetAddress.getByName(String host);

InetAddress.getByAddress(byte[] address);


URL

可以直接从 URL 中读取字节流数据。

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

    URL url = newURL("http://www.baidu.com");

    /*字节流*/

    InputStream is =url.openStream();

    /*字符流*/

    InputStreamReader isr =new InputStreamReader(is, "utf-8");

    /*提供缓存功能*/

    BufferedReader br = newBufferedReader(isr);


    String line;

    while ((line =br.readLine()) != null) {

        System.out.println(line);

    }

    br.close();

}


Sockets

ServerSocket:服务器端类

Socket:客户端类

服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。

Datagram

DatagramSocket:通信类

DatagramPacket:数据包类

你可能感兴趣的:(Java I/O流(1)-- BIO)