End

IO流 简介 总结 API 案例-2

本文地址


目录

IO流 简介 总结 API 案例-2

基本数据类型读写流 Data-Stream

允许应用程序以与机器无关方式从底层输入流中读写Java基本数据类型

适用场景

若我们想输出一个long类型(8个字节)或float类型(4个字节)的数据,怎么办呢?
可以一个字节一个字节输出,也可以转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊!
数据流DataOutputStream就解决了我们输出基本数据类型的困难。
数据流可以直接输出float类型或long类型,提高了数据读写的效率。

另外,我们有时只是要存储一个对象的成员数据,没有必要存储整个对象的信息,假设成员数据的类型都是Java的基本数据类型,这样的需求不必使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。

DataInputStream

DataInputStream 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。

DataInputStream 对于多线程访问不一定是安全的。

继承关系

  • class DataInputStream extends FilterInputStream implements DataInput
  • class FilterInputStream extends InputStream
  • 所有已实现的接口:Closeable, DataInput

构造方法

  • DataInputStream(InputStream in) 使用指定的底层 InputStream 创建一个 DataInputStream。

常用方法

  • int read(byte[] b) 从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b 中。
    • 以整数形式返回实际读取的字节数。
    • 在输入数据可用、检测到文件末尾或抛出异常之前,此方法将一直阻塞。
    • 如果 b 为 null,则抛出 NullPointerException。
    • 如果 b 的长度为 0,则不读取字节并返回 0;否则,尝试读取至少一个字节。
    • 如果因为流位于文件末尾而没有字节可用,则返回值 -1;否则至少读取一个字节并将其存储到 b 中。
  • int read(byte[] buf,int off,int len) 从包含的输入流中将最多 len 个字节读入一个 byte 数组中。
    • 尽量读取 len 个字节,但读取的字节数可能少于 len 个,也可能为零。以整数形式返回实际读取的字节数。
    • 在输入数据可用、检测到文件末尾或抛出异常之前,此方法将阻塞。
    • 如果 len 为零,则不读取任何字节并返回 0;否则,尝试读取至少一个字节。
    • 如果因为流位于文件未尾而没有字节可用,则返回值 -1;否则,至少读取一个字节并将其存储到 b 中。
  • static String readUTF(DataInput in) 从流 in 中读取用 UTF-8 修改版格式编码的 Unicode 字符格式的字符串;然后以 String 形式返回此字符串。UTF-8 修改版表示形式的一些细节与 DataInput 的 readUTF 方法完全相同。

其他方法

  • 从类 java.io.FilterInputStream 继承的方法:available, close, mark, markSupported, read, reset, skip
  • 从接口 java.io.DataInput 继承的方法:...

DataOutputStream

DataOutputStream 允许应用程序以适当方式将基本 Java 数据类型写入输出流中。

继承关系

  • class DataOutputStream extends FilterOutputStream implements DataOutput
  • class FilterOutputStream extends OutputStream
  • 所有已实现的接口:Closeable, DataOutput, Flushable

构造方法

  • DataOutputStream(OutputStream out) //创建一个新的数据输出流,将数据写入指定基础输出流。

写入八种基本类型数据

  • void writeBoolean(boolean v) //将一个 boolean 值以 1-byte 值形式写入基础输出流。值 true 以值 (byte)1 的形式被写出;值 false 以值 (byte)0 的形式被写出。如果没有抛出异常,则计数器 written 增加 1。
  • void writeByte(int v) //将一个 byte 值以 1-byte 值形式写出到基础输出流中。如果没有抛出异常,则计数器 written 增加 1。
  • void writeChar(int v) //将一个 char 值以 2-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 2。
  • void writeDouble(double v) //使用 Double 类中的 doubleToLongBits 方法将 double 参数转换为一个 long 值,然后将该 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 8。
  • void writeFloat(float v) //使用 Float 类中的 floatToIntBits 方法将 float 参数转换为一个 int 值,然后将该 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 4。
  • void writeInt(int v) //将一个 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 4。
  • void writeLong(long v) //将一个 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 8。
  • void writeShort(int v) //将一个 short 值以 2-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 2。

