Java 输入/输出 I/O流 RandomAccessFile

文件的编码

文本文件就是字节序列,能够是任意编码形式。在中文操做系统上直接建立文本文件,则该文本文件只能识别ANSI编码,其余编码方式会产生乱码java

package imooc.io;

import java.io.UnsupportedEncodingException;
import java.util.Iterator;

public class EncodeDemo {

    public static void main(String[] args) throws Exception 
    {
        String player = "维斯布鲁克Westbrook";
        byte[] bs = player.getBytes();        
        // 转换成字节序列用的是项目默认的编码GBK
        for (byte b : bs) 
        {
            // 把字节(转换成了int)以16进制显式
            System.out.print(Integer.toHexString(b & 0xff) + " ");
        }
        System.out.println();
        
        byte[] bs2 = player.getBytes("gbk");
        // GBK编码中文占2个字节,英文占1个字节
        for (byte b : bs2) 
        {
            System.out.print(Integer.toHexString(b & 0xff) + " ");
        }
        System.out.println();
        
        byte[] bs3 = player.getBytes("utf-8");
        // utf-8编码中文占3个字节,英文占1个字节
        for (byte b : bs3) 
        {
            System.out.print(Integer.toHexString(b & 0xff) + " ");
        }
        System.out.println();
        
        // java是双字节编码utf-16be
        byte[] bs4 = player.getBytes("utf-16be");
        // utf-16be编码中文占2个字节,英文占2个字节
        for (byte b : bs4) 
        {
            System.out.print(Integer.toHexString(b & 0xff) + " ");
        }    
        System.out.println();
        
        /*
         * 当字节序列是某种编码时,若想把字节序列变成字符串
         * 须要采用以上编码方式,不然将出现乱码
         */
        
        // 使用项目默认编码
        String string = new String(bs4);
        System.out.println("项目默认编码:" + string);
        // 使用字符串构造的第二个参数
        String string2 = new String(bs4, "utf-16be");
        System.out.println("utf-16be编码:" + string2);
    }
}

运行结果:程序员

ce ac cb b9 b2 bc c2 b3 bf cb 57 65 73 74 62 72 6f 6f 6b 
ce ac cb b9 b2 bc c2 b3 bf cb 57 65 73 74 62 72 6f 6f 6b 
e7 bb b4 e6 96 af e5 b8 83 e9 b2 81 e5 85 8b 57 65 73 74 62 72 6f 6f 6b 
7e f4 65 af 5e 3 9c 81 51 4b 0 57 0 65 0 73 0 74 0 62 0 72 0 6f 0 6f 0 6b 
项目默认编码:~鬳痎渷QK
utf-16be编码:维斯布鲁克Westbrook

File类

文件与目录都是使用File来操做的,File能新建、删除、重命名文件和目录,File不能访问文件内容自己。若是须要访问文件内容自己,则须要使用输入/输出流数组

访问文件和目录

访问文件名相关的方法安全

  • String getName():返回此File对象所表示的文件名和路径名(若是是路径,则返回最后一级子路径名)网络

  • String getPath():返回此File对象所对应的路径名dom

  • File getAbsoluteFile():返回此File对象的绝对路径性能

  • String getAbsolutePath():返回此File对象所对应的绝对路径名测试

  • String getParent():返回此File对象所对应目录(最后一级子目录)的父路径名编码

  • boolean renameTo(File newName):重命名此File对象所对应的文件或目录,若是重命名成功,则返回true;不然返回falsespa

文件检测相关方法

  • boolean exists():判断File对象所对应的文件或目录是否存在

  • boolean canWrite():判断File对象所对应的目录或文件是否可写

  • boolean canRead():判断File对象所对应的目录或文件是否可读

  • boolean isFile():判断File对象所对应的是不是文件,而不是目录

  • boolean isDirectory():判断File对象所对应的是不是目录,而不是文件

  • boolean isAbsolute():判断File对象所对应的文件或目录是不是绝对路径。该方法消除了不一样平台的差别,能够直接判断File对象是否为绝对路径。在UNIX/Linux/BSD等系统上,若是路径名开头是一条斜线(/),则代表该File对象对应一个绝对路径;在Windows等系统上,若是路径开头是盘符,则说明它是绝对路径

获取常规文件信息

  • long lastModified():返回文件最后修改时间

  • long length():返回文件内容的长度

