【java】详解I/O流
目录结构:
1. File类
Java用File来访问文件、目录,以及对它们的删除、创建等。
接下来列出File类中常用的方法:
1. 访问文件名相关的方法
Sring getName():返回此File对象所表示的文件名或路径名(如果是路径则返回最后一级子路径)
String getPath():返回此File对象所对应的路径名
File getAbsoluteFile():返回此File对象所对应的绝对路径名称
String getParent():返回此File对象所对应的目录(最后一级子目录)的父目录名。
boolean renameTo(File newName):重命名此File对象所对应的的文件或是目录,如果重命名成功,则返回true,否则返回false。
2.文件检查相关的方法
boolean exists():判断File对象所对应的文件或目录是否存在。
boolean canWrite():判断File对象所对应的文件或目录是否可写。
boolean canRead():判断File对象所对应的文件或目录是否可读。
boolean isFile():判断File对象所对应的是否是文件。
boolean isDirectory():判断FIle对象所对应的是否是目录。
boolean isAbsolute():判断File所对应的文件或目录是否是绝对路径。该方法消除了不同平台的差异,可以直接判断FIle对象是否为绝对路径。在UNIX/Linux/BSD 等系统,如果路径名开头是一条斜线(/),则表明该File对象所对应一个绝对路径。在Window系统上,如果路径开头盘符,则说明它是一个绝对路径。
3.获取常规文件信息
boolean lastModified():返回文件的最后修改时间。
boolean length():返回文件内容的长度。
4.获取文件的常规信息
boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建一个该File对象所对应的新文件。如果创建成功则返回true,否则返回false。
boolean delete():删除File对象所对应的文件或是目录。
static File createTempFile(String prefix,String suffix) 在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀做文件名。这是一个静态方法,可以直接通过File类来调用。Prefix参数必须至少是3字节长。建议前缀使用一个短的,有意义的字符串。suffix 可以为null,这种情况下默认使用后缀“.temp”。
static File createTempFile(String prefix,String suffix,File directory) 在directory指定的目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。
void deleteOnExit():注册一个删除勾子,指定当Java虚拟机退出时,删除File对象所对应的文件和目录。
5.目录操作相关的方法
boolean mkdir():试图创建一个File对象所对应的目录,如果创建成功,则返回true;否则返回false,调用方法时,File对象必须对应一个路径。
String[] list() 列出File对象的所有子文件名和路径名。
File[] listFiles() 列出File对象的所有子文件和路径。
static File[] listRoots() 列出系统所有的根目录。
下面使用使用递归打印某个目录下及其子目录下的所有的文件名:
public class TestDirPrint {
    //打印指定目录中的内容,要求子目录中的内容也要打印出来
    public static void dirPrint(File f){
        //1.若f关联的是普通文件,则直接打印文件名即可
        if(f.isFile()){
            System.out.println(f.getName());
        }
        //2.若f关联的是目录文件,则打印目录名的同时使用[]括起来
        if(f.isDirectory()){
            System.out.println("[" + f.getName() + "]");
            //3.获取该目录中的所有内容,分别进行打印
            File[] ff = f.listFiles();
            for(File ft : ff){
                dirPrint(ft);
            }
        }
    }
}
2. I/O流体系
2.1 流的基本介绍
首先介绍流的分类:
按照流的方向来分,可以分为字节流和字符流。
输入流:只能读取数据。
输出流:只能向其中写入数据。
输入流和输出流都是站在程序的角度进行考虑的。
按照流处理的数据单元不同,可以分为字节流和字符流。
字节流和字符流的用法几乎一致,区别是它们操作的数据单元不同。字节流操作的数据单元是一个字节,字符流操作的数据单元是2个字节。
按照流的角色,可以分为节点流和处理流。
节点流是可以直接向特定的IO设备进行读/写操作的流,这种流也被称为低级流。处理流则用于对一个已存在的流,通过封装后的流来实现数据读/写功能,处理流被称为装饰流或高级流。
java的输入/输出流提供了近40个类,下面按照流的功能进行分类:
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | 
| 抽象基类 | InputStream | OutputStream | Reader | Writer | 
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter | 
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter | 
| 访问管道 | PipedInptStream | PipedOutoutStream | PipedReader | PipedWriter | 
| 访问字符串 | StringReader | StringWriter | ||
| 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter | 
| 转化流 | InputStreamReader | OutputStreamWriter | ||
| 对象流 | ObjectInputStream | ObjectOutputStream | ||
| 抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | 
| 打印流 | PrintStream | |||
| 推回输入流 | PushbackInputStream | PushbackReader | ||
| 特殊流 | DataInputStream | DataOutputStream | 
我们已经知道字节流的数据单元是一个字节,字符流的数据单元是两个字节。通常情况下,如果访问的是文本内容,那么应该使用字符流,如果访问的是二进制内容,那么应该使用字节流。
使用字节流访问文本内容,如果一次访问不完,那么很有可能出现乱码的情况。
例如有GBK格式编码的文本文件test.txt,内容如下:
“do not give up.不要放弃。”
这段文本中“do not give up.”一共占据15个字节。“不要放弃。”一共占了10个字节。
如果使用如下的代码来访问:
String path="C:\\Users\\dell\\Desktop\\test.txt";
FileInputStream fis=new FileInputStream(new File(path));
byte[] bytes=new byte[1024];//一次读取1024个字节
int offset=0;
String result="";
while((offset=fis.read(bytes,0,bytes.length))!=-1){
result+=new String(bytes,0,offset);
}
System.out.println(result);
由于上面的文本没有1024个字节,所以可以一次性读完,并不会出现乱码情况。
而如果改为一次读取3个字节,那么就会出现乱码。我们甚至还可以推断出,那些字符会出现乱码,由于读取数据的单位为3个字节,所以前面的“do not give up.”会分为5次读完。后面的汉字“不要放弃。”每个汉字占两个字节,为了方便理解,我们把这10个字节用一下这些字符来表示“12 34 56 78 9A”,第一次读取了三个字节123,12可以被正常解析为“不”,后面有一个字节不能被正常解析。第二此又读取三个字节456,45不能被正常解析,6也不能被正常解析。第三次又读取三个字节789,78可以被正常解析,9不能。最后读取最后一个字节A,A也不能被正常解析。所以最终汉字中只有“不”、“弃”可以被正常解析。
2.2 访问文件
在前面介绍过访问文件主要涉及到FileInputStreaam、FileOutputStream、FileReader、FileWriter,如果是二进制文件,那么使用字节流。如果是文本文件,那么使用字符流。
接下使用FileInputStream、FileOutputStream实现对文件的复制:
public class TestFileCopy {
    public static void main(String[] args) {
        try{
            //1.建立FileInputStream类的对象与源文件建立关联
            FileInputStream fis
                = new FileInputStream("D:/java09/day16/javaseday16-IO流常用的类-06.wmv");
            //2.建立FileOutputStream类的对象与目标文件建立关联
            FileOutputStream fos = new FileOutputStream("c:/javaseday16-IO流常用的类-06.wmv");
            //3.不断地读取源文件中的内容并写入到目标文件中
            /* 可以实现文件的拷贝,但是文件比较大时效率很低
            int res = 0;
            while((res = fis.read()) != -1){
                fos.write(res);
            }
            */
            //第二种方案,根据源文件的大小准备对应的缓冲区(数组),可能导致内存溢出
            //第三种方案,无论文件的大小是多少,每次都准备一个1024整数倍的数组
            byte[] data = new byte[1024 * 8];
            int res = 0;
            while((res = fis.read(data)) != -1){
                fos.write(data, 0, res);
            }
            System.out.println("拷贝文件结束!");
            //4.关闭文件输入流对象
            fis.close();
            //5.关闭文件输出流对象
            fos.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestFileCopy.java
2.3 转化流
转化流主要涉及到InputStreamReader和OutputStreamWriter,都是将字节流转化为字符流。
接下来结合输入转化流InputStreamReader和打印流PrintStream进行演示:
public class TestBufferedReaderPrintStream {
    public static void main(String[] args) {
        try{
            //1.创建BufferedReader类型的对象与键盘输入(System.in)进行关联
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(System.in));
           //有时候在这里读取网页中的中文的时候,会出现乱码,可以用如下的解决
           //  BufferedReader br = new BufferedReader(
           //       new InputStreamReader(new URL("http://www.baidu.com").openStream(),"utf-8"));
            //2.创建PrintStream类型的对象与c:/a.txt文件进行关联
            PrintStream ps = new PrintStream(new FileOutputStream("d:/a.txt"));
            //3.不断地提示用户输入并读取一行本文,并且写入到d:/a.txt中
            int flag = 1;
            while(true){
                System.out.println("请输入要发送的内容:");
                //读取用户输入的一行文本
                String str = br.readLine();
                //4.当用户输入的是"bye"时,则结束循环
                if("bye".equalsIgnoreCase(str)) break;
                /*
                   //将发送消息的时间写入到文件中
                   Date d1 = new Date();
                   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                   ps.println(sdf.format(d1));//将时间转化为字符串,写入到文件中
                   //也可以将一个字符串转化为时间对象
                  Date d2=new Date();
                  String str2="2017-05-31 17:51:04";
                  d2=sdf.parse(str2);//将字符串解析为对应的Date对象
                */
                //将str写入到文件中
                ps.println(str);
                flag++;
            }
            //5.关闭相关流对象
            ps.flush();
            ps.close();
            br.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestBufferedReaderPrintStream.java
2.4 DataInputStream 和 DataOutputStream
这两流比较特殊,他们可以对基本数据类型进行操作。
DataInputStream测试:
public class TestDataOutputStream {
    public static void main(String[] args) {
        try{
            //1.创建DataOutputStream的对象与参数指定的文件进行关联
            DataOutputStream dos = new DataOutputStream(
                    new FileOutputStream("c:/a.txt"));
            //2.将整数数据66写入文件
            dos.writeInt(88);
            //3.关闭输出流对象
            dos.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestDataOutputStream.java
DataOutputStream测试:
public class TestDataInputStream {
    public static void main(String[] args) {
        try{
            //1.创建DataInputStream类的对象与参数指定的文件关联
            DataInputStream dis = new DataInputStream(
                    new FileInputStream("c:/a.txt"));
            //2.读取文件中的一个int类型数据并打印出来
            int res = dis.readInt();
            System.out.println("res = " + res); // 88
            //3.关闭流对象
            dis.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestDataInputStream.java
2.5 对象流
对象流主要涉及到类是ObjectOutputStream和ObjectInputStream,ObjectOutputStream是用于将一个对象整体写入到输入流中,ObjectInputStream是用于从流中读取一个对象。
ObjectOutputStream测试:
public class TestObjectOutputStream {
    public static void main(String[] args) {
        try{
            //1.创建ObjectOutputStream类的对象与指定的文件关联
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("c:/user.dat"));
            //2.准备User类型的对象并进行初始化
            User user = new User("Mark", "123456", "xiaomg@163.com");
            //3.将User类型的对象整体写入到文件中
            oos.writeObject(user);
            System.out.println("写入对象成功!");
            //4.关闭输出流对象
            oos.flush();
            oos.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestObjectOutputStream.java
ObjectInputStream测试
public class TestObjectInputStream {
    public static void main(String[] args) {
        try{
            //1.创建ObjectInputStream类型的对象与指定的文件关联
            ObjectInputStream ois = new ObjectInputStream(
                    new FileInputStream("c:/user.dat"));
            //2.读取文件中的一个对象并打印出来
            Object obj = ois.readObject();
            if(obj instanceof User){
                User user = (User)obj;
                System.out.println(user);
            }
            //3.关闭流对象
            ois.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
TestObjectInputStream.java
需要注意被writeObject和readObject的对象应该序列化,因为流不能传输对象,所以只能将其状态保存在一组字节进行传输,详情可以查看Java对象序列化。
2.6 推回输入流
在java体系中有两个特殊的流,就是PushbackInputStream和PushbackReader,他们都提供了如下三个方法:
void unread(byte[]/char[] buf):将一个字节/字符数组的内容推回到缓存区里,从而允许重复读取刚刚读取的内容。
void unread(byte[]/char[] buf,int off,int len):将一个字节/字符数组里从off开始,长度为len字节/字符推回到缓冲区里,从而允许重复读取刚刚读取的内容。
void unread(int b): 将一个字节/字符推回到缓存区里,从而允许重复读取刚刚读取的内容。
推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将指定数组的内容推回到缓冲区里,而推回输入流每次调用read方法时总是先推回缓冲区里读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()方法所需的数组时才会从原输入流中读取。

在创建PushbackInputStream和PushbackReader时指定了缓冲区的大小,如果没指定那么默认的缓冲区大小是1,如果程序中推回到缓冲区的内容超过了推回缓冲区的大小,那么将会引发Pushback buffer overflow的IOException异常。
下面的案例是打印“throws”前的32个字节的数据:
public class PushBackStreamTest {
    public static void main(String[] args) throws IOException {
        try {
            // 创建PushbackReader对象,指定推回缓冲区的大小为64
            PushbackReader pr = new PushbackReader(new FileReader("PushBackStreamTest.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("throws")) > 0) {
                    // 将本次内容和上次内容一起推回缓冲区
                    pr.unread((lastContent + content).toCharArray());
                    // 重新定义一个长度为targetIndex的Char数组
                    if (targetIndex > 32) {
                        buf = new char[targetIndex];
                    }
                    // 再次读取指定长度的内容,也就是throws之前的内容
                    pr.read(buf, 0, targetIndex);
                    // 打印
                    System.out.println(new String(buf, 0, targetIndex));
                    break;
                } else {
                    lastContent = content;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
PushBackStreamTest.java
3.重定向标准输入/输出
Java的标准输入/输出是分别通过System.in和System.out来代表的,在默认情况下,它们默认代表键盘和显示器。当程序通过System.in来获取时,实际上是从键盘输入;当程序从System.out输出时,实际上是输出到屏幕。java提供可以调用系统脚本命令的实现。
System类提供了如下三个标准重定向输入/输出的方法
static void setErr(PrintStream) :从定向“标准”错误输出流
static void setIn(InputStream in):从定向“标准”输入流
static void setOut(PrintStream out):从定向“标准”输出流
下面的程序是重定向标准的输出流,将System.out的输出重定向到文件:
import java.io.File;
import java.io.IOException;
import java.io.PrintStream; public class RedirectOut {
public static void main(String[] args) {
PrintStream ps=null;
try{
//创建PrintStream输出流
ps=new PrintStream(new File("out.txt"));
//将标准数据流重定向到ps流
System.setOut(ps);
//向标准输出流输出一个字符串
System.out.println("hello world");
//向标准输出流输出一个对象
System.out.println(new RedirectOut());
}catch(IOException e){
e.printStackTrace();
}finally{
ps.close();
}
}
}
RedirectOut.java
下面的程序是重定向标准的输入流,将System.in的输入重定向到文件:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner; public class RedirectIn {
public static void main(String[] args) throws IOException {
FileInputStream fis=null;
try{
fis=new FileInputStream(new File("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 e)
{ }finally{
fis.close();
}
}
}
RedirectIn.java
4.Java虚拟机读写其他进程的数据
使用Runtime对象的exec()方法,可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程。通过该方法,java提供可以调用系统脚本命令的实现。
exec()方法提供了几个重载版本,如下:
public Process exec(String command)----- 在单独的进程中执行指定的字符串命令。
public Process exec(String [] cmdArray)--- 在单独的进程中执行指定命令和变量
public Process exec(String command, String [] envp)---- 在指定环境的独立进程中执行指定命令和变量
public Process exec(String [] cmdArray, String [] envp)---- 在指定环境的独立进程中执行指定的命令和变量
public Process exec(String command,String[] envp,File dir)---- 在有指定环境和工作目录的独立进程中执行指定的字符串命令
public Process exec(String[] cmdarray,String[] envp,File dir)---- 在指定环境和工作目录的独立进程中执行指定的命令和变量
Process类提供了如下三个进行通信的方法,用于让程序和其他子进程通信:
InputStream getErrorStream():获取子进程的错误流
InputStream getInputStream():获取子进程的输入流
OutputStream getOutputStream():获取子进程的输出流
例如:
1.RunTime.getRuntime().exec(String command);
//在windows下相当于直接调用 /开始/搜索程序和文件 的指令,比如
Runtime.getRuntime().exec("notepad.exe"); //打开windows下记事本。 2.public Process exec(String [] cmdArray);
Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",command);//Linux下
Runtime.getRuntime().exec(new String[]{ "cmd", "/c",command});//Windows下
exec方法的返回值是一个Process类型的数据,通过这个返回值,通过这个返回值就可以获取到命令的执行信息。
Process的其余几种方法:
1.destroy():杀掉子进程
2.exitValue():返回子进程的出口值,值 0 表示正常终止
3.getErrorStream():获取子进程的错误流
4.getInputStream():获取子进程的输入流
5.getOutputStream():获取子进程的输出流
6.waitFor():导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,根据惯例,0 表示正常终止
需要注意的是,在java中调用runtime线程执行脚本是非常消耗资源的,所以不要频繁调用。
需要说一说Process的waitFor方法,该方法的作用是等待子线程的结束。
Process p = Runtime.getRuntime().exec("notepad.exe");
p.waitFor();
System.out.println("--------------------------------------------我被执行了");//在手动关闭记事本软件后,才会被打印
需要注意,调用Runtime.getRuntime().exec()后,如果不及时捕捉进程的输出,会导致JAVA挂住,看似被调用进程没退出。所以,解决办法是,启动进程后,再启动两个JAVA线程及时的把被调用进程的输出截获。
class StreamGobbler extends Thread {
    InputStream is;
    String type;
    public StreamGobbler(InputStream is, String type) {
        this.is = is;
        this.type = type;
    }
    public void run() {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                if (type.equals("Error")) {
                    System.out.println("Error   :" + line);
                } else {
                    System.out.println("Debug:" + line);
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}
StreamGobbler.java
调用代码:
        try {
            Process proc = Runtime.getRuntime().exec("cmd /k start dir");
            StreamGobbler errorGobbler = new StreamGobbler(
                    proc.getErrorStream(), "Error");
            StreamGobbler outputGobbler = new StreamGobbler(
                    proc.getInputStream(), "Output");
            errorGobbler.start();
            outputGobbler.start();
            proc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
在实际的项目中,我们除了像上面那样调用,还会执行脚本文件。
    public static void main(String[] args) {
        executeCommand("dir");
    }
    // 执行一个命令并返回相应的信息
    public static void executeCommand(String command) {
        try {
            // 在项目根目录下将命令生成一份bat文件, 再执行该bat文件
            File batFile = new File("dump.bat");
            if (!batFile.exists())
                batFile.createNewFile();
            // 将命令写入文件中
            FileWriter writer = new FileWriter(batFile);
            writer.write(command);
            writer.close();
            // 执行该bat文件
            Process process = Runtime.getRuntime().exec(
                    "cmd /c " + batFile.getAbsolutePath());
            process.waitFor();
            // 将bat文件删除
            batFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
5 NIO
传统的I/O都是通过流的方式来访问数据的,输入流和输出流都是阻塞式的,比如InputStream,当调用数据的read方法时,若数据源没有数据,那么程序将会阻塞。传统的输入/输出流都是通过字节的移动来处理的(即使不直接处理字节流,但底层的实现还是依赖字节处理),也就是说,面向流的系统一次只能处理一个字节,因此面向流的输入/输出效率通常不高。
从JDK1.4开始,Java提供了一些列用于改进的输入/输出处理的新功能,这些功能被称为新IO(New IO,简称NIO),这些类都放在java.nio包及子包下面。
5.1 NIO简介
NIO和传统IO有相同的目的,都是进行输入/输出,但是新IO使用不同的方式来处理输入/输出,新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存的方式来访问文件了,通过这种方式比传统的IO速度要快许多。
Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统的输入/输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输;Channel与传统的InputStream和OutputStream的最大区别就是提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则新IO是面向块的处理。
Buffer可以理解为容器,发送到Channel中的所有对象都必须先放到Buffer中,从Channel中读取到的数据也必须先放到Buffer中。
5.2 Buffer类
Buffer就像一个数组,可以保存多个相同类型的数据。Byte是一个抽象类,其最常用的的之类是ByteBuffer类,它可以在底层数组上执行get/set操作,除此之外,其他基本数据类型都有相应的Buffer类:CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。
在Buffer中有三个比较重要的概念分别为:容量(capacity)、界限(limit)和位置(position)。
a.容量(capacity):缓冲区的容量表示该Buffer的最大数据容量。该值不可为负,创建后不能修改。
b.界限(limit):第一个不应该被读取或写入缓冲区的位置索引。位于Limit之后的数据,既不可被读,也不可被写。该值不能超过Capacity,或是小于0。
c.位置(position):用于指明下一个可以被读取数据的位置索引。
除此之外,还提供一个可选标记mark,Buffer运行直接将position定位到mark处。
他们之间的关系如下:
0<=mark<=position<=limit<=capacity
下面是读了一些数据的Buffer图:
当调用flip()方法,该方法将limit设置为position所在位置,并将position重置为0,这样就是使Buffer的读写指针又移到了开始位置。也就是说,Buffer调用flip()方法之后,Buffer为输出数据做好准备。当调用Buffer的clear()方法时,它将position设置为0,将limit设置为capacity,这样再次为向Buffer重装数据做好准备。
示例:
public class Test {
    public static void main(String[] args) {
        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("capacity:"+buff.capacity());//
        System.out.println("limit:"+buff.limit());//8
        System.out.println("position:"+buff.position());//3
        //调用flip()方法
        buff.flip();
        System.out.println("capacity:"+buff.capacity());//
        System.out.println("limit:"+buff.limit());//3
        System.out.println("position:"+buff.position());//
        System.out.println("第一个元素:"+buff.get());//a
        System.out.println("position:"+buff.position());//1
        //调用clear()后
        buff.clear();
        System.out.println("capacity:"+buff.capacity());//
        System.out.println("limit:"+buff.limit());//8
        System.out.println("position:"+buff.position());//0        
        //取出第二个元素
        System.out.println("执行clear()方法后,缓冲区并没有被清除,第三个数据:"+buff.get(1));//b
    }
}
上面介绍了Buffer类的使用,下面介绍Channel类。
5.3 Channel类
Channel类似于传统的流对象,但与传统的流对象有两个主要区别:
a.Channel可以直接指定文件的部分或全部数据映射为Buffer
b.程序不能直接访问Channel中的数据,包括读取和写入都不行,Channel只能与Buffer进行交互。
java为Channel接口提供了DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、SocketChannel等实现类,顾名思义,可以非常方便的理解这些Channel类。
所有的Channel都不应该通过构造器直接创建,而是通过传统的InputStream、OutputStream方法的getChannel来返回Channel,不同的节点流获得Channel也不一样。
下面的所有案例都是以FileChannel为介绍。
打印一个文件中的所有内容:
public class FileChannelTest {
    public static void main(String[] args) {
        File fileIn=new File("D:/test1.txt");
        FileInputStream fis=null;//文件输入流
        FileChannel fisChannel=null;//文件输入通道
        try{
            fis=new FileInputStream(fileIn);//创建文件输入流
            fisChannel=fis.getChannel();//通过FileInputStream获得FileChannel对象
            //将fisChannel里的所有的数据映射到buffer中
            MappedByteBuffer buffer=fisChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileIn.length());
            //使用GBK的字符集来创建解码器
            Charset charset=Charset.forName("GBK");
            CharBuffer cBuffer= charset.decode(buffer);
            System.out.println(cBuffer);//打印
        }catch(Exception e)
        {
            e.printStackTrace();
        }finally{
            try{
                fisChannel.close();
                fis.close();
            }catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}
上面的程序是一次性将文件的所有内容都加载到内存中,如果担心文件过大引起性能下降,那么可以分部分来获取。
例如:
      File fileIn=new File("D:/test1.txt");
        FileInputStream fis=null;//文件输入流
        FileChannel fisChannel=null;//文件输入通道
        try{
            fis=new FileInputStream(fileIn);//创建文件输入流
            fisChannel=fis.getChannel();//通过FileInputStream获得FileChannel对象
            //设置每次取数据的大小
            ByteBuffer bbf=ByteBuffer.allocate(8);
            Charset charset=Charset.forName("GBK");
            while(fisChannel.read(bbf)>0)
            {
                //锁定Buffer的空白区
                bbf.flip();
                CharBuffer cBuffer= charset.decode(bbf);
                System.out.println(cBuffer);
                //初始化Buffer,为下次取数据做好准备
                bbf.clear();
            }
        }catch(Exception e)
        {
            e.printStackTrace();
        }finally{
            try{
                fisChannel.close();
                fis.close();
            }catch(Exception e)
            {
                e.printStackTrace();
            }
        }
下面使用FileChanel实现文件的复制功能:
public class FileChannelCopyTest {
    public static void main(String[] args) {
        File fileIn=new File("D:/test1.txt");
        File fileOut=new File("D:/test2.txt");
        FileInputStream fis=null;//文件输入流
        FileOutputStream fos=null;//文件输出流
        FileChannel fisChannel=null;//文件输入通道
        FileChannel fosChannel=null;//文件输出通道
        try{
            fis=new FileInputStream(fileIn);//创建文件输入流
            fisChannel=fis.getChannel();//通过FileInputStream获得FileChannel对象、
            fos=new FileOutputStream(fileOut);
            fosChannel=fos.getChannel();//通过FileOutputStream获得FileChannel对象.
            MappedByteBuffer buffer=fisChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileIn.length());
            fosChannel.write(buffer,0);
        }catch(Exception e)
        {
            e.printStackTrace();
        }finally{
            try{
                fisChannel.close();
                fis.close();
            }catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}
FileChannelCopyTest.java
5.4 文件锁
文件锁在操作系统中是很平常的事情,如果多个运行的程序需要并发地同时修改同一个文件时,程序之间需要某种机制来通信,使用文件锁可以有效地阻止多个进程并发修改同一个文件。
在NIO中,java提供了FileLock来支持文件锁功能,在FileChannel中提供的lock()和tryLock()方法可以获得文件锁FileLock对象。
lock和tryLock方法的区别:
a.当lock试图锁定某个文件时,如果无法获得文件锁,程序将会一直阻塞;
b.tryLock是尝试锁定文件,它将直接返回而不是阻塞,如果获得文件锁,则返回文件锁,否则返回null。
lock和tryLock除了定义了无参方法,还定义了如下格式的方法:
lock(long position,long size,boolean shared):对文件从position开始,长度为size的内容加锁。
tryLock(long position,long size,boolean shared):非阻塞方式加锁,参数的作用与上一个方法类似。
当shared为true时,表明该锁是一个共享锁,它允许多个进程来读取文件,阻止其他进程获得对该文件的排他锁,shared为true只能使用在一个可读的Channel上。
当shared为false时,表明该锁是一个排他锁,它将锁住对该文件的写操作,shared为false只能使用在一个可写的Channel上。
FileLock还提供了一个isShared()方法来判断它获得的锁是否是共享锁。
下面是一个案例:
public class FileLockTest {
    public static void main(String[] args) {
        try{
            File file=new File("D:/test1.txt");
            FileChannel fc=new FileInputStream(file).getChannel();
            FileLock flock= fc.tryLock();
            Thread.sleep(10000);//锁定10秒钟
            flock.release();
        }catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}
【java】详解I/O流的更多相关文章
- Java 详解 JVM 工作原理和流程
		Java 详解 JVM 工作原理和流程 作为一名Java使用者,掌握JVM的体系结构也是必须的.说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java ... 
- 详解 I/O流
		I/O流是用于处理设备之前信息传输的流,在我们今后的学习甚至是工作中,都是十分重要的. 在我们的日常生活中,也是很常见的,譬如:文件内容的合并.设备之键的文件传输,甚至是下载软件时的断点续传,都可以用 ... 
- 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)
		方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ... 
- tomcat使用详解(week4_day2)--技术流ken
		tomcat简介 Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache.Sun和其他一些公司及个人共同开发 ... 
- 逆向工程生成的Mapper.xml以及*Example.java详解
		逆向工程生成的接口中的方法详解 在我上一篇的博客中讲解了如何使用Mybayis逆向工程针对单表自动生成mapper.java.mapper.xml.实体类,今天我们先针对mapper.java接口中的 ... 
- 详解API Gateway流控实现,揭开ROMA平台高性能秒级流控的技术细节
		摘要:ROMA平台的核心系统ROMA Connect源自华为流程IT的集成平台,在华为内部有超过15年的企业业务集成经验. 本文分享自华为云社区<ROMA集成关键技术(1)-API流控技术详解& ... 
- 面试题:JavaIO流分类详解与常用流用法实例
		Java流概念: Java把所有的有序数据都抽象成流模型,简化了输入输出,理解了流模型就理解了Java IO.可以把流想象成水流,里面的水滴有序的朝某一方向流动.水滴就是数据,且代表着最小的数据流动单 ... 
- Java IO详解(二)------流的分类
		一.根据流向分为输入流和输出流: 注意输入流和输出流是相对于程序而言的. 输出:把程序(内存)中的内容输出到磁盘.光盘等存储设备中 输入:读取外部数据(磁盘.光盘等存储设备的数据)到程序(内 ... 
- Floyd算法(三)之 Java详解
		前面分别通过C和C++实现了弗洛伊德算法,本文介绍弗洛伊德算法的Java实现. 目录 1. 弗洛伊德算法介绍 2. 弗洛伊德算法图解 3. 弗洛伊德算法的代码说明 4. 弗洛伊德算法的源码 转载请注明 ... 
随机推荐
- java多线程之间的顺序问题
			java 多线程: 这样写有问题的:这样写可以的: package com.test; import java.util.concurrent.CountDownLatch; import java. ... 
- Java归去来第1集:手动给Eclipse配置Maven环境
			一.Eclipse配置Maven 1.1.下载Maven http://maven.apache.org/download.cgi,选择对应的版本,window下载apache-maven-3.5.3 ... 
- linux 添加elasticsearch 开机重启(自启动)
			在 /etc/init.d 文件夹下建立脚本 eg:data.sh #chkconfig: 2345 80 90#description:auto_run#!bin/bashexport JAVA_H ... 
- Spring中默认bean名称的生成策略/方式修改
			最近公司项目打算模块化,其实一个原因也是为了能够整合公司多个业务的代码,比如一个资源xxx,两个业务中都有对这个资源的管理,虽然是一个资源,但是是完全不同的定义.完全不同的表.不同的处理逻辑.所以打算 ... 
- Unity3d for beginners
			tutorial addr: https://www.youtube.com/watch?v=QUCEcAp3h28 1.打开Unity3d File->newProject ->cre ... 
- 庞果英雄会第二届在线编程大赛·线上初赛:AB数
			题目链接 给定两个正整数a,b,分别定义两个集合L和R, 集合L:即把1~a,1~b中整数乘积的集合定义为L = {x * y | x,y是整数且1 <= x <=a , 1 <= ... 
- c++10进制转换为任意2-16进制数字
			#include<stdio.h> #include<stdlib.h> #include<iostream> using namespace std; int m ... 
- 《React-Native系列》44、基于多个TextInput的键盘遮挡处理方案优化
			曾经写过两篇关于在ReactNative上处理键盘遮挡输入表单TextInput的情况.建议读者能够先看看 1.<React-Native系列>33. 键盘遮挡问题处理 2.<Rea ... 
- python2和python3网络访问包
			python3 import http.client import urllib.parse python2 import httplib import urllib 
- MongoDB副本集配置系列一:安装MongoDB
			1:下载MongoDB 2.6版本 https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-2.6.9.zip 2:解压 tar ... 
