计算机中经常出现的一个词就是IO流,IO流为什么叫IO流呢,IO就是 input output 的简称,先说 IO,从计算机体系结构上来说,计算机的主板的许多芯片中有个特别的东西叫 IO 芯片(intel 的架构),或者计算机的 cpu 中集成有 IO 引脚(arm 架构、单片机之类)。这个东西主要作用就是计算机芯片和其他设备交换数据用的,特别指计算机内存和其他外围设备交换数据时使用的。再说流,stream 是 IO 设备读写的一种方法,对比内存中数据通常的读写方法,这种数据读写方法叫做流式读写,回想我们用c语言读写内存中的数据,我们都是一个单位一个单位读写的,这个单位是数据线的宽度决定的,比如 8 bits、32 bit、 64 bits。而流式方法,好比水流,我们每次读写的数据的大小是可以指定的,然后它会自动分多次有秩序地持续完成数据交换,这里主要跟硬件的缓冲区和 DMA 有关系。IO 都有流的概念,流式读写的特征跟水流一样是自动的多次读一个资源的数据到内存,多次写内存的数据到资源,流解决了两个速度不同的存储设备之间传输数据的问题,速度慢的外设先把数据放到缓冲区,然后集中一小段时间占用总线把这些数据传给内存,其他时间内存和其他设备交换数据,或者内存先把数据传送给外设缓冲区,缓冲区再慢慢把数据写入设备对应位置。流式读写可以分为 节点流和处理流,字节流和字符流,输入流和输出流。
IO中的流就相当与我们日常生活中的管道,我们通过管道来把水引到用户,通过管道把石油输送到大罐.同样,我们利用流来从硬盘的文件中读数据到你的程序中,利用流来写数据到硬盘的文件,文件流 缓冲流 数据流 转换流 Print流 Object流正是为了实现这些功能的不同的类,他们具体包含了实现这些功能的方法,但如果每次都要从硬盘读取一个字节数据或写1个字节数据到硬盘,那就对硬盘损害太大了,比如电驴就损害硬盘.解决办法:在内存中建立一个缓冲区(buffer),读一次硬盘就把缓冲区装满,然后你就可以从缓冲区读取数据,写数据的时候,先在内存中把数据写到缓冲区,写满,然后把数据一次性地从缓冲区写到硬盘.这样对硬盘的访问次数大大减少了.缓存要交换的数据:就是读数据的时候把数据一次性读到缓冲区和写数据的时候先把数据写到缓冲区的意思,buffer是在内存中是通过字节数组实现的。
所以在 Java 中 IO流 是一组有序的数据序列,以 程序 为中心,根据数据流的方向,可分为输入流和输出流两种 IO 流,这相当于在程序和其他设备之间的一条通道,程序可以使用这条通道从数据源输入数据,再把数据输出到目的地,完成把源地址中的字节序列送到目的地的过程。虽然程序 IO流 经常和磁盘文件存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存、显示屏、网络设备、硬盘 等等。
总结起来就是:流是指 自动有序的完成数据交换,IO 是指我们的程序和其他设备之间数据交换,IO流就是指我们的程序和其他设备之间自动有序的完成数据交换。
Java 语言定义了许多专门负责各个方式的输入/输出的类库,这些类都被放在 java.io 包中。其中所有输入流类都是抽象类 InputStream (字节输入流)或者抽象类 Reader (字符输入流)的子类,而所有输出流都是抽象类 OutputStream(字节输出流)或者抽象类 Writer (字符流输出流)的子类。
字节流两个基类:InputStream OutputStream
字符流两个基类:Reader Writer
字符流和字节流相比,有两个不同,一个是流式传输的时候传输协议不同,因为传输的数据单位改变了;二是字符流还带有编解码的功能,我们的字符数据在 java 程序里面是以 unicode 编码的形式存在,字符流可以对我们程序中的字符再编码,编码为 utf-8 、gbk 等,可以对其他设备传过来的数据解码,把 utf-8、gbk 等编码解码为 unicode 码。
字符流是字节流的功能的扩展,不但可以读取或者写入流中的字节数据,还可以把流中的字节数据解码为字符,或者把字符编码之后的字节数据发送到流中。但是 Reader Writer 这两个字符流基类却只负责编解码,不带流输入输出的功能,Reader Writer 中流IO的功能是其子类实现的,这个和模板方法模式有点类似。
和 InputStream OutputStream 相比较,Reader Wirter 之所以是字符流,是因为它们都是让读写的让人懂得字符。所以它们都加上了 er 后缀表人。这样 InputStream OutputStream 就只表示最基本含义的字节流,多么直接,InputStream 没有加上字节的含义是因为计算机读写最基本的单位就是字节,所以默认的东西没有表示出来,这表示多么直接和简洁啊。底层上来看,Reader Writer 因为加上了码表,所以可以对字节数据进行编码解码,得到人们可以读懂的字符。
由以上四个类派生出来的子类名称都是 以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader。
FileWriter== File +Writer,用来向文件写入字符的类
构造器
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。
Writer 类常用方法
void write(String str):写入字符串。
abstract void flush():刷新该流的缓冲
abstract void close():关闭此流,但要先刷新它。
// 创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录 // 下。如果该目录下已有同名文件,默认将被覆盖。其实该步就是在明确数据要存放的目的地。File类关于文 // 件创建有更多的方式 FileWriter fw = new FileWriter("Test.txt"); // 调用write方法,将字符串写入到流中。 fw.write("abcde"); // 刷新流对象中的缓冲中的数据。 // 将数据刷到目的地中。 fw.flush(); // 关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。 // 将数据刷到目的地中。 // 和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。 fw.close();
在windows 系统里,对于文本文件用两个字符 \r\n 来代表回车符,在linux系统里用 \n 代表回车符。
FileWriter 主要就是用来把字符写入文本文件中,让文本文件中的数据有编码,它结合了 File 类的功能,可以创建文件,还结合了 Writer 类的功能可以对写入文件的数据进行系统默认编码。它主要的功能就是 write 写数据,所以 FileWriter 类被划分到了 Writer 这个系统下面。
/** * 需求:定义一个应用程序日志管理类, 简单演示 FileWriter 的使用 */ package cn.itcast.others.iostream; import java.io.FileWriter; import java.io.IOException; /** * @class: LogManager * @package: cn.itcast.others * @description: TODO * @author: vivianZhao * @date: 2013-7-17 下午11:55:30 * @version: 1.0 */ public class LogManager { private FileWriter fileWriter; private static LogManager logManagerInstance = new LogManager(); private LogManager() { } public static void main(String[] args) { LogManager logManager = LogManager.getInstance(); logManager.writeLog("log123.txt", "log.txt"); logManager.writeLog("log1.txt", "log.txt"); } public void writeLog(String logString, String LogFileName) { try { // fileWriter = new FileWriter(LogFileName, true); fileWriter = new FileWriter(LogFileName); fileWriter.write(logString); } catch (IOException e) { // TODO: handle exception e.printStackTrace(); } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e2) { // TODO: handle exception e2.printStackTrace(); } } } } public static LogManager getInstance() { return logManagerInstance; } }
FileReader == File + Reader,用来读取字符文件的便捷类
构造方法:
/** * 需求:使用 FileWriter FileReader copy 字符文件,演示 FileWriter FileReader 的使用 * * 思路: Reader 有两种读的方法,一是一个字符一个字符的读取,二是一次读取指定 char[] 大小的长度 * * 步骤: * * 总结: * 1.FileReader read 方法使用时,使用 while 循环判断有没有读到流结束符 * 2.FileReader FileWriter 使用的时候,异常处理部分代码比较多,掩盖了业务逻辑,但是异常处理部分代码是不需 * 改动的,所以可以使用模板方法模式,或者装饰者模式,把异常处理 和 业务逻辑隔离 * 3. */ package cn.itcast.others.iostream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; /** * @class: CopyCharFile * @package: cn.itcast.others.iostream * @description: TODO * @author: vivianZhao * @date: 2013-7-18 上午9:10:42 * @version: 1.0 */ public class CopyCharFile { public static void main(String[] args) { // test api } /** * @method: copyCharFile1 * @description: 按字符 copy 字符文件 * @param sourceFileName * 源文件路径 * @param destinationFileName * 目的文件路径 * @throws IOException * @return: void * @author: vivianZhao * @date: 2013-7-18 上午9:23:26 * @version: 1.0 */ public static void copyCharFile1(String sourceFileName, String destinationFileName) { FileWriter fileWriter = null; FileReader fileReader = null; try { fileWriter = new FileWriter(sourceFileName); fileReader = new FileReader(destinationFileName); int charTemp; while ((charTemp = fileReader.read()) != -1) { fileWriter.write(charTemp); } } catch (IOException e) { // TODO: handle exception e.printStackTrace(); } finally { try { if (fileReader != null) { fileReader.close(); } } catch (IOException e2) { // TODO: handle exception e2.printStackTrace(); } finally { try { if (fileWriter != null) { fileWriter.close(); } } catch (IOException e3) { // TODO: handle exception e3.printStackTrace(); } } } } /** * @method: copyCharFile2 * @description: 按字符数组 copy 文件 * @param sourceFileName * 源文件路径 * @param destinationFileName * 目的文件路径 * @return: void * @author: vivianZhao * @date: 2013-7-18 上午9:34:08 * @version: 1.0 */ public static void copyCharFile2(String sourceFileName, String destinationFileName) { FileWriter fileWriter = null; FileReader fileReader = null; try { fileWriter = new FileWriter(sourceFileName); fileReader = new FileReader(destinationFileName); char[] charBuffer = new char[1024]; while ((fileReader.read(charBuffer)) != -1) { fileWriter.write(charBuffer); } } catch (IOException e) { // TODO: handle exception e.printStackTrace(); } finally { try { if (fileReader != null) { fileReader.close(); } } catch (IOException e2) { // TODO: handle exception e2.printStackTrace(); } finally { try { if (fileWriter != null) { fileWriter.close(); } } catch (IOException e3) { // TODO: handle exception e3.printStackTrace(); } } } } }
2.可以使用单位行输入输出。
3.在使用 BufferedWriter 类的 Writer() 方法时,数据并没有立即写入至输出流中.而是首先进入缓存区中,如果想理解将缓存区中个的数据写入输出流中,一定要动用flush()方法。 缓冲区的出现提高了对数据的读写效率。
4..BufferedWriter 类和 BufferedReader 类主要功能还是 write read 字符数据,所以在 Writer Reader 这个主线下面,如果有 Buffered 接口的话可以说该类实现了 Buffered 接口。
BufferedWriter 字符输出流缓冲区:
缓冲区要结合流才可以使用,在流的基础上对流的功能进行了增强, 缓冲区的出现是为了提高流的操作效率,所以在创建缓冲区之前,必须要先有流对象。
该缓冲区提供了一个夸平台的换行符.
newLine();
BufferedReader 字符读取流缓冲区:
该缓冲区提供了一个一次读一行的方法 readLine,方便与对文本进行数据的获取,和 Reader 中的 read 不同的是方法读到文件末尾的时候返回 null。
缓冲区技术提高了流的操作效率,所以创建缓冲区虽然是流的子类,但是缓冲区对象自身不创建流对象,创建缓冲区对象之前,依赖于提供给缓冲区构造函数流对象;缓冲区中提供了一个跨平台的换行符 newline(),和 readline()。readLine() 方法只返回回车符之前的类容,不带回车符,readLine 读到文件末尾返回 null。
查看源码可以发现 BufferedReader 中 readLine() read() 方法都是使用的 BufferedReader 的 fill() 方法,而 fill() 方法则使用的是 Reader 的 read(char [] c, int offset, int len) 方法实现的。所以 BufferedReader、BufferedWriter 的优势主要就是有 readLine() 和 newLine() 方法。
/** * 需求:演示 BufferedReader 和 BufferedWriter 的使用,复制一个 java 文件 */ package cn.itcast.others.iostream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; class CopyTextBuf { public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { // 为了提高效率.加入缓冲技术,将字符读取流对象作为参数传递给缓冲区的构造函数.建立缓冲区,创建流 // 对象和文件相关联的 bufr = new BufferedReader(new FileReader("BufferedReaderDemo.java")); bufw = new BufferedWriter(new FileWriter( "BufferedReaderDemoCopy_1.txt")); String s = null; // 因为读取一行到末尾的时候会返回null.就可以以null作为循环条件来循环 while ((s = bufr.readLine()) != null) { bufw.write(s); // 读取一行.输出一行 bufw.newLine(); // 换行 } } catch (IOException e) { throw new RuntimeException("文件路径错误"); } finally { try { if (bufr != null) bufr.close(); } catch (IOException e) { System.out.println("读取关闭文件失败"); } finally { try { if (bufw != null) bufw.close(); } catch (IOException e) { System.out.println("输出关闭文件失败"); } } } } }
装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类于类之间的耦合关系。
装饰者模式使用的好处和弊端:当想要对已有的类进行功能增强的时候,可以再定义一个类,将已有类的对象传入,基于已有的功能,并提供加强功能,那么再定义的类就称为装饰者类。装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
MyReader
|---MyTextReader
|---MyBufferedTextReader
|---MyMediaReader
|---MyBufferedMediaReader
|---MyDataReader
|---MyBufferedDataReader
Reader
|---FileReader
|---。。。
|---。。。
|---BufferedReader
带行号的 Reader,在从流中数据数据的时候,可以记住行号,是 BufferedReaderd 的子类,和 FileReader 关系也属于装饰类,记录行号的缓冲字符输入流。此类定义了方法 setLineNumber(int)
和 getLineNumber(),它们可分别用于设置当前行号,和获取记录中的当前行号。
模拟 BufferedReader
/** 需求:自定义一个包含 readLine 方法的 BufferedReader 来模拟一下 BufferedReader */ import java.io.FileReader; import java.io.IOException; import java.io.Reader; class MyBufferedReader extends Reader { private Reader r; MyBufferedReader(Reader r)// 建立构造函数.一开始就要有流 { this.r = r; } // 可以一次读一行数据的方法。 public String myReadLine() throws IOException { // 定义一个临时容器。原BufferReader封装的是字符数组。 // 为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。 StringBuilder sb = new StringBuilder(); int ch = 0; while ((ch = r.read()) != -1) { if (ch == '\r') continue;// 判断如果是\r的话在运行.判刑下个\n,如果是最后一行了.返回字符串 if (ch == '\n') return sb.toString(); else sb.append((char) ch); } if (sb.length() != 0) return sb.toString(); return null; } /* * 覆盖Reader类中的抽象方法。 */ public int read(char[] cbuf, int off, int len) throws IOException { return r.read(cbuf, off, len); } public void close() throws IOException { r.close(); } public void myClose() throws IOException { r.close(); } } class MyBufferedReaderDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); MyBufferedReader myBuf = new MyBufferedReader(fr);// 自定义的装饰类 String line = null; while ((line = myBuf.myReadLine()) != null) { System.out.println(line); } myBuf.myClose();// 关闭资源 } }
字节流基本操作与字符流类相同 write、read、flush、close,但它不仅可以操作字符数据,还可以操作其他格式数据。(是不是可以把 Reader、Writer 作为 InputStream OutputStream 的扩展类,因为它们只是给 InputStream OutputStream 增加了字符编解码功能。)
字节输入流的抽象类,是所有字节流输入流的父类,
InputStream 类的继承层次结构
InputStream
|--AudioInputStream
|--ByteArrayInputStream 数据源是ByteArray
|--FilterInputStream 过滤器流,相当于在程序和流之间加了一个数据过滤器
|--BufferedInputStream
|--DataInputStream
|--LineNumberInputStream 是字节输入流增强的功能,字节带行号? Reader 那里 LineNumberXXX 和 BufferedXXX 就是继承关系,这里是聚合关系
|--等等.详情参阅API java.io包中
|--FileInputStream 数据源是File
|--InputStream
|--ObjectInputStream 数据格式是 Object
|--PipedInputStream
|--SequenceInputStream
|--StringBufferInputStream 数据格式是StringBuffer
InputStream 常用方法:
int available() 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
void close() 关闭此输入流并释放与该流关联的所有系统资源。
abstract int read() 从输入流中读取数据的下一个字节。
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。
void mark(int readlimit) 在此输入流中标记当前的位置,主要配合 reset 方法使用
boolean markSupported() 测试此输入流是否支持 mark 和 reset 方法。
void reset() 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
long skip(long n) 跳过和丢弃此输入流中数据的 n 个字节。
是字节输入流的抽象类,次抽象表示输出字节流的所有类的超类 OutputStream
OutputStream
|--ByteArrayOutputStream
|--FilterOutputStream
|--BufferedOutputStream
|--DataOutputStream
|--PrintStream
|--等等.详情参阅API java.io包中
|--FileOutputStream
|--ObjectOutputStream
|--OutputStream
|--PipedOutputStream
常用方法:
void close() 关闭此输出流并释放与此流有关的所有系统资源。
void flush() 刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b) 将指定的字节写入此输出流。
FileInputStream == File + InputStream
FileOutputStream == File + OutputStream
FileInputStream 类与 FileOutputStream 类都是用来操作磁盘文件的,如果用户的文件读取需求比较简单只需要读取文件中的字节流,可以使用 FileInputStream 类。该类继承自 InputStream 类
FlieOutputSream 类与 FileInputStream 类对应提供了把字节数据写入文件和从文件中读取字节数据的能力,FileOutputStream 类是 OutputStream 类的子类。
示例:
/** * 需求:拷贝一个图片 * 思路: * 1.创建一个字符输入流和图片相关联. * 2.用字节写入流对创建图片文件,用于存储到图片数据. * 3.通过循环续写,完成数据的存储. * 4.关闭资源. * * 步骤: * * 总结: * 1.文件输入字节流 和 文件输入字符流 一样,读到文件的结束标记的时候都是返回 -1 * 2.FileInputStream 和 FileOutputStream 相当于在 InputStream OutputStream 的基础上指定了数据源和数据目 * 的地,所以可以实现具体的读写操作 */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class CopyPic { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("D:\\2.jpg"); // 建立字符输入流对象和文件相关联 fos = new FileOutputStream("copy.jpg"); // 建立字符输出流对象指定路径和文件名字 byte[] buf = new byte[1024]; // 创建一个字节数组用来保存读取的数据 int num = 0; // 定义一个变量用于保存读取到的字节数 while ((num = fis.read(buf)) != -1) { fos.write(buf, 0, num); } } catch (IOException e) { throw new RuntimeException("路径错误"); } finally { try { if (fos != null) // 判断创建文件失败是否为null; fos.close(); } catch (IOException e) { System.out.println("输出关闭失败"); } try { if (fis != null) fis.close(); } catch (IOException e) { System.out.println("输入关闭失败"); } } } }
BufferedInputStream 和 BufferedOutputStream
/** * 需求:使用带缓冲区的流 copy 一个 mp3 * * 思路: * * 步骤: * * 总结: BufferedInputStream、BufferedInputStream 和 InputStream、OutputStream * 是聚合关系,前者是后者的装饰者 */ package cn.itcast.others.iostream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class CopyPic { public static void main(String[] args) throws IOException { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(new FileInputStream( "D:\\Maid with the Flaxen Hair.mp3")); bos = new BufferedOutputStream(new FileOutputStream("copy.mp3")); int num = 0; while ((num = bis.read()) != -1) { bos.write(num); } } catch (IOException e) { } bis.close(); bos.close(); } }
InputStreamReader :
java.lang.Object
java.io.Reader
java.io.InputStreamReader
Reader == InputStream + InputStreamReader ,所以 InputStreamReader 是字节流通向字符流的桥梁。每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。
构造方法:
InputStreamReader(InputStream in)
InputStreamReader(InputStream in, String charsetName)
示例:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter :
java.lang.Object
java.io.Writer
java.io.OutputStreamWriter
Writer == OutputStream + OutputStreamWriter,OutputStreamWriter 是字符流通向字节流的桥梁,将要写入流中的字符编码成字节,每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。
构造方法:
OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out, String charsetName)
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));// System.in 默认设备为键盘
Print + (OutputStream + OutputStreamWriter + File)
其中在构造函数中可以指定 OutputStream 、 file、编码方式,默认是系统编码。
PrintStream
为其他输出流添加了打印功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream
永远不会抛出IOException
;而是,异常情况仅设置可通过checkError
方法测试的内部标志。另外,为了自动刷新,可以创建一个PrintStream
;这意味着可在写入 byte 数组之后自动调用flush
方法,可调用其中一个 println
方法,会自动写入一个换行符或字节 ('\n'
)。
构造方法:
PrintStream(String fileName) 创建具有指定文件名称且不带自动行刷新的新打印流
PrintStream(OutputStream out, boolean autoFlush,String encoding)
创建新的打印流。
import java.io.*; import java.util.*; import java.text.*; class ExceptionInfo { public static void main(String[] args) throws IOException { try { int[] arr = new int[2]; System.out.println(arr[3]); } catch (Exception e) { try { Date d = new Date();// 建立日期类 SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");// 格式化日期 String s = sdf.format(d); PrintStream ps = new PrintStream("exeception.log");// 设置异常文件 ps.println(s); System.setOut(ps); } catch (IOException ex) { throw new RuntimeException("日志文件创建失败"); } e.printStackTrace(System.out);// 异常信息 } } }
方法
System.getProperties()
Properties.list(PrintStream out) // 将属性列表输出到指定的输出流
import java.io.*; import java.util.*; class SystemInfo { public static void main(String[] args) throws IOException { Properties prop = System.getProperties(); // System.out.println(prop); prop.list(new PrintStream("sysinfo.txt")); // Properties中的list方法PrintStream创建具有指定文件名称且不带自动行刷新的新打印流。 } }