3

文件的编码

文本文件就是字节序列,可以是任意编码形式。在中文操作系统上直接创建文本文件,则该文本文件只能识别ANSI编码,其他编码方式会产生乱码

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对象所对应的路径名

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

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

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

  • boolean renameTo(File newName):重命名此File对象所对应的文件或目录,如果重命名成功,则返回true;否则返回false

文件检测相关方法

  • 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");
    }
}

布still
461 声望32 粉丝

数据挖掘、用户行为研究、用户画像