`
holdbelief
  • 浏览: 707639 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

第十四课时: 输入/输出1

阅读更多

 Java 的 IO 支持通过 java.io 包下的类和接口来支持,在 java.io 包下主要包括输入、输出两种 IO 流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流以字符来处理输入、输出操作。除此之外,Java 的 IO 流使用了一种装饰器设计模式,它将 IO 流分成底层节点流和上层处理流,其中节点流用于和底层物理存储节点直接关联——不同的物理节点获取节点流的方式可能存在一些差异,但程序可以把不同的的物理节点流包装成统一的处理流,从而允许程序用统一的输入、输出代码来读取不同物理存储节点的资源。

 一、File 类

 File 类是 java.io 包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过 File 类来完成,值得指出的是不管是文件、还是目录都可以使用 File 来操作,File 能新建、删除和重命名文件和目录,File 不能访问文件内容本身。如果希望访问文件内容本身,则需要使用输入/输出流。

 1、访问文件和目录

 可以使用 绝对路径 和 相对路径

 

 访问文件名相关方法:

 

  • String getName()
  • String getPath()
  • File getAbsoluteFile()
  • String getAbsolutePath()
  • String getParent()
  • boolean renameTo(File newName)
 文件检测相关方法:
  • boolean exists()
  • boolean canWrite()
  • boolean canRead()
  • boolean isFile()
  • boolean isAbsolute()
 获取常规文件信息
  • long lastModified()
  • long length()
 文件操作的相关方法
  • boolean createNewFile()
  • boolean delete()
  • static File createTempFile(String prefix, String suffix)
  • static File createTempFile(String prefix, String suffix, File directory)
  • void deleteOnExit()
 目录操作的相关方法
  • boolean mkdir()
  • String[] list()
  • File[] listFiles()
  • static File[] listRoots()
 示例:
package code15_1;

import java.io.File;

public class FileTest {
 public static void main(String[] args) throws Exception {
 // 以当前路径来创建一个 File 对象
 File file = new File(".");
 // 直接获取文件名,输出一点
 System.out.println(file.getName());
 // 获取相对路径的父路径可能出错,下面代码输出 null
 System.out.println(file.getParent());
 // 获取绝对路径
 System.out.println(file.getAbsolutePath());
 // 获取上一级路径
 System.out.println(file.getAbsoluteFile().getParent());
 // 在当前路径下创建一个临时文件
 File tempFile = File.createTempFile("aaa", ".txt", file);
 // 指定当 JVM 退出时删除该文件
 tempFile.deleteOnExit();
 // 以系统当前时间作为文件名来创建文件
 File newFile = new File(System.currentTimeMillis() + "");
 System.out.println("newFile 对象是否存在:" + newFile.exists());
 // 以指定 newFile 对象来创建一个文件
 newFile.createNewFile();
 // 以 newFile 对象来创建一个目录,因为 newFile 对象已经存在,所以下面的方法返回 false,即无法创建该目录
 newFile.mkdir();
 // 使用 list 方法来列出当前路径下所有文件和路径
 String[] fileList = file.list();
 System.out.println("==============当前路径下所有文件和路径如下==============");
 for(String fileName:fileList)
 {
 System.out.println(fileName);
 }
 // listRoots 静态方法列出所有的磁盘根路径
 File[] roots = File.listRoots();
 for(File root:roots)
 {
 System.out.println(root);
 }
 }
}

 2、文件过滤器
 在 File 的 list 方法中可以接受一个 FilenameFilter 参数,通过该参数可以只列出符合条件的文件。
 FilenameFilter 接口里包含了一个 accept(File dir, String name) 方法,该方法将依次对指定 File 的所有子目录、子文件夹进行迭代,如果该方法返回 true,这 list 方法会列出该子目录或者子文件夹。
 示例:
package code15_1;

import java.io.File;
import java.io.FilenameFilter;

public class FilenameFilterTest {

 /**
 * @param args
 */
 public static void main(String[] args) {
 File file = new File(".");
 String[] nameList = file.list(new MyFilenameFilter());
 for(String name : nameList)
 {
 System.out.println(name);
 }
 }

}
// 实现自己的 FilenameFilter 实现类
class MyFilenameFilter implements FilenameFilter
{

 @Override
 public boolean accept(File dir, String name) {
 // 如果文件名以 .java 结尾,或者文件对应一个路径,返回 true
 return name.endsWith(".java") || new File(name).isDirectory();
 }
}

 二、理解 Java 的 IO 流
 1、流的分类
 输入流和输出流
  • 输入流:只能从中读取数据,而不能向其写出数据
  • 输出流:只能向其写出数据,而不能从中读取数据
 Java 的输入流主要由 InputStream 和 Reader 作为基类,而输出流则主要由 OutputStream 和 Writer 作为基类。
 字节流和字符流
  • 字节流操作的最小数据单元是 8 位的字节
  • 字符流则主要由 Reader 和 Writer 作为基类
 节点流和处理流
  • 可以从/向一个特定的 IO 设备(如磁盘、网络)读/写数据的流,称为节点流,节点流常常也被称为低级流(Low Level Stream)
  • 处理流则用于对一个已存在的流进行连接或封装,通过封装后流来实现数据读/写功能。处理流也被称为高级流或者包装流
 2、流的概念模型
 Java 把所有设备里的有序数据抽象成流模型简化了输入/输出的处理,理解了流的概念模型也就了解了 Java IO
 Java 的 IO 流共涉及到 40 多个类,这 40 多个类都是从 4 个抽象基类派生出来的:
  • InputStream/Reader:所有输入流的基类,前者是字节数入流,后者是字符输入流
  • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
 三、字节流和字符流
 1、InputStream 和 Reader
 他们是所有输入流的基类, 他们两个都是抽象类.
 InputStream 里包含如下三个方法:
  • int read(): 从输入流中读取单个字节,返回所读取字节数据(字节数据可直接转换成 int 类型)
  • int read(byte[] b):从输入流中读取最多 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数
  • int read(byte[] b, int off, int len):从输入流中读取最多 len 字节的数据,并将其存储在数组 b 中,放入 b 数组中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。
 在 Reader 里包含如下三个方法:
  • int read(): 从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换成 int 类型)
  • int read(char[] cbuf): 从输入流中读取最多 cbuf.length 个字符的数据,并将其存储在字符数组 cbuf 中,返回实际读取的字符数
  • int read(char[] cbuf, int off, int len): 从输入流中读取最多 len 个字符的数据,并将其存储在字符数组 cbuf 中,放入 cbuf 数组中时,并不是从数组的起点开始,而是从 off 位置开始,返回实际读取的字符数。
 read(char[] cbuf) 或 read(byte[] b) 方法返回 -1 时,即表明到了输出流的结束点。
 正如前面提到,InputStream 和 Reader 都是抽象类,本身并不能创建实例,但它们分别有一个读取文件的输入流:FileInputStream 和 FileReader,它们都是节点流——会直接和指定文件关联。
 示例:
public class FileInputStreamTest
{
 public static void main(String[] args) throws IOException
 {
 // 创建字节数入流
 FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
 // 创建一个长度为 1024 的“竹筒”
 byte[] bbuf = new byte[1024];
 // 用于保存实际读取的字节数
 int hasRead = 0;
 // 使用循环来重复 “取水” 过程
 while ((hasRead = fis.read(bbuf)) > 0)
 {
 // 取出 “竹筒” 中的水滴(字节),将字节数组转换成字符串输入
 System.out.print(new String(bbuf, 0, hasRead));
 }
 // 关闭文件输入流,放在 finally 块里更安全
 fis.close();
 }
}

 FileReader 示例:
public class FIleReaderTest
{
 public static void main(String[] args) throws IOException
 {
 FileReader fr = null;
 try
 {
 // 创建字符输入流
 fr = new FileReader("FileReaderTest.java");
 // 创建一个长度为 32 的 “竹筒”
 char[] cbuf = new char[32];
 // 用于保存实际读取的字符数
 int hasRead = 0;
 // 使用循环来重复 “取水” 过程
 while ((hasRead = fr.read(cbuf)) > 0)
 {
 // 取出 “竹筒” 中的水滴(字节),将字符数组转换成字符串输入
 System.out.print(new String(cbuf, 0, hasRead));
 }
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 }
 finally
 {
 // 使用 finally 块来关闭文件输入流
 if (fr != null)
 {
 fr.close();
 }
 }
 }
}
 除此之外,InputStream 和 Reader 还支持如下几个方法来移动记录指针:
  • void mark(int readAheadLimit): 在记录指针当前位置记录一个标记 (mark)
  • boolean markSupported(): 判断此输入流是否支持 mark() 操作, 即是否支持记录标记
  • void reset():将此流的记录指针重新定位到上一次记录标记 (mark) 的位置
  • long skip(long n): 记录指针向前移动 n 个字节/字符
 2、OutputStream 和 Writer
 OutputStream 和 Writer 两个流都提供了如下三个方法:
  • void write(int c): 将指定的字节/字符输出到输出流中, 其中 c 既可以代表字节,也可以代表字符.
  • void write(byte[]/char[] buf): 将字节数组/字符数组中的数据输出到指定的输出流中
  • void write(byte[]/char[] buf, int off, int len): 将字节数组/字符数组中从 off 位置开始,长度为 len 的字节/字符输出到输出流中
 示例:
public class FileOutputStreamTest
{
 public static void main(String[] args) throws IOException
 {
 FileInputStream fis = null;
 FilrOutputStream fos = null;
 try
 {
 // 创建字节数入流
 fis = new FileInputStream("FileOutputStreamTest.java");
 // 创建字节输出流
 fos = new FileOutputStream("newFile.txt");
 byte[] bbuf = new byte[32];
 int hasRead = 0;
 // 循环从输入流中取出数据
 while ((hasRead = fis.read(bbuf)) > 0)
 {
 // 每读取一次,即写入文件输出流, 读了多少, 就写多少
 fos.write(bbuf, 0, hasRead);
 }
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 }
 finally
 {
 // 使用 finally 块来关闭文件输入流
 if (fis != null)
 {
 fis.close();
 }
 // 使用 finally 块来关闭文件输出流
 if (fos != null)
 {
 fos.close();
 }
 }
 }
}

 如果希望直接输出字符串内容, 则使用 Writer 会有更好的效果, 示例:
public class FileWriterTest
{
 public static void main(String[] args) throws IOException
 {
 FileWriter fw = null;
 try
 {
 // 创建字符输出流
 fw = new FileWriter("poem.txt");
 fw.write("aaa\r\n");
 fw.write("bbb\r\n");
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 }
 finally
 {
 // 使用 finally 块来关闭文件输出流
 if (fw != null)
 {
 fw.close();
 }
 }
 }
}

 四、输入/输出流体系
 1、处理流的用法
 使用处理流的典型思路是:使用处理流来包装节点流,程序通过处理流来执行输入/输出功能, 让节点流与底层的 I/O 设备、文件交互.
 识别处理流非常简单, 只要流的构造器参数不是一个物理节点, 而是一个已经存在的流, 那么这种流就一定是处理流; 而所有节点流都是直接以物理 IO 节点作为构造器参数的.
 示例: 使用 PrintStream 处理流来包装 OutputStream, 使用处理流后的输出流在输出时将更加方便.
public class PrintStreamTest
{
 public static void main(String[] args) throws IOException
 {
 PrintStream ps = null;
 try
 {
 // 创建一个接点输出流: FileOutpuptStream
 FileOutputStream fos = new FileOutputStream("test.txt");
 // 以 PrintStream 来包装 FileOutputStream 输出流
 ps = new PrintStream(fos);
 // 使用 PrintStream 来执行输出
 ps.println("普通字符串");
 // 直接使用 PrintStream 输出对象
 ps.println(new PrintStreamTest());
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace(ps);
 }
 finally
 {
 ps.close();
 }
 }
}
 2、输入/输出流体系
 Java 的输入输出流体系提供了近 40 个类
输入/输出流体系
分类
字节输入流
字节输出流
字符输入流
字符输出流
抽象基类
InputStream
OutputStream
Reader
Writer
访问文件
FileInputStream
FileOutputStream
FileReader
FileWriter
访问数组
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
访问管道
PipedInputStream
PipedOutputStream

PipedReader

PipedWriter
访问字符串


StringReader
StringWriter
缓冲流
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
转换流


InputStreamReader
OutputStreamWriter
对象流
ObjectInputStream
ObjectOutputStream


抽象基类
FilterInputStream
FilterOutputStream
FilterReader
FilterWriter
打印流

PrintStream

PrintWriter
退回输入流
PushbackInputStream

PushbackReader

特殊流
DataInputStream
DataOutputStream



上面表格中列出了一种以数组为物理节点的节点流, 字节流以字节数组为节点, 字符流以字符数组为节点; 与此类似的是, 字符流还可以使用字符串作为为物理节点, 用于实现从字符串读取内容, 或将内容写入字符串(实际上用 StringBuffer 充当了字符串)的功能.
示例:
public class StringNodeTest
{
public static void main(String[] args) throws Exception
{
String src = "SELECT * FROM USERS WHERE (1 = 1) \r\n"
+ "AND (username = ?) \r\n"
+ "AND (password = ?) \r\n"
+ "AND (address LIKE ?) \r\n";
StringReader sr = new StringReader(src);
char[] buffer = new char[32];
int hasRead = 0;
try
{
// 采用循环读取的访问读取字符串
while ((hasRead = sr.read(buffer)) > 0)
{
System.out.print(new String(buffer, 0, hasRead));
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
sr.close();
}

// 创建 StringWriter 时, 实际上以一个 StringBuffer 作为输出接点
// 下面指定的 20 就是 StringBuffer 的初始长度
StringWriter sw = new StringWriter(20);
sw.write("SELECT * FROM EMP \r\n");
sw.write("WHERE (1 = 1) \r\n");
sw.write("AND (add LIKE ?) \r\n");

System.out.println("------------- 下面是 sw 的字符串节点里的内容 -----------");
// 使用 toString 方法返回 StringWriter 的字符串节点的内容
System.out.println(sw.toString());
}
}
3、转换流
输入/输出流体系里还提供了 2 个转换流, 这两个转换流用于实现将字节流转换成字符流
  • InputStreamReader 将字节输入流转换成字符输入流
  • OutputStreamReader 将字节输出流转换成字符输出流
示例: 下面是以获取键盘输入为例介绍转换流的用法, Java 使用 System.in 代表标准输入, 即键盘输入, 但这个标准输入流是 InputStream 类实例, 使用不方便, 而我们知道键盘输入内容都是文本内容, 所以我们可以使用 InputStreamReader 将其转化成字符输入流, 普通 Reader 读取输入内容时依然不方便, 我们可以将普通 Reader 再次包装成 BufferedReader, 利用 BufferedReader 的 readLine 方法可以一次读取一行的内容.
public class KeyinTest
{
public static void main(String[] args)
{
BufferedReader br = null;
try
{
// 将 System.in 对象转化成 Reader 对象
InputStreamReader reader = new InputStreamReader(System.in);
// 将普通 Reader 包装成 BufferedReader
br = new BufferedReader(reader);
String buffer = null;
while ((buffer = br.readLine()) != null)
{
// 如果循环方式来一行一行的读取
if (buffer.equals("exist"))
{
System.exit(1);
}
// 打印读取的内容
System.out.println("输入内容为:" + buffer);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
// 关闭输入流
finally
{
try
{
br.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}

4、推回输入流
在输入/输出流体系中, 有两个特殊的流与众不同, 就是 PushbackInputStream 和 PushbackReader, 它们分别提供了如下三个方法:
  • void unread(byte[]/char[] buf): 将一个字节/字符数组内容推回到推回缓冲区里, 从而允许重复读取刚刚读取的内容
  • void unread(byte[]/char[] b, int off, int len): 将一个字节/字符数组里从 off 开始, 长度为 len 字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容
  • void unread(int b): 将一个字节/字符推回到推回缓冲区里, 从而允许重复读取刚刚读取的内容
这三个方法实际上和三个 read 方法是一一对应的, 这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的 unread 方法时, 系统将会把指定数组的内容推回到该缓冲区里, 而退回输入流每次调用 read 方法时总是先从推回缓冲区读取, 只有当完全读取了推回缓冲区内的内容后, 而且还没有装满 read 所需的数组时才会从原输入流中读取. 当我们创建一个 PushbackInputStream 和 PushbackReader 时, 需要指定推回缓冲区的大小, 默认的推回缓冲区的长度为 1. 如果程序中推回到推回缓冲区的内容超出了推回缓冲区的大小, 程序将引发 Pushback buffer overflow 的 IOException.
示例: 
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

public class PushbackTest
{
public static void main(String[] args)
{
PushbackReader pr = null;
try
{
// 创建一个 PushbackReader 对象, 指定推回缓冲区的长度为 64
pr = new PushbackReader(new FileReader("src\\PushbackTest.java"), 64);
char[] buf = new char[32];
// 用以保存上次读取的字符串内容
String lastContent = "";
int hasRead = 0;
// 循环读取文件内容
while ((hasRead = pr.read(buf)) > 0)
{
// 将读取的内容转成字符串
String content = new String(buf, 0, hasRead);
int targetIndex = 0;
// 将上次读取的字符串和本次读取的字符串拼起来, 查看是否包含目标字符串
// 如果包含目标字符串
if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0)
{
// 将本次内容和上次内容一起推回缓冲区
pr.unread((lastContent + content).toCharArray());
// 再次读取指定长度的内容 (就是目标字符串之前的内容)
char[] buf2 = new char[targetIndex];
pr.read(buf2, 0, targetIndex);
// 打印读取的内容
System.out.print(new String(buf2, 0, buf2.length));
System.exit(1);
}
else
{
// 打印上次读取的内容
System.out.print(lastContent);
// 将本次读取的内容设为上次读取的内容
lastContent = content;
}
}
}
catch (IOException ioe) 
{
ioe.printStackTrace();
}
finally
{
try
{
if (pr != null)
{
pr.close();
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}

、重定向标准输入/输出
Java 的标准输入/输出分别通过 System.in 和 System.out 来代表, 默认情况下它们分别代表键盘和显示器.
在 System 类里提供了三个重定向标准输入/输出的方法:
  • static void setErr(PrintStream err): 重定向 "标准" 错误输出流
  • static void setIn(InputStream in): 重定向 "标准" 输入流
  • static void setOut(PrintStream out): 重定向 "标准" 输出流
示例: 下面程序通过重定向标准输出流, 将 System.out 的输出重定向向文件输出, 而不是在屏幕上输出
public class RedirectOut
{
public static void main(String[] args)
{
PrintStream ps = null;
try
{
// 一次性创建 PrintStream 输出流
ps = new PrintStream(new FileOutputStream("out.txt"));
// 将标准输出重定向到 ps 输出流
System.setOut(ps);
// 向标准输出输出一个字符串
System.out.println("普通字符串");
// 项标准输出输出一个对象
System.out.println(new RedirectOut());
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
finally
{
if (ps != null)
{
ps.close();
}
}
}
}
示例: 下面程序重定向标准输入, 从而可以将 System.in 重定向到指定文件, 而不是键盘输入.
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;

public class RedirectIn
{
public static void main(String[] args)
{
FileInputStream fis = null;
try
{
fis = new FileInputStream("src/RedirectIn.java");
// 将标准输入重定向到 fis 输入流
System.setIn(fis);
// 使用 System.in 创建 Scanner 对象, 用于获取标准输入
Scanner sc = new Scanner(System.in);
// 增加下面一行将只把回车作为分隔符
sc.useDelimiter("\n");
// 判断是否还有下一个输入项
while (sc.hasNext())
{
// 输出输入项
System.out.println(sc.next());
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
if (fis != null)
{
try
{
fis.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}
}

六、Java 虚拟机读写其他进程的数据
Runtime 对象的 exec 方法可以运行平台上的其他程序, 该方法产生一个 Process 对象, Process 对象代表由该 Java 程序启动的子进程, Process 类提供了如下三个方法, 用于让程序和其子进程进行通信:
  • InputStream getErrorStream(): 获取子进程的错误信息
  • InputStream getInputStream(): 获取子进程的输入流
  • OutputStream getOutputStream(): 获取子进程的输出流
示例:
public class ReadFromProcess
{
public static void main(String[] args)
{
BufferedReader br = null;
try
{
// 运行 javac 命令, 返回运行该命令的子进程
Process p = Runtime.getRuntime().exec("javac");
// 以 p 进程的错误流创建 BufferedReader 对象
// 这个错误流对本程序是输入流, 对 p 进程则是输出流
br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String buff = null;

while ((buff = br.readLine()) != null)
{
System.out.println(buff);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
try
{
if (br != null)
{
br.close();
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}

九. Java 新 IO
前面介绍 BufferedReader 时介绍到她的一个特性, 当 BufferedReader 读取输入流中的数据时, 如果没有读到有效数据时, 程序将在此处阻塞该线程的执行 (使用 InputStream 的 read 方法从流中读取数据时, 如果数据源中没有数据, 他也会阻塞线程), 也就是前面介绍的输入, 输出流都是阻塞式的输入, 输出. 不仅如此, 传统的输入, 输出流都是通过字节的移动来处理的(即使我们可因不直接去处理字节流, 但底层的实现还是依赖于字节处理), 也就是说面向流的输入/输出系统一次只能处理一个字节, 因此面向流的输入/输出系统通常效率不高.
从 JDK 1.4 开始, Java 提供了一些列改进的输入/输出处理的新特性, 这些功能通常被称为新 I/O (New I/O), 这些类都被放在 java.nio 包及其子包下, 并且对原 java.io 包中的很多类似类以 NIO 为基础进行了改写, 新增了满足新 IO 的功能.
1. Java 新 IO 概述
新 IO 采用内存映射文件的方式来处理输出/输出, 新 IO 将文件或文件的一段区域映射到内存中, 这样就可以向访问内存一样访问文件了.
Java 中 NIO 相关的包如下:
  • java.nio 包: 主要提供了一些和 Buffer 相关的类
  • java.nio.channels 包: 主要包括 Channel 和 Selector 相关的类
  • java.nio.charset 包: 主要包含和字符集相关的类
  • java.nio.channels.spi 包: 主要包含提供 Channel 服务的类
  • java.nio.charset.spi 包: 主要包含提供字符集服务的相关类
Channel (通道) 和 Buffer (缓冲) 是新 IO 中两个核心对象, Channel 是对传统输入/输出系统中的模拟, 在新 IO 系统中所有数据都需要通过通道传输; Channel 与传统的 InputStream, OutputStream 最大的区别在于它提供了一个 map 方法, 通过该 map 方法可以直接将 "一块数据" 映射到内存中, 如果说传统的输入/输出系统是面向流的处理, 而新 IO 则是面向块的处理.
Buffer 可以被理解为一个容器, 它的本质是一个数组, 发送到 Channel 中的所有对象都必须先放到 Buffer 中, 而从 Channel 中读取的数据也必须先读到 Buffer 中.
除了 Channel 和 Buffer 之外, 新 IO 还提供了用于将 UNICODE 字符串映射成字节序列以及逆映射操作的 Charset 类, 还提供了用于支持非阻塞式输入/输出 的 Selector 类.
2、使用 Buffer
从结构上看, Buffer 就像一个数组, 他可以保存多个类型相同的数据. Buffer 是一个抽象类, 其最常用的子类是 ByteBuffer, 他可以在底层字节数组上进行 get/set 操作, 除了 ByteBuffer 之外, 对应其他基本数据类型 (boolean 除外) 都有相应的 Buffer 类: ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer.
上面这些 Buffer 类, 除了 ByteBuffer 之外, 他们都采用了相同或者类似的方法来管理数据, 只是各自管理的数据类型不同, 这些 Buffer 都没有提供构造器, 通过使用如下方法得到一个 Buffer 对象:
  • static XxxBuffer allocate(int capacity): 创建一个容量为 capacity 的 XxxBuffer 对象
其中 ByteBuffer 类还有一个子类: MappedByteBuffer, 它用于表示 Channel 将磁盘文件的部分或全部映射到内存中后得到的结果, 通常 MappedByteBuffer 对象由 Channel 的 map 方法返回.
在 Buffer 中有三个重要的概念: 容量(capacity), 界限(limit) 和位置(position):
  • 容量 (capacity): 
  • 界限 (limit): 第一个不应该被读出或者写入的缓冲区位置索引, 也就是说, 位于 limit 后的数据既不可被读也不可被写
  • 位置 (position): 用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于 IO 流中的记录指针). 
除此之外, Buffer 里还可以支持一个可选标记 (mark, 类似传统 IO 流中 mark), 该 mark 允许程序直接将 position 直接定位到该 mark 处, 这些只满足:
0 <= mark <= position <= capacity
Buffer 的主要作用就是装入数据, 然后传输数据, 开始时 Buffer 的 position 为 0, limit 为 capacity, 程序调用
put 不断地向 Buffer 中放入数据(或者从 Channel 中获取一些数据), 每放入一些数据, Buffer 的 position 相应地向后
移动一些位置.
当 Buffer 装入数据结束后, 调用 Buffer 的 flip 方法, 该方法将 limit 设置为 position 所在的位置, 将 position 设置为 0,这样使得从 Buffer 中读数据时总是从 0 开始, 读完刚刚装入的所有数据即结束, 也就是说 Buffer 调用 flip 方法后, Buffer 为输出数据做好了准备. 当 Buffer 输出数据结束后, Buffer 调用 clear 方法, clear 方法不是清空 Buffer 的数据, 它仅仅将 position 置为 0 , 将 limit 置为 capacity, 这样为再次向 Buffer 重装如数据做好准备.
除此之外, Buffer 还包含如下一些常用的方法:
  • int capacity()
  • boolean hasRemaining(): 判断当前位置(position)和界限(limit)之间的元素是否可供处理
  • int limit(): 返回 Buffer 的界限 (limit) 的位置
  • Buffer limit(int newLt): 重新设置界限 (limit)的值, 并返回一个具有新的 limit 的缓冲区对象
  • Buffer mark(): 设置 Buffer 的 mark 的位置, 他只能在 0 和 位置 (position) 之间做 mark
  • int position(): 返回 Buffer 中的当前位置
  • Buffer position(int newPs): 设置 Buffer 的新位置, 并返回一个具有改变 position 后的 Buffer 对象
  • int remaining(): 返回当前位置和界限(limit)之间的元素个数
  • Buffer reset(): 将位置(position)转到 mark 所在的位置
  • Buffer rewind(): 将位置 (position) 设置为 0, 取消设置的 mark
除了这些移动 position, limit,mark 的方法之外, Buffer 的所有子类还提供了两类重要的方法: put 和 get 方法, 用于向 Buffer 中放入数据和从 Buffer 中取出数据. 当使用 put 和 get 方法来放入, 取出数据时, Buffer 既支持对单个数据的访问, 也支持对批量数据的访问 (以数组作为参数)
当使用 put 和 get 来访问 Buffer 中的数据时, 分为相对和绝对两种:
  • 相对 (Relative): 从 Buffer 的当前位置读取或写入数据, 然后将位置 (position) 的值按处理元素的个数增加
  • 绝对 (Absolute): 直接根据索引来向 Buffer 中读取或写入数据, 使用绝对方式来访问 Buffer 里的数据时, 并不会影响位置 position 的值.
示例:
import java.nio.CharBuffer; public class BufferTest { public static void main(String[] args) { // 创建 Buffer CharBuffer buff = CharBuffer.allocate(8); System.out.println("capacity: " + buff.capacity()); System.out.println("limit: " + buff.limit()); System.out.println("position: " + buff.position()); // 放入元素 buff.put('a'); buff.put('b'); buff.put('c'); System.out.println("加入三个元素后, position = " + buff.position()); // 调用 flip() 方法 buff.flip(); System.out.println("执行 flip() 方法后, limit = " + buff.limit()); System.out.println("执行flip() 方法后, position = " + buff.position()); // 取出第一个元素 System.out.println("第一个元素 (position = 0): " + buff.get()); System.out.println("取出第一个元素后, position = " + buff.position()); // 调用 clear 方法 buff.clear(); System.out.println("执行 clear() 方法后, limit = " + buff.limit()); System.out.println("执行 clear() 方法后, position = " + buff.position()); System.out.println("执行 clear() 方法后, 缓冲区内容并没有被清除: " + buff.get(2)); System.out.println("执行绝对读取后, position = " + buff.position()); } }
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics