文件IO流编程

一、继承体系

二、概念

1)定义

1、流的定义

流:数据在数据源(文件)和程序(内存)之间经历的路程

输入流:数据从数据源(文件)到程序(内存)的路径

输出流:数据从程序(内存)到数据源(文件)的路径

Java中流分为两种流

字节流:可以用于读写二进制文件以及任何类型文件(以字节为单位去读取,byte)

字符流:可以用于读写文本文件,不能操作二进制文件(用记事本可以打开的文件就叫文本文件,以字符为单位去读取,char)

扫描二维码关注公众号,回复: 1709888 查看本文章

2、框架图


3、判断输入流还是输出流

   以内存为参照,如果数据是向内存流动(内存占用增大),则是输入流;如果数据是向内存流出(内存占用减小),则是输出流

2)字符流与字节流的区别与转换流

区别:

·       读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位。

·       处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

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

转换流:

转换流的特点:

1.     其是字符流和字节流之间的桥梁

2.     可对读取到的字节数据经过指定编码转换成字符

3.     可对读取到的字符数据经过指定编码转换成字节

何时使用转换流?

1.     当字节和字符之间有转换动作时;

2.     流操作的数据需要编码或解码时。

具体的对象体现:

1.     InputStreamReader:字节到字符的桥梁

2.     OutputStreamWriter:字符到字节的桥梁

3)相关类与接口

相关类:File、RandomAccessFile、InputStream、OutputStream、Reader、Writer

接口:Serializable

File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 

RandomAccessFile类

该对象特点:

1.     该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。

2.     该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。

三、对象序列化

所有分布式尝尝需要跨平台、跨网络,因此要求所有传的参数、返回值都必须实现序列化

1)定义

序列化:把Java对象转换为字节序列的过程

反序列化:把字节序列转换为Java对象的过程

2)用途

1、把对象的字节序列永久地保存到硬盘上,通常存放在一个文件仲(持久化对象)

2、在网络上传送对象的字节序列(网络传输对象)

3)实现接口

序列化:ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中

反序列化:ObjectInputStream代表对象输入流,它的readObject()方法从一个输出流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

四、NIO

1)Channel

Channel和传统IO中的Stream很相似。虽然很相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写;

  以下是常用的几种通道:

·       FileChannel   可以从文件读或者向文件写入数据;无法设置为非阻塞模式

·       SocketChanel 以TCP来向网络连接的两端读写数据;

·       ServerSocketChannel  能够监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写;

·       DatagramChannel 以UDP协议来向网络连接的两端读写数据。

2)Buffer

是一个容器,是一个连续数组。

上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。

在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有:

·       ByteBuffer

·       IntBuffer

·       CharBuffer

·       LongBuffer

·       DoubleBuffer

·       FloatBuffer

·       ShortBuffer

  如果是对于文件读写,上面几种Buffer都可能会用到。但是对于网络读写来说,用的最多的是ByteBuffer。

缓冲区内部细节

状态变量

可以用三个值指定缓冲区在任意时刻的状态:

  • position  跟踪从缓冲区中写了、获取了多少数据
  • limit  表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
  • capacity表明可以储存在缓冲区中的最大数据容量。指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。

position 总是小于或者等于 limit。

limit 决不能大于 capacity。

总结:position<limit<capacity

访问方法:

flip

现在我们要将数据写到输出通道中。在这之前,我们必须调用 flip() 方法。这个方法做两件非常重要的事:

  1. 它将 limit 设置为当前 position。
  2. 它将 position 设置为 0。

clear

最后一步是调用缓冲区的 clear() 方法。这个方法重设缓冲区以便接收更多的字节。 Clear 做两种非常重要的事情:

  1. 它将 limit 设置为与 capacity 相同。
  2. 它设置 position 为 0。

3)Selector

Selector的作用就是用来轮询(检测)每个注册的Channel,一旦发现Channel有注册的时间发生,便获取事件然后进行处理(一个线程吹多个Channel,因为多线程存在线程上下文切换,因此能减少系统开销)

4) Scatter&Gather

分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。

scatter/ gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息

ScatteringReads(不适用于动态消息)

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body   =ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

Gathering Writes(适合于动态消息)


ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

5) 通道之间的数据传输

在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。

transferFrom()

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);

transferTo()

transferTo()方法将数据从FileChannel传输到其他的channel中。

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel(); 
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);

6) NIO与IO的主要区别与选择

主要区别:

1、 面向流与面向缓冲

2、 阻塞与非阻塞IO

3、 选择器(Selectors)

选择IO或NIO考虑的几个方面:

1、 API调用

2、 数据处理(流或者块(但是必须要读完才能操作))

3、 用来处理数据的线程数

7) FileChannel、SocketChannel&ServerSocketChannel用法

FileChannel:

打开FileChannel

在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。

从FileChannel读取数据

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

向FileChannel写数据

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
   channel.write(buf);
}

关闭FileChannel

channel.close();

FileChannel的position方法

long pos = channel.position();
channel.position(pos +123);
如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志

FileChannel的size方法

long fileSize = channel.size();

FileChannel的truncate方法

channel.truncate(1024);

SocketChannel:

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:

1.  打开一个SocketChannel并连接到互联网上的某台服务器。

2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

打开 SocketChannel

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

从 SocketChannel 读取数据

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。

写入 SocketChannel

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
    channel.write(buf);
}

关闭 SocketChannel

socketChannel.close();

非阻塞模式

可以设置 SocketChannel 为非阻塞模式(non-blockingmode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。

connect()

如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(! socketChannel.finishConnect() ){
    //wait, or do something else...
}

write()

非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。

read()

非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。

非阻塞模式与选择器

非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。

ServerSocketChannel:

NIO进行服务端开发的一般步骤:

  1. 创建一个ServerSocketChannel,并配置它为非阻塞模式;
  2. 绑定监听,配置相关的TCP参数,比如backlog大小;
  3. 创建一个独立的I/O进程,用于轮询多路复用器Selector
  4. 创建Selector,将之前创建的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT事件
  5. 启动I/O线程,在一个循环体中执行Selecttor.selector()方法,轮询就绪的Channel
  6. 当轮询到就绪的Channel的时候,就需要对它的状态进行判断,如果是OP_ACCEPT状态,说明在这个时候,有客户端接入了,需要调用ServerSocketChannel.accept()方法接收新的客户端;
  7. 设置新接入的客户端链路SocketChannel为非阻塞模式,并配置一些TCP参数
  8. 将SocketChannel注册到Selector,监听OP_READ事件
  9. 如果轮询到了OP_READ事件,则说明在SocketChannel中有新的就绪数据包,这时候需要创建ByteBuffer读取数据包;
  10. 如果轮询到channel中的事件为OP_WRITE,说明还有数据包没有发送完成,需要继续进行发送

下面以一个简单的Demo来说明NIO的使用方法:

package study170301;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

importjava.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

import java.util.Set;

public class NioServer {

   public void run() {

       new Thread(new MultipexerTimeServer(8080)).start(); // 启动服务器

    }

   public static void main(String[] args) {

       new NioServer().run();

    }

   class MultipexerTimeServer implements Runnable {

       private Selector selector;

       private ServerSocketChannel serverChannel;

       private volatile boolean stop;

       public MultipexerTimeServer(int port) {

           try {

                serverChannel =ServerSocketChannel.open();

                selector = Selector.open();

               serverChannel.configureBlocking(false);

               serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册连接事件

                serverChannel.socket().bind(newInetSocketAddress(port), 1024);

                System.out.printf("Serveris started in %s:\n", port);

           } catch (IOException e) {

                e.printStackTrace();

           }

       }

       public void stop() {

           this.stop = true;

       }

       private void handleEvent(SelectionKey key) {

           // 只有在key合法的情况下,才对相应的事件做处理

           if (key.isValid()) {

                // 处理客户端的连接事件

                if (key.isAcceptable()) {

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

                    try {

                        SocketChannel sc =ssc.accept();

                       sc.configureBlocking(false);

                        sc.register(selector,SelectionKey.OP_READ); // 注册可读事件

                    } catch (IOException e) {

                        e.printStackTrace();

                    }
                }

                // 处理读事件

                if (key.isReadable()) {

                    SocketChannel socketChannel =(SocketChannel) key.channel();

                    ByteBuffer readBuffer =ByteBuffer.allocate(1024); // 创建一个1024的空间

                    try {

                        int bytes =socketChannel.read(readBuffer);

                        if (bytes > 0) {

                            readBuffer.flip();// 从缓存中出数据

                            byte[] bt = newbyte[readBuffer.remaining()]; // 创建一个byte,用来装数据

                            readBuffer.get(bt);

                           System.out.println(new String(bt)); // 输出从客户端读到的数据

                        }

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

           } else {

                System.out.println("key isnot valid...");

           }

       }

       @Override

       public void run() {

           while (!stop) {

                try {

                    selector.select(1000);

                    Set<SelectionKey>selectedKeys = selector.selectedKeys();

                   Iterator<SelectionKey> it = selectedKeys.iterator();

                    SelectionKey key = null;

                    while (it.hasNext()) {

                        key = it.next();

                       it.remove();

                        // 在这里对key进行处理

                        handleEvent(key);

                    }

                } catch (IOException e) {

                    e.printStackTrace();

                } // 设置轮询扫描的时间间隔

           }
       }
    }

}


猜你喜欢

转载自blog.csdn.net/mr_chungh/article/details/80710519