IO与NIO
一 . IO
1 . io流根据处理数据类型的不同分为字节流(InputStream和OutputStream)和字符流(Reader和Writer);根据数据流向的不同分为输入流(InputStream和Reader)和输出流(OutputStream和Writer)。
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。字节流主要是操作byte类型数据,以byte数组为主要操作对象
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。字符流主要是操作char[]或者String。
- 结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
3 .随机读取文件:类RandomAccessFile可以随机的读取一个文件中指定位置的数据。因为在文件中,所有得内容都是按照字节存放的,都有固定的保存位置. 实例化RandomAccessFile的时候需要传递File类的对象,以及文件的打开模式,r:读 w:写 rw:读写,使用rw模式时如果文件不存在,则会自动创建.RandomAccessFile不是InputStream和OutputStream继承层次结构中的一部分,它是完全独立的一个类。
public class UseRandomAccessFile { public static void store() throws IOException { RandomAccessFile raf = new RandomAccessFile("C:/Users/Desktop/output.txt", "rw"); for (int i = 0; i < 7; i++) { raf.writeDouble(i * 1.414); } raf.writeUTF("----------> the end of file"); raf.close(); } public static void display() throws IOException { RandomAccessFile raf = new RandomAccessFile("C:/Users/Desktop/output.txt", "r"); for (int i = 0; i < 7; i++) { System.out.println("value " + i + " : " + raf.readDouble()); } System.out.println(raf.readUTF()); raf.close(); } public static void main(String[] args) throws IOException { store(); display(); } }
4 .InputStream
,OutputStream
,Reader
,Writer
都是抽象类。
ByteArrayInputStream
、StringBufferInputStream
、FileInputStream
是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。ByteArrayOutputStream
、FileOutputStream
是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据PipedInputStream
是从与其它线程共用的管道中读取数据,PipedOutputStream
是向与其它线程共用的管道中写入数据。ObjectInputStream
/ObjectOutputStream
和所有FilterInputStream
/FilterOutputStream
的子类都是装饰流(IO流大量使用了设计模式中的装饰器模式)。OutputStreamWriter
和InputStreamReader
用于实现字节流和字符流之间的相互转化。可对读取到的字节/字符数据经过指定编码转换成字符/字节。这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
5 .IO流的几个经典用法:
- 输入流的相关操作
public class InputFile { /** * 缓冲输入文件 * @Description * @author niepei * @param filename * @return * @throws IOException */ public static String read(String filename) throws IOException { BufferedReader in = new BufferedReader(new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); while((s=in.readLine())!=null){ sb.append(s+"\n");//readLine()会清除掉换行符,所以这里需要手动添加 } in.close(); return sb.toString(); } /** * 从内存输入 * @Description * @author niepei * @throws IOException */ public static void memoryInput() throws IOException{ //使用read()方法返回的String来创建一个StringReader对象 StringReader in = new StringReader(InputFile.read("D:/workspace/IO/src/io/InputFile.java")); int c; //使用read()方法一次读取一个字符发送给控制台 while((c=in.read())!=-1){ //read()方法返回的是int,为了正常显示需要转为char System.out.print((char)c); } } /** * 格式化的从内存输入 * @Description * @author niepei * @throws IOException */ public static void memoryInputFormat() throws IOException { DataInputStream in = new DataInputStream(new ByteArrayInputStream(read("D:/workspace/IO/src/ioInputFile.java").getBytes())); while (in.available() != 0) { System.out.print((char) in.readByte()); } } public static void main(String[] args) throws IOException { /*System.out.println(read(PATH+"InputFile.java"));*/ /*memoryInput();*/ memoryInputFormat(); } }
- 输出流的相关操作
public class OutpuFile { /** * 将文本写入文件 * @Description * @author niepei * @throws IOException */ public static void basicFileOutPut() throws IOException { BufferedReader in = new BufferedReader(new StringReader(InputFile.read("D:/workspace/IO/src/ioOutpuFile.java"))); // PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(C:/Users/Desktop/output.txt))); PrintWriter out = new PrintWriter("C:/Users/Desktop/output.txt");/*本行代码是上一行代码的简写形式,javaSE5新增了这个构造方法替代我们层层的使用装饰器*/ int lineCount = 1; String s; while((s=in.readLine())!=null){ out.println(lineCount++ +" : " + s); } out.close(); System.out.println(InputFile.read(FILE)); } /** * 数据的存储与恢复 * @Description * @author niepei * @throws IOException */ @SuppressWarnings("resource") public static void storeAndRecoveryData() throws IOException{ DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("C:/Users/Desktop/output.txt"))); out.writeUTF("pi is "); out.writeDouble(3.14159265357); out.writeUTF("Square root of 2 is "); out.writeDouble(1.414); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("C:/Users/Desktop/output.txt"))); System.out.print(in.readUTF()); System.out.println(in.readDouble()); System.out.print(in.readUTF()); System.out.println(in.readDouble()); } /** * 标准io,System.in,System.ou.System.err这三个都是PrintStream对象<字节流> * @Description * @author niepei * @throws IOException */ public static void standardIO() throws IOException{ //可以将System.out转换为PrintWriter,true代表开启自动清空功能 PrintWriter out = new PrintWriter(System.out,true); out.println("hello world!"); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String s; while((s=in.readLine())!=null){ System.out.println(s); } } public static void main(String[] args) throws IOException { /*basicFileOutPut();*/ /*storeAndRecoveryData();*/ standardIO(); } }
参考以下博客:
二 . NIO
1 . NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。
2 . NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。Channel 用于源节点与目标节点的连接, 负责缓冲区中数据的传输,Channel 本身不存储数据,因此需要配合缓冲区进行传输; Buffer 负责存储。
3 . NIO 中的Channel表示打开到 IO 设备(例如:文件、套接字)的连接。 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
4 . 通道的主要实现类
java.nio.channels.Channel 接口:
|--FileChannel
|--SocketChannel
|--ServerSocketChannel
|--DatagramChannel
Channel的用法示例:
package cn.com.io.nio; /* * 1. Java 针对支持通道的类提供了 getChannel() 方法 * 本地 IO: * FileInputStream/FileOutputStream * RandomAccessFile * * 网络IO: * Socket * ServerSocket * DatagramSocket * * 2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open() * 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel() */ public class TestChannel { /** * 1.1 利用通道完成文件的复制(非直接缓冲区) * @Description * @author niepei */ public void fileCopy1() throws IOException {// 10874-10953 long start = System.currentTimeMillis(); FileInputStream fis = new FileInputStream("d:/1.mkv"); FileOutputStream fos = new FileOutputStream("d:/2.mkv"); // ①获取通道 FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); // ②分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // ③将通道中的数据存入缓冲区中 while (inChannel.read(buf) != -1) { buf.flip(); // 切换读取数据的模式 // ④将缓冲区中的数据写入通道中 outChannel.write(buf); buf.clear(); // 清空缓冲区 } inChannel.close(); outChannel.close(); fos.close(); fis.close(); long end = System.currentTimeMillis(); System.out.println("耗费时间为:" + (end - start)); } /** * 1.2 实现将文件复制(非直接缓冲区) * @Description * @author niepei * @throws IOException */ public void fileCopy2() throws IOException{ FileChannel inchannel = FileChannel.open(Paths.get("dream.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE); FileChannel outchannel = FileChannel.open(Paths.get("drama.txt"),StandardOpenOption.CREATE, StandardOpenOption.READ,StandardOpenOption.WRITE); ByteBuffer buff = ByteBuffer.allocate(1024); while((inchannel.read(buff))!=-1){ buff.flip(); outchannel.write(buff); buff.clear(); } inchannel.close(); outchannel.close(); } /** * 2.使用直接缓冲区完成文件的复制(内存映射文件) * @Description * @author niepei * @throws IOException */ public void fileCopy3() throws IOException {// 2127-1902-1777 long start = System.currentTimeMillis(); FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // 内存映射文件 MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); // 直接对缓冲区进行数据的读写操作 byte[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); inChannel.close(); outChannel.close(); long end = System.currentTimeMillis(); System.out.println("耗费时间为:" + (end - start)); } /** * 3.通道之间的数据传输(直接缓冲区) * @Description * @author niepei * @throws IOException */ public void test3() throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // inChannel.transferTo(0, inChannel.size(), outChannel); outChannel.transferFrom(inChannel, 0, inChannel.size()); inChannel.close(); outChannel.close(); } /** * 4.分散(Scatter)与聚集(Gather) * 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中 * 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中 * * @Description * @author niepei * @throws IOException */ @SuppressWarnings("resource") public void test4() throws IOException { RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw"); // 1. 获取通道 FileChannel channel1 = raf1.getChannel(); // 2. 分配指定大小的缓冲区 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); // 3. 分散读取 ByteBuffer[] bufs = { buf1, buf2 }; channel1.read(bufs); for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("-----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); // 4. 聚集写入 RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); } /** * 5.从文件读入数据并输出到控制台 * @Description * @author niepei * @throws IOException */ public void readDataFromFile()throws IOException{ // 建立到文件的通道 FileChannel inchannel = FileChannel.open(Paths.get("dream.txt"),StandardOpenOption.READ, StandardOpenOption.WRITE); // 分配缓冲区 ByteBuffer buff = ByteBuffer.allocate(1024); while(inchannel.read(buff)!=-1){//从通道中读数据到指定的缓冲区 buff.flip();//翻转缓冲区 } byte[] bt = buff.array();//返回当前缓冲区的字节数组 System.out.println(new String(bt,0,buff.limit())); buff.clear();//清空缓冲区 inchannel.close();//关闭通道 } }
5 . 缓冲区(Buffer):在 NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据,根据数据类型不同(boolean 除外),提供了相应类型的缓冲区。
6 . 缓冲区中的四个核心属性:0 <= mark <= position <= limit <= capacity
capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)
position : 位置,表示缓冲区中正在操作数据的位置。
mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
7 . 直接缓冲区与非直接缓冲区:
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存 中。可以提高效率
Buffer的用法示例:
public class LearnBuffer { @Test public void test1(){ String str = "abcde"; //1. 分配一个指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("-----------------allocate()----------------"); System.out.println(buf.position());//0 System.out.println(buf.limit());//1024 System.out.println(buf.capacity());//1024 //2. 利用 put() 存入数据到缓冲区中 buf.put(str.getBytes()); System.out.println("-----------------put()----------------"); System.out.println(buf.position());//5 System.out.println(buf.limit());//1024 System.out.println(buf.capacity());//1024 //3. 切换读取数据模式 buf.flip(); System.out.println("-----------------flip()----------------"); System.out.println(buf.position());//0 System.out.println(buf.limit());//5 System.out.println(buf.capacity());//1024 //4. 利用 get() 读取缓冲区中的数据 byte[] bytes = new byte[buf.limit()]; buf.get(bytes); System.out.println("-----------------get()----------------"); System.out.println(new String(bytes, 0, bytes.length));//abcde System.out.println(buf.position());//5 System.out.println(buf.limit());//5 System.out.println(buf.capacity());//1024 //5. rewind() : 可重复读 buf.rewind(); System.out.println("-----------------rewind()----------------"); System.out.println(buf.position());//0 System.out.println(buf.limit());//5 System.out.println(buf.capacity());//1024 //6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态 buf.clear(); System.out.println("-----------------clear()----------------"); System.out.println(buf.position());//0 System.out.println(buf.limit());//1024 System.out.println(buf.capacity());//1024 System.out.println((char)buf.get());//a } @Test public void test2(){ String str = "abcde"; ByteBuffer buf = ByteBuffer.allocate(1024); buf.put(str.getBytes()); buf.flip(); byte[] bytes = new byte[buf.limit()]; buf.get(bytes, 0, 2); System.out.println(new String(bytes, 0, 2));//ab System.out.println(buf.position());//2 //mark() : 标记 buf.mark(); buf.get(bytes, 2, 2); System.out.println(new String(bytes, 2, 2));//cd System.out.println(buf.position());//4 //reset() : 恢复到 mark 的位置 buf.reset(); System.out.println(buf.position());//2 //判断缓冲区中是否还有剩余数据 if(buf.hasRemaining()){ //获取缓冲区中可以操作的数量 System.out.println(buf.remaining());//3 } } @Test public void test3(){ //分配直接缓冲区 ByteBuffer buf = ByteBuffer.allocateDirect(1024); System.out.println(buf.isDirect()); } }
参考博客