写入其他类型数据

  • void write(byte[] b, int off, int len) //将指定 byte 数组中从偏移量 off 开始的 len 个字节写入基础输出流。如果没有抛出异常,则计数器 written 增加 len。
  • void write(int b) //将指定字节(参数 b 的八个低位)写入基础输出流。如果没有抛出异常,则计数器 written 增加 1。
  • void writeBytes(String s) //将字符串按字节顺序写出到基础输出流中。按顺序写出字符串中每个字符,丢弃其八个高位。如果没有抛出异常,则计数器 written 增加 s 的长度。中文会乱码!
  • void writeChars(String s) //将字符串按字符顺序写入基础输出流。通过 writeChar 方法将每个字符写入数据输出流。如果没有抛出异常,则计数器 written 增加 s 长度的两倍。
  • void writeUTF(String str) //以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。

首先,通过 writeShort 方法将两个字节写入输出流,表示后跟的字节数。该值是实际写出的字节数,不是字符串的长度。根据此长度,使用字符的 UTF-8 修改版编码按顺序输出字符串的每个字符。如果没有抛出异常,则计数器 written 增加写入输出流的字节总数。该值至少是 2 加 str 的长度(英文字符),最多是 2 加 str 的三倍长度(中文字符)。

其他方法

  • void flush() //清空此数据输出流。这迫使所有缓冲的输出字节被写出到流中。
  • int size() //返回计数器 written 的当前值,即到目前为止写入此数据输出流的字节数。
  • 从类 java.io.FilterOutputStream 继承的方法:close, write
  • 从接口 java.io.DataOutput 继承的方法:write

测试代码

public static void dataStream() {
    try {
        DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(FILE_PATH));
        DataInputStream inputStream = new DataInputStream(new FileInputStream(FILE_PATH));
        switch (count % 4) {
            case 0:
                outputStream.writeBoolean(true);
                outputStream.writeByte(97);
                outputStream.writeChar('b');
                outputStream.writeInt(99);
                outputStream.writeShort(100);
                outputStream.writeLong(101);
                outputStream.writeFloat(0.5f);
                outputStream.writeDouble(0.5d);
                outputStream.flush();

                String content = inputStream.readBoolean() + "," + inputStream.readByte() + "," + inputStream.readChar() + ","
                    + inputStream.readInt() + "," + inputStream.readShort() + "," + inputStream.readLong() + "," +
                    inputStream.readFloat() + "," + inputStream.readDouble();
                Log.i("bqt", "读取的写入数据:" + content);//true,97,b,99,100,101,0.5,0.5
                break;
            case 1:
                outputStream.write(255);//将指定字节的最低8位写入基础输出流,1111 1111
                outputStream.write(256);//1 0000 0000
                outputStream.flush();

                byte[] array = new byte[inputStream.available()];// 读取字节数组
                int length = inputStream.read(array);
                Log.i("bqt", length + "," + array.length + ",读取的写入数据:" + Arrays.toString(array));//2,2,[-1, 0]
                break;
            case 2:
                String text = "青天abc12";
                byte[] textArray = text.getBytes();//2*3 + 5*1 = 11
                outputStream.write(textArray, 0, textArray.length);
                outputStream.writeBytes(text);  //7,一个字符以一个字节的长度写入,中文会乱码!
                outputStream.writeChars(text); //14,一个字符两个字节
                outputStream.writeUTF(text);//将一个字符串用UTF-8编码写到基本输出流
                outputStream.flush();

                byte[] full = new byte[inputStream.available()];
                inputStream.readFully(full);
                Log.i("bqt", full.length + "," + new String(full));//45,青天abc12R)abc12�RY)��a��b��c��1��2��青天abc12

                inputStream = new DataInputStream(new FileInputStream(FILE_PATH));
                byte[] temp = new byte[textArray.length];
                Log.i("bqt", inputStream.read(temp) + "," + new String(temp));//11,青天abc12
                temp = new byte[text.length()];
                Log.i("bqt", inputStream.read(temp) + "," + new String(temp));//7,R)abc12
                temp = new byte[2 * text.length()];
                Log.i("bqt", inputStream.read(temp) + "," + new String(temp));//14,�RY)��a��b��c��1��2
                Log.i("bqt", inputStream.readUTF());//青天abc12。长度为【2 + 3 * 2 + 5 * 1 = 13】
                break;
            case 3:
                outputStream.writeChars("青天abc12");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < outputStream.size(); i += 2) {
                    char c = inputStream.readChar();
                    sb.append(c);
                }
                Log.i("bqt", sb.toString()); // 青天abc12
                break;
            default:
                break;
        }
        outputStream.close();
        inputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