文件操做相关的方法

  • boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建的一个该File对象所指定的新文件,若是建立成功则返回true;不然返回false

  • boolean delete():删除File对象所对应的文件或路径

  • static File CreateTempFile(String prefix,String suffix):在默认的临时文件目录建立一个临时空文件,使用给定前缀、系统生成的随机数和给定后缀做为文件名。这是一个静态方法,能够直接经过File来调用。preFix参数必须至少是3个字节长。建议前缀使用一个短的、有意义的字符串。建议前缀使用一个短的、有意义的字符串,好比”hjb“ 或”main”. suffix参数能够为null,在这种状况下,将使用默认的后缀”.tmp”

  • static File CreateTempFile(String prefix,String suffix,File directory):在directory所指定的目录中建立一个临时空文件,使用给定前缀、系统生成的随机数和给定后缀做为文件名。这是一个静态方法,能够直接经过File来调用

  • void deleteOnExit():注册一个删除钩子,指定当Java虚拟机退出时,删除File对象随对应的文件和目录

目录操做相关方法

  • boolean mkdir(); 试图建立一个File对象所对应的目录,若是建立成功,则返回true;不然返回false. 调用该方法时File对象必须对应一个路径,而不是一个文件

  • String[] list(); 列出File对象的全部子文件名和路径名,返回String数组

  • File[] listFiles(); 列出File对象的全部子文件和路径,返回File数组

  • static File[] listRoots(); 列出系统全部的根路径。这是一个静态方法,能够直接经过File类来调用

import java.io.*;

public class FileTest
{
    public static void main(String[] args)
        throws IOException
    {
        // 以当前路径来建立一个File对象
        File file = new File(".");
        // 直接获取文件名,输出一点
        System.out.println(file.getName());
        // 获取相对路径的父路径可能出错,下面代码输出null
        System.out.println(file.getParent());
        // 获取绝对路径
        System.out.println(file.getAbsoluteFile());
        // 获取上一级路径
        System.out.println(file.getAbsoluteFile().getParent());
        // 在当前路径下建立一个临时文件
        File tmpFile = File.createTempFile("aaa", ".txt", file);
        // 指定当JVM退出时删除该文件
        tmpFile.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();
        System.out.println("====系统全部根路径以下====");
        for (File root : roots)
        {
            System.out.println(root);
        }
    }
}

文件过滤器

File类的list()方法能够接收一个FilenameFilter参数,经过该参数能够只列出符合条件的文件

FilenameFilter接口里包含一个accept(File dir, String name)方法,该方法将依次对指定的File的全部子目录或者文件进行迭代,若是该方法返回true,则list()方法将会列出该子目录或者文件

import java.io.*;

public class FilenameFilterTest
{
    public static void main(String[] args)
    {
        File file = new File(".");
        // 使用Lambda表达式(目标类型为FilenameFilter)实现文件过滤器。
        // 若是文件名以.java结尾,或者文件对应一个路径,返回true
        String[] nameList = file.list((dir, name) -> name.endsWith(".java") || new File(name).isDirectory());
        for(String name : nameList)
        {
            System.out.println(name);
        }
    }
}

遍历目录

实现类:

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

// 列出File的经常使用操做好比过滤、遍历等操做
public class FileUtils 
{
    public static void listDirectory(File dir) throws IOException 
    {
        /*
         * 列出指定目录下(包括其子目录)的全部文件
         * @param dir
         * @throws IOExcepton
         */
        if (!dir.exists()) 
        {
            throw new IllegalArgumentException("目录:" + dir + "不存在");
        }
        if (!dir.isDirectory()) 
        {
            throw new IllegalArgumentException(dir + "不是目录");            
        }
        
        String[] filenames = dir.list();    // 返回字符串数组
        for (String string : filenames) {
            System.out.println(dir + string);
        }
        
        // 遍历子目录下的内容,需构造File对象,进行递归操做
        File[] files  = dir.listFiles();        // 返回直接子目录(文件)的抽象
        if (files != null && files.length >0) 
        {
            for (File file:files) 
            {
                if (file.isDirectory()) 
                {
                    // 递归操做
                    listDirectory(file);
                }
                else 
                {
                    System.out.println(file);
                }
            }
        }
    }
}

测试类:

import java.io.File;
import java.io.IOException;

public class FileTest1 
{
    public static void main(String[] args) throws IOException 
    {
        FileUtils.listDirectory(new File("D:\\coding\\Java路径"));
    }

}

理解Java的IO流