对象序列化反序列化流 Object-Stream

ObjectInputStream

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化

ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectInputStream 用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。

ObjectInputStream 确保从流创建的图形中所有对象的类型与 Java 虚拟机中显示的类相匹配。使用标准机制按需加载类。

只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。

readObject 方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。

可以使用 DataInput 上的适当方法从流读取基本数据类型。

默认情况下,对象的反序列化机制会将每个字段的内容恢复为写入时它所具有的值和类型。反序列化进程将忽略声明为瞬态或静态的字段。对其他对象的引用使得根据需要从流中读取这些对象。使用引用共享机制能够正确地恢复对象的图形。反序列化时始终分配新对象,这样可以避免现有对象被重写。

读取对象类似于运行新对象的构造方法。为对象分配内存并将其初始化为零 (NULL)。为不可序列化类调用无参数构造方法,然后从以最接近 java.lang.object 的可序列化类开始和以对象的最特定类结束的流恢复可序列化类的字段。

继承关系

  • class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
  • 所有已实现的接口:Closeable, DataInput, ObjectInput, ObjectStreamConstants
  • 接口 ObjectStreamConstants 中没有定义任何方法,但是定义了很多常量

构造方法

  • ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。

接口 ObjectInput 和 DataInput 中的方法

其他方法

  • void defaultReadObject() 从此流读取当前类的非静态和非瞬态字段。
  • ObjectInputStream.GetField readFields() 按名称从流中读取持久字段并使其可用。
  • Object readUnshared() 从 ObjectInputStream 读取“非共享”对象。
  • void registerValidation(ObjectInputValidation obj, int prio) 在返回图形前注册要验证的对象。
  • 从类 java.io.InputStream 继承的方法:mark, markSupported, read, reset, skip
  • 从接口 java.io.ObjectInput 继承的方法:read, skip

ObjectOutputStream

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包

writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。

还可以使用 DataOutput 中的适当方法将基本数据类型写入流中。还可以使用 writeUTF 方法写入字符串

对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。可使用引用共享机制对单个对象的多个引用进行编码,这样即可将对象的图形恢复为最初写入它们时的形状。

继承关系

  • class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
  • 所有已实现的接口:Closeable, DataOutput, Flushable, ObjectOutput, ObjectStreamConstants
  • 接口 ObjectStreamConstants 中没有定义任何方法,但是定义了很多常量

构造方法

  • ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。

接口 ObjectOutput 和 DataOutput 中的方法

其他方法

  • void writeFields() 将已缓冲的字段写入流中。
  • void writeUnshared(Object obj) 将“未共享”对象写入 ObjectOutputStream。
  • void defaultWriteObject() 将当前类的非静态和非瞬态字段写入此流。
  • ObjectOutputStream.PutField putFields() 获取用于缓冲写入流中的持久存储字段的对象。
  • void reset() 重置将丢弃已写入流中的所有对象的状态。
  • void useProtocolVersion(int version) 指定要在写入流时使用的流协议版本。

测试代码