Java的IO流是实现输入输出的基础,它能够方便地实现数据的输入/输出操做,在Java中把不一样的输入/输出源抽象为"流"(stream),经过流的方式容许Java程序使用相同的方式来访问不一样的输入/输出源。stream是从起源(source)到接收(sink)的有序数据

流的分类

输入流和输出流

按照流的流向来分,能够分为输入流和输出流:

  • 输入流:只能从中读取数据,而不能向其写入数据

  • 输出流:只能向其写入数据,而不能从中读取数据
    这里的输入、输出都是从程序运行所在内存的角度来划分的

Java的输入流主要由InputStream和Reader做为基类,而输出流则主要由OutputStream和Writer做为基类。均为抽象类,没法建立实例

字节流和字符流

字节流和字符流的用法几乎彻底同样,区别在于字节流和字符流所操做的数据单元不一样--字节流操做的数据单元是8位字节,而字符流操做的数据单元是16位的字符

字节流主要有InputStream和OutputStream做为基类,而字符流则组要由Reader和Writer做为基类

节点流和处理流

按照流的角色来分,能够分为节点流和处理流:

能够从/从一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流(Low Level Stream)。当使用节点流进行输入/输出时,程序直接链接到实际的数据源,和时间的输入/输出节点链接

clipboard.png

处理流则用于对一个已存在的流进行链接或封装,经过封装后的流来实现数据读/写功能,处理流也被称为高级流。使用处理流进行输入/输出时,程序并不会直接链接到实际的数据源,没有和实际的输入/输出节点链接

使用处理流的一个明显好处是,只要使用相同的处理流,程序就能够采用彻底相同的输入/输出代码来访问不一样的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应的发生变化

流的概念模型

  • InputStream/Reader:全部输入流的基类,前者是字节输入流,后者是字符输入流

  • OutputStream/Writer:全部输出流的基类,前者是字节输出流,后者是字符输出流

处理流的功能主要体如今如下两个方面:

  • 性能的提升:主要以增长缓冲的方式来提供输入/输出的效率

  • 操做的便捷:处理流可能提供了一系列便捷的方法来一次输入/输出大批量的内容,而不是输入/输出一个或多个“水滴”

处理流能够“嫁接”在任何已存在的流的基础之上,Java应用程序采用相同的代码、透明的方式来访问不一样的输入/输出设备的数据流

字节流和字符流

如下介绍4个访问文件的节点流用法

InputStream和Reader

InputStream和Reader是全部输入流的抽象基类,自己不能建立实例来执行输入,是全部输入流的模板,其方法全部输入流均可使用

InputStream包含以下3个方法

  • int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)

  • int read(byte[] b):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数

  • int read(byte[] b, int off, int len):从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并非从数组起点开始,而是从off位置开始,返回实际读取的字节数

在Reader中包含以下3个方法

  • int read():从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)

  • int read(char[] cbuf):从输入流中最多读取cbuf.length个字符的数据,并将其存储在字节数组cbuf中,返回实际读取的字符数

  • int read(char[] cbuf, int off ,int len):从输入流中最多读取len个字符的数据,并将其存储在数组cbuf中,放入数组cbuf中时,并非从数组起点开始,而是从off位置开始,返回实际读取的字符数

InputStream和Reader都是抽象类,自己不能建立实例,分别有一个用于读取文件的输入流:FileInputStream和FileReader,它们都是节点流——会直接和指定文件关联

使用FileInputStream读取自身:

import java.io.*;

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读取文件自己:

import java.io.*;

public class FileReaderTest
{
    public static void main(String[] args)
    {
        try(
            // 建立字符输入流
            FileReader 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 ex)
        {
            ex.printStackTrace();
        }
    }
}

InputStream和Reader移动记录指针的方法

  • void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)

  • boolean markSupported():判断输入流是否支持mark()操做,便是否支持标记记录

  • void reset():将此流的记录指针从新定位到上一次标记(mark)的位置

  • long skip(long n):记录指针向前移动n个字节/字符

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的字节/字符输出到输出流中

由于字符流直接以字符做为操做单位,因此Writer能够用字符串来代替字符数组,即以String对象做为参数。Writer里面还包含以下两个方法

  • void write(String str):将str字符串里包含的字符输出到指定输出流中。

  • void write (String str, int off, int len):将str字符串里面从off位置开始,长度为len的字符输出到指定输出流中

如下程序,使用FileInputStream执行输入,FileOutputStream执行输出,用以负责FileOutputStreamTest.java文件的功能:

import java.io.*;
public class FileOutputStreamTest
{
    public static void main(String[] args)
    {
        try(
            // 建立字节输入流
            FileInputStream fis = new FileInputStream("FileOutputStreamTest.java");
            // 建立字节输出流
            FileOutputStream 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();
        }
    }
}

使用Java的IO流执行输出时,必须关闭输出流,关闭输出流除了能够保证流的物理资源被回收以外,可能还能够将输出流缓冲区中的数据flush到物流节点里(在执行close()方法以前,自动执行输出流的flush()方法)

Writer对于直接输出字符串内容有着更好的效果

import java.io.*;
public class FileWriterTest
{
    public static void main(String[] args)
    {
        try(
            FileWriter fw = new FileWriter("AllStar.txt"))
        {
            fw.write("2016-2017赛季NBA全明星阵容\r\n");
            fw.write("西部首发:斯蒂芬-库里、詹姆斯-哈登、凯文-杜兰特、科怀-伦纳德、安东尼-戴维斯\r\n");
            fw.write("东部首发:勒布朗-詹姆斯、凯尔-欧文、扬尼斯-阿德托昆博、德玛尔-德罗赞、吉米-巴特勒\r\n");
            fw.write("西部替补:拉塞尔-威斯布鲁克、克莱-汤普森、戈登-海沃德、德拉蒙德-格林、德马库斯-考辛斯、马克-加索尔、德安德鲁-乔丹\r\n");
            fw.write("东部替补:以赛亚-托马斯、凯尔-洛瑞、肯巴-沃克、约翰-沃尔、保罗-乔治、凯文-乐福、保罗-米尔萨普\r\n");
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
}

输入/输出流体系

处理流的用法

处理流能够隐藏底层设备上节点流的差别,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操做

使用处理流的典型思路是,使用处理流来包装节点流,程序经过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互

处理流:流的构造器参数不是一个物理节点,而是已经存在的流;节点流:都是直接以物理IO节点做为构造器参数的

clipboard.png

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class PrintStreamTest 
{
    public static void main(String[] args) 
    {
        try
        (
                FileOutputStream fos = new FileOutputStream("AllStar.txt");
                PrintStream ps = new PrintStream(fos);
        ) 
        {
            // 使用PrintStream执行输出
            ps.println("全明星阵容");
            // 使用PrintStream输出对象
            ps.println(new PrintStreamTest());
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}

输出文本内容,一般将输出流包装成PrintStream后进行输出

在处理处理流包装了底层节点流以后,关闭输入/输出流资源时,只要关闭最上层的处理流便可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流

Java输入/输出流体系中经常使用的流的分类表

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 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

若是进行输入/输出的内容是文本内容,则应该考虑使用字符流;若是进行输入/输出的内容是二进制内容,则应该考虑使用字节流

转换流

转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流

Java没有将字符流转换为字节流的转换流,由于:字节流比字符流的使用范围更广,但字符流比字节流操做方便。

Java使用System.in表明标准输入,即键盘输入,但这个标准输入流是InputStream类的实例,使用不太方便,并且键盘输入内容都是文本内容,因此可使用InputStreamReader将其转换成字符输入流,普通的Reader读取输入内容时依然不太方便,咱们能够将普通的Reader再次包装成BufferedReader,利用BufferedReader的readLine()方法能够一次读取一行内容

import java.io.*;

public class KeyinTest
{
    public static void main(String[] args)
    {
        try(
            // 将Sytem.in对象转换成Reader对象
            InputStreamReader reader = new InputStreamReader(System.in);
            // 将普通Reader包装成BufferedReader
            BufferedReader br = new BufferedReader(reader))
        {
            String line = null;
            // 采用循环方式来一行一行的读取
            while ((line = br.readLine()) != null)
            {
                // 若是读取的字符串为"exit",程序退出
                if (line.equals("exit"))
                {
                    System.exit(1);
                }
                // 打印读取的内容
                System.out.println("输入内容为:" + line);
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
}

BufferedReader流具备缓冲功能,能够一次读取一行文本——以换行符为标志,若是它没有读到换行符,则程序阻塞,等到读到换行符为止

推回输入流

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()所需的数组时,才会到原输入流中读取内容

clipboard.png

import java.io.*;
public class PushbackTest
{
    public static void main(String[] args)
    {
        try(
            // 建立一个PushbackReader对象,指定推回缓冲区的长度为64
            PushbackReader pr = new PushbackReader(new FileReader("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());
                    // 从新定义一个长度为targetIndex的char数组
                    if(targetIndex > 32)
                    {
                        buf = new char[targetIndex];
                    }
                    // 再次读取指定长度的内容(就是目标字符串以前的内容)
                    pr.read(buf , 0 , targetIndex);
                    // 打印读取的内容
                    System.out.print(new String(buf , 0 ,targetIndex));
                    System.exit(0);
                }
                else
                {
                    // 打印上次读取的内容
                    System.out.print(lastContent);
                    // 将本次内容设为上次读取的内容
                    lastContent = content;
                }
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
}

重定向标准输入/输出

Java的标准输入/输出分别经过System.in和System.out来表明,在默认的状况下分别表明键盘和显示器,当程序经过System.in来得到输入时,其实是经过键盘得到输入。当程序经过System.out执行输出时,程序老是输出到屏幕

在System类中提供了三个重定向标准输入/输出的方法:

  • static void setErr(PrintStream err):重定向“标准”错误输出流

  • static void setIn(InputStream in):重定向“标准”输入流

  • static void setOut(PrintStream out):重定向“标准”输出流

重定向“标准”输入流

import java.util.*;
import java.io.*;

public class RedirectIn
{
    public static void main(String[] args)
    {
        try(
            FileInputStream fis = new FileInputStream("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 ex)
        {
            ex.printStackTrace();
        }
    }
}

重定向“标准”输出流

import java.io.*;

public class RedirectOut
{
    public static void main(String[] args)
    {
        try(
            // 一次性建立PrintStream输出流
            PrintStream ps = new PrintStream(new FileOutputStream("out.txt")))
        {
            // 将标准输出重定向到ps输出流
            System.setOut(ps);
            // 向标准输出输出一个字符串
            System.out.println("普通字符串");
            // 向标准输出输出一个对象
            System.out.println(new RedirectOut());
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
}

Java虚拟机读取其余进程的数据

使用Runtime对象的exec()方法运行平台上的其余程序并产生一个Process对象,该对象表明由该Java程序启动的子进程,Process类提供了以下3个方法,用于让程序和其子进程进行通信:

  • InputStream getErrorStream():获取子进程的错误流

  • InputStream getInputStream():获取子进程的输入流

  • OutputStream getOutputStream():获取子进程的输出流

子进程读取Java程序的数据,就是让Java程序把数据输出到子进程中,使用输出流

下面的代码实现了获取子进程的错误输出

import java.io.*;
public class ReadFromProcess
{
    public static void main(String[] args)
        throws IOException
    {
        // 运行javac命令,返回运行该命令的子进程
        Process p = Runtime.getRuntime().exec("javac");
        try(
            // 以p进程的错误流建立BufferedReader对象
            // 这个错误流对本程序是输入流,对p进程则是输出流
            BufferedReader br = new BufferedReader(new
                InputStreamReader(p.getErrorStream())))
        {
            String buff = null;
            // 采起循环方式来读取p进程的错误输出
            while((buff = br.readLine()) != null)
            {
                System.out.println(buff);
            }
        }
    }
}

Java程序中启动Java虚拟机运行另外一个Java程序,并向另外一Java程序输入数据:

import java.io.*;
public class WriteToProcess
{
    public static void main(String[] args)
        throws IOException
    {
        // 运行java ReadStandard命令,返回运行该命令的子进程
        Process p = Runtime.getRuntime().exec("java ReadStandard");
        try(
            // 以p进程的输出流建立PrintStream对象
            // 这个输出流对本程序是输出流,对p进程则是输入流
            PrintStream ps = new PrintStream(p.getOutputStream()))
        {
            // 向ReadStandard程序写入内容,这些内容将被ReadStandard读取
            ps.println("普通字符串");
            ps.println(new WriteToProcess());
        }
    }
}

定义一个ReadStandard类,该类能够接受标准输入并将标准输入写入out.txt文件

import java.io.*;
import java.util.Scanner;

class ReadStandard
{
    public static void main(String[] args) throws IOException
    {
        try(
                // 使用Scanner.in建立Scanner对象,用于获取标准输入
                Scanner sc = new Scanner(System.in);
                PrintStream ps = new PrintStream(new FileOutputStream("outtext.txt"))
                )
        {
            // 只把回车做为分隔符
            sc.useDelimiter("\n");
            // 判断是否还有下一个输入项
            while (sc.hasNext()) 
            {
                // 输出输入项
                System.out.println("键盘输入的内容为:" + sc.next());
            }
        }
        catch (IOException ioe) 
        {
            ioe.printStackTrace();
        }
    }
}

RandomAccessFile

RandomAccessFile是Java输入输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它便可以读取文件内容,也能够向文件输出数据。与普通的输入/输出流不一样的是,RandomAccessFile 支持“随机访问”的方式,程序能够直接跳转到文件的任意地方来读写数据。它的最大局限是只能读写文件,不能读写其余IO节点

RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置,当程序建立一个新的RandomAccessFile对象时,该对象的文件记录指针对于文件头(也就是0处),当读写n个字节后,文件记录指针将会向后移动n个字节。RandomAccessFile包含两个方法来操做文件记录指针:

  • long getFilePointer():返回文件记录指针的当前位置

  • void seek(long pos):将文件记录指针定位到pos位置

RandomAccessFile类在建立对象时,除了指定文件自己,还须要指定一个mode参数,该参数指定RandomAccessFile的访问模式,该参数有以下四个值:

  • r:以只读方式打开指定文件。若是试图对该RandomAccessFile指定的文件执行写入方法则会抛出IOException

  • rw:以读取、写入方式打开指定文件。若是该文件不存在,则尝试建立文件

  • rws:以读取、写入方式打开指定文件。相对于rw模式,还要求对文件的内容或元数据的每一个更新都同步写入到底层存储设备

  • rwd:以读取、写入方式打开指定文件。相对于rw模式,还要求对文件的内容的每一个更新都同步写入到底层存储设备

访问指定的中间部分数据

import java.io.*;

public class RandomAccessFileTest
{
    public static void main(String[] args)
    {
        try(
            RandomAccessFile raf =  new RandomAccessFile(
                "RandomAccessFileTest.java" , "r"))
        {
            // 获取RandomAccessFile对象文件指针的位置,初始位置是0
            System.out.println("RandomAccessFile的文件指针的初始位置:" + raf.getFilePointer());
            // 移动raf的文件记录指针的位置
            raf.seek(300);
            byte[] bbuf = new byte[1024];
            // 用于保存实际读取的字节数
            int hasRead = 0;
            // 使用循环来重复“取水”过程
            while ((hasRead = raf.read(bbuf)) > 0 )
            {
                // 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
                System.out.print(new String(bbuf , 0 , hasRead ));
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
}

向指定文件后追加内容

import java.io.*;
public class AppendContent
{
    public static void main(String[] args)
    {
        try(
            //以读、写方式打开一个RandomAccessFile对象
            RandomAccessFile raf = new RandomAccessFile("out.txt" , "rw"))
        {
            //将记录指针移动到out.txt文件的最后
            raf.seek(raf.length());
            raf.write("追加的内容!\r\n".getBytes());
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
}

向指定文件、指定位置插入内容

RandomAccessFile若是向文件的指定的位置插入内容,则新输出的内容会覆盖文件中原有的内容。若是须要向指定位置插入内容,程序须要先把插入点后面的内容读入缓冲区,等把须要的插入数据写入文件后,再将缓冲区的内容追加到文件后面

import java.io.*;
public class InsertContent
{
    public static void insert(String fileName, long pos, String insertContent) throws IOException
    {
        File tmp = File.createTempFile("tmp" , null);
        tmp.deleteOnExit();
        try(
            RandomAccessFile raf = new RandomAccessFile(fileName , "rw");
            // 使用临时文件来保存插入点后的数据
            FileOutputStream tmpOut = new FileOutputStream(tmp);
            FileInputStream tmpIn = new FileInputStream(tmp))
        {
            raf.seek(pos);
            // ------下面代码将插入点后的内容读入临时文件中保存------
            byte[] bbuf = new byte[64];
            // 用于保存实际读取的字节数
            int hasRead = 0;
            // 使用循环方式读取插入点后的数据
            while ((hasRead = raf.read(bbuf)) > 0 )
            {
                // 将读取的数据写入临时文件
                tmpOut.write(bbuf, 0, hasRead);
            }
            // ----------下面代码插入内容----------
            // 把文件记录指针从新定位到pos位置
            raf.seek(pos);
            // 追加须要插入的内容
            raf.write(insertContent.getBytes());
            // 追加临时文件中的内容
            while ((hasRead = tmpIn.read(bbuf)) > 0 )
            {
                raf.write(bbuf, 0, hasRead);
            }
        }
    }
    public static void main(String[] args)
        throws IOException
    {
        insert("InsertContent.java", 45, "插入的内容\r\n");
    }
}
相关文章
相关标签/搜索