public static void objectStream() {
    try {
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
        outputStream.writeInt(10086); //可以使用 DataOutput 中的适当方法将基本数据类型写入流中
        outputStream.writeUTF("UTF格式"); //可以使用 writeUTF 方法写入字符串
        outputStream.writeObject("也是对象"); //字符串也可以当做对象来写入

        Person writePerson = new Person("包青天", 28, new Person.Work("小米", 25000));
        outputStream.writeObject(writePerson);//所有引用到的类都必须实现了Serializable接口,否则会报 NotSerializableException
        outputStream.flush();

        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(FILE_PATH));
        Log.i("bqt", inputStream.readInt() + "," + inputStream.readUTF() + "," + inputStream.readObject()); //10086,UTF格式,也是对象

        Person readPerson = (Person) inputStream.readObject();//将字节重建成一个对象。注意,这个对象和之前保存的对象不是同一个对象!
        Log.i("bqt", (writePerson == readPerson) + "," + (writePerson.equals(readPerson))); //false,false
        Log.i("bqt", writePerson.toString() + "\n" + readPerson.toString()); //所有字段的值都是相同的,但引用都是不同的!

        outputStream.close();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

随机访问文件读写 RandomAccessFile

介绍

此类的实例支持对随机访问文件读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

随机访问文件RandomAccessFile不是io体系中的子类,可以认为是一个io工具类,其特点有:

  • 该对象即能读、又能写。
  • 该对象内部维护了一个byte数组,并通过指针可以操作数组中的元素。
  • 可以通过getFilePointer方法获取指针的位置,和通过seek方法设置指针的位置。
  • 其实该对象就是将字节输入流和输出流进行了封装。
  • 该对象的源或者目的只能是文件。

继承关系

  • class RandomAccessFile implements DataOutput, DataInput, Closeable
  • 所有已实现的接口:Closeable, DataInput, DataOutput

构造方法

  • RandomAccessFile(File file, String mode) 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
  • RandomAccessFile(String name, String mode) 创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

可选的模式

  • "r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
  • "rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
  • "rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
  • "rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。

测试代码

public static void randomAccessFile() {
    try {
        switch (count % 3) {
            case 0:
                randomTest();
                break;
            case 1:
                byte[] content = "什么是最重要的,什么是最紧迫的,什么是最有价值的,什么是最有意义的。".getBytes();
                multiDownload(content, new Random().nextInt(10) + 1);
                break;
            case 2:
                RandomAccessFile accessFile = new RandomAccessFile(FILE_PATH, "rw");
                byte[] bytes = new byte[(int) accessFile.length()];
                accessFile.read(bytes);
                System.out.println("完整内容为:" + new String(bytes));
                break;
            default:
                break;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private static void randomTest() {
    try {
        new File(FILE_PATH).delete();
        RandomAccessFile accessFile = new RandomAccessFile(FILE_PATH, "rw");//文件不存在自动创建
        accessFile.seek(0);
        accessFile.writeInt(10086);
        accessFile.seek(6);//从指定位置开始读取
        accessFile.writeChar('哀');
        accessFile.writeUTF("塞翁失马");
        accessFile.writeChars("哀叹");

        accessFile.seek(0);
        byte[] bytes = new byte[(int) accessFile.length()];
        accessFile.read(bytes);
        System.out.println("完整内容为:" + new String(bytes)); //����'f����T���塞翁失马T�S�

        accessFile.seek(0);
        Log.i("bqt", "" + accessFile.readInt()); //10086
        accessFile.seek(6);//从指定位置开始读取
        Log.i("bqt", accessFile.readChar() + "," + accessFile.readUTF()); //哀,塞翁失马
        Log.i("bqt", new String(new char[]{accessFile.readChar(), accessFile.readChar()})); //哀叹
        accessFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void multiDownload(byte[] content, int number) {
    new File(FILE_PATH).delete();
    try {
        RandomAccessFile accessFile = new RandomAccessFile(FILE_PATH, "rw");
        accessFile.setLength(content.length); //预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件
        accessFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    int partLength = content.length / number; //每一部分复制的最大长度
    for (int i = 0; i <= number; i++) {
        final int skipPos = i * partLength;
        int length = (content.length - skipPos) > partLength ? partLength : (content.length - skipPos);
        if (length > 0) {
            byte[] subContent = new byte[length];
            System.arraycopy(content, skipPos, subContent, 0, length);
            // 从源数组 content 的 skipPos 位置开始复制,复制到 subContent 的 0 位置,当复制 length 个后结束复制
            new Thread(() -> download(FILE_PATH, skipPos, subContent)).start();//利用多线程同时写入一个文件
        }
    }
}

private static void download(String filepath, long skipPos, byte[] content) {
    try {
        RandomAccessFile accessFile = new RandomAccessFile(filepath, "rw");
        accessFile.seek(skipPos);
        accessFile.write(content);
        accessFile.close();
        Log.i("bqt", "已复制【" + new String(content) + "】到文件中");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

合并输入流 SequenceInputStream

介绍

有些情况下,我们需要从多个输入流中向程序读入数据,此时,可以使用合并流,合并流的作用是将多个源合并成一个源。

在操作上,该类从第1个InputStream对象进行读取,直到读取完全部内容,然后切换到第2个InputStream对象。对于使用Enumeration对象的情况,该类将持续读取所有InputStream对象中的内容,直到到达最后一个InputStream对象的末尾为止。

当到达每个InputStream对象的末尾时,与之关联的流就会被关闭。

关闭通过SequenceInputStream创建的流,会导致关闭所有未关闭的流。

构造方法

  • SequenceInputStream(Enumeration<? extends InputStream> e) 通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。将按顺序读取由该枚举生成的输入流,以提供从此 SequenceInputStream 读取的字节。在用尽枚举中的每个输入流之后,将通过调用该流的 close 方法将其关闭。
  • SequenceInputStream(InputStream s1, InputStream s2) 通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。

常用的几个方法

  • int available() 返回不受阻塞地从当前底层输入流读取(或跳过)的字节数的估计值,方法是通过下一次调用当前底层输入流的方法。此方法仅调用当前底层输入流的 available 方法并返回结果。
  • void close() 关闭此输入流并释放与此流关联的所有系统资源。关闭的 SequenceInputStream 无法执行输入操作,且无法重新打开。
  • int read() 从此输入流中读取下一个数据字节。返回 0 到 255 范围内的 int 字节。如果因为已经到达流的末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常之前,此方法一直阻塞。
  • int read(byte[] b, int off, int len) 将最多 len 个数据字节从此输入流读入 byte 数组。
  • 从类 java.io.InputStream 继承的方法:mark, markSupported, read, reset, skip

文件切割

splitFile(new File("d:/卖火柴的小女孩.mp3"), new File("d:/split"), 1024 * 1024);
public static void splitFile(File source, File saveDir, int maxLength) {
    if (!saveDir.exists()) {
        System.out.println("创建目录:" + saveDir.mkdirs());
    }
    try {
        FileInputStream inputStream = new FileInputStream(source);
        byte[] buf = new byte[maxLength];
        int len;
        int count = 0;
        while ((len = inputStream.read(buf)) != -1) {
            String fileName = source.getName() + "-part" + (++count);

            FileOutputStream outputStream = new FileOutputStream(new File(saveDir, fileName));
            outputStream.write(buf, 0, len);
            outputStream.flush();
            outputStream.close();

        }
        inputStream.close();
        System.out.println("切割完成");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

文件合并

mergeFile(new File("d:/split").listFiles(), new File("d:/卖火柴的小女孩2.mp3"));
public static void mergeFile(File[] splitFiles, File mergedFile) {
    try {
        Arrays.sort(splitFiles); //默认是按升序排序的,如数字类型按照 数字由小到大 的顺序,字符串按照 abcd 的顺序
        System.out.println("要合并的文件列表:" + Arrays.toString(splitFiles));
        ArrayList<FileInputStream> inputStreams = new ArrayList<>();
        for (File file : splitFiles) {
            inputStreams.add(new FileInputStream(file));
        }
        SequenceInputStream inputStream = new SequenceInputStream(Collections.enumeration(inputStreams));
        FileOutputStream outputStream = new FileOutputStream(mergedFile);
        byte[] buf = new byte[1024];
        int len;
        while ((len = inputStream.read(buf)) != -1) {
            outputStream.write(buf, 0, len);
        }
        outputStream.close();
        inputStream.close();
        System.out.println("合并完成");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

文本扫描工具 Scanner

介绍

java.util.Scanner是 Java5 的新特征,主要功能是简化文本扫描。
这个类最实用的地方表现在获取控制台输入,其他的功能都很鸡肋,尽管 Java API 文档中列举了大量的 API 方法,但是都不怎么地。

当通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()next()方法即可。

常用的几个方法

  • useDelimiter(String pattern) 指定新的匹配分隔符的 Pattern
  • delimiter() 返回此 Scanner 当前正在用于匹配分隔符的 Pattern
  • hasNext() 判断扫描器中当前扫描位置后是否还存在下一段
  • hasNextLine() 如果在此扫描器的输入中存在另一行,则返回 true
  • next() 查找并返回来自此扫描器的下一个完整标记
  • nextLine() 此扫描器执行当前行,并返回跳过的输入信息

next()方法的特点

  • 直到读取到有效字符后才结束输入
  • 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
  • 对输入有效字符之前遇到的空白会自动将其去掉
  • 不能得到带有空格的字符串

nextLine()方法的特点

  • 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符
  • 可以获得空白符

注意:如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取。

案例一

public static void scannerTest(Scanner scanner) {
    System.out.println(scanner.delimiter());
    System.out.println("开始输入吧");
    while (scanner.hasNext()) {
        String text = scanner.next();
        System.out.println("你输入了:" + text);
        if ("88".equals(text)) {
            scanner.close();
            break;
        }
    }
    System.out.println("结束");
}

场景1:

scannerTest(new Scanner(System.in));
\p{javaWhitespace}+
开始输入吧
1
你输入了:1
2
你输入了:2
88
你输入了:88
结束

场景2:

scannerTest(new Scanner("1 2  3-4\n5\t67"));
\p{javaWhitespace}+
开始输入吧
你输入了:1
你输入了:2
你输入了:3-4
你输入了:5
你输入了:67
结束

场景3:

Scanner scanner = new Scanner("1 2  3-4\n5\t67");
scanner.useDelimiter("-");//指定新的分隔符
scannerTest(scanner);
-
开始输入吧
你输入了:1 2  3
你输入了:4
5    67
结束

案例二

public static void getInputText() {
    String account;
    String password;
    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.println("请输入你的账号:");
        account = scanner.nextLine();
        System.out.println("请输入你的密码:");
        password = scanner.nextLine();
        if ("1".equals(account) && "1".equals(password)) {
            System.out.println("欢迎光临");
            scanner.close();
            break;
        } else {
            System.out.println("账号密码错误:" + account + "," + password);
        }
    }
    System.out.println("结束");
}

以上使用 nextLine 时的效果:

请输入你的账号:
12 34
请输入你的密码:
5
账号密码错误:12 34,5
请输入你的账号:

以上使用 next 时的效果:

请输入你的账号:
12 34
请输入你的密码:
账号密码错误:12,34
请输入你的账号:

系统输入输出流 System.in/out/err

System.in, System.out, System.err这3个流是System类中的静态成员,并且已经预先在JVM启动的时候初始化完成:

  • System.in是一个连接控制台程序和键盘输入的InputStream流。注意:其read()方法是一个阻塞式方法。
  • System.out是一个PrintStream流(OutputStream的间接子类)。System.out一般会把你写到其中的数据输出到控制台上。
  • System.err是一个PrintStream流。System.err与System.out的运行方式类似,但它更多的是用于打印错误文本

定义:

  • public static final InputStream in:“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
  • public static final PrintStream out:“标准”输出流。此流已打开并准备接受输出数据。通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
  • public static final PrintStream err:“标准”错误输出流。此流已打开并准备接受输出数据。
  • 通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。按照惯例,此输出流用于显示错误消息,或者显示那些即使用户输出流(变量 out 的值)已经重定向到通常不被连续监视的某一文件或其他目标,也应该立刻引起用户注意的其他信息。

可以使用System.setIn()、System.setOut()、System.setErr()方法设置新的系统流,之后的数据都将会在新的流中进行读写。

这三个方法均为静态方法,内部调用了本地native方法重新设置系统流。

try {
    System.setOut(new PrintStream(new FileOutputStream("d:\\system.out.txt")));
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
System.out.println("不会在控制台打印,而会输出到文件中");

请务必在 JVM 关闭之前调用flush()方法冲刷 System.out,确保 System.out 把数据输出到了文件中。

2016-12-28

posted @ 2016-12-28 19:16  白乾涛  阅读(676)  评论(0编辑  收藏  举报