java选择文件保存数据_Java 之路 (二十) -- Java I/O 上(BIO、文件、数据流、如何选择I/O流、典型用...

前言

Java 的 I/O 类库使用 流 这个抽象概念,代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。流 屏蔽了实际的 I/O 设备中处理数据的细节。

数据流是一串连续不断的数据的集合,简单理解的话,我们可以把 Java 数据流当作是 管道里的水流。我们只从一端供水(输入流),而另一端出水(输出流)。对输入端而言,只关心如何写入数据,一次整体全部输入还是分段输入等;对于输出端而言,只关心如何读取数据,,无需关心输入端是如何写入的。

对于数据流,可以分为两类:

字节流:数据流中最小的数据单元是字节(二进制数据)字符流:数据流中最小的数据单元是字符(Unicode 编码,一个字符占两个字节)

1. 概述

对于 Java.io 包内核心其实就是如下几个类:InputStream、OutputStream、Writer、Reader、File、(RandomAccessFile)。只要熟练掌握这几个类的使用,那么 io 部分就掌握的八九不离十了。

对于上面的几个类,又可以如下分类:

文件:File、RandomAccessFile字节流:InputStream、OutputStream字符流:Writer、Reader

io 包内还有一些其他的类,涉及安全以及过滤文件描述符等等,这里重点只在 io 的输入输出,有兴趣可以自行了解:https://docs.oracle.com/javase/9/docs/api/java/io/package-tree.html

简单介绍一下这几个类:

File:用于描述文件或者目录信息,通常代表的是 文件路径 的含义。RandomAccessFile:随机访问文件InputStream:字节流写入,抽象基类。OutputStream:字节流输出,抽象基类。Reader:字符流输入,抽象基类Writer:字符流输出,抽象基类

2. 文件

2.1 File

File - 文件和目录路径名的抽象表示。它既可以指代文件,也可以代表一个目录下的一组文件。当指代文件集时,可以调用 list() 方法,返回一个字符数组,代表目录信息。

下面简单列举 File 的使用:

1. 读取目录

public class TestFile {

public static void main(String[] args) {

File path = new File("./src/com/whdalive/io");

String[] list;

list = path.list();

for (String dirItem : list) {

System.out.println(dirItem);

}

}

}

/**输出

TestFile.java

*/

2. 创建目录

public class TestFile {

public static void main(String[] args) {

File file = new File("D://test1/test2/test3");

file.mkdirs();

System.out.println(file.isDirectory());

}

}

/**输出

true

*/

需要注意 mkdir() 和 mkdirs() 方法的区别

mkdir() 创建一个文件夹

mkdirs() 创建当前文件夹以及其所有父文件夹

3. 删除目录或文件

public class TestFile {

public static void main(String[] args) {

File file = new File("D://test1");

deleteFolder(file);

}

private static void deleteFolder(File folder) {

File[] files = folder.listFiles();

if (files!=null) {

for (File file : files) {

if (file.isDirectory()) {

deleteFolder(file);

}else {

file.delete();

}

}

}

folder.delete();

}

}

2.2 RandomAccessFile

RandomAccessFile 是一个完全独立的类,它和其他 I/O 类别有着本质不同的行为,它适用于记录由大小已知的记录组成的文件,因此可以将记录从一处转移到另一处,然后读取或者修改记录。

在 java SE 4 中,它的大多数功能由 nio 存储映射文件所取代,因此该类实际上用的不多了。

3. 数据流

数据流相关类的派生关系如图所示,四个基本的类为 InputStream、OutputStream、Writer、Reader,其余类都是这四个类派生出来的。

3.1 字节流

3.1.1 InputStream

InputStream 是所有字节流输入的抽象基类,作用是用来表示那些从不同数据源产生输入的类。这些数据源包括:

字节数组String 对象文件管道一个由其他种类的流组成的序列,以便我们可以将他们收集合并到一个流内其他数据源,比如网络链接等。

每种数据源都有一个对应的 InputStream 子类,如下:

类功能如何使用ByteArrayInputStream允许将内存的缓冲区当作 InputStream 使用作为一种数据源:将其与 FilterInputStream 对象相连以提供有用接口StringBufferInputStream(弃用)将 String 转换成 InputStream作为一种数据源:将其与 FilterInputStream 对象相连以提供有用接口FileInputStream用于从文件读取信息作为一种数据源:将其与 FilterInputStream 对象相连以提供有用接口PipedInputStream产生用于写入相关 PipedOutputStream 的数据。实现”管道化“概念作为多线程中数据源:将其与 FilterInputStream 对象相连以提供有用接口SequenceInputStream将两个或多个 InputStream 对象转换成单一 InputStream作为一种数据源:将其与 FilterInputStream 对象相连以提供有用接口FilterInputStream抽象类,作为装饰器接口。其中装饰器为其他的 InputStream 类提供游泳功能见↓

FilterInputStream 类的设计采用了装饰器模式,FilterInputStream 类是所有装饰器类的基类,为被装饰的对象提供通用接口,它的子类可以控制特定输入流,以及修改内部 InputStream 的行为方式:是否缓冲,是否保留读过的行,是否把单一字符回退输入流等。

类功能如何使用DataInputStream与 DataOutputStream 搭配使用,因此可以按照可移植方式从流读取基本数据类型包含用于读取基本类型数据的全部接口BufferedInputStream防止每次读取时都得进行实际写操作。代表”使用缓冲区“与接口对象搭配LineNumberInputStream(已弃用)跟踪输入流中的行号仅增加了行号,因此可能要与接口对象搭配使用PushbackInputStream具有”能弹出一个字节的缓冲区“。因此可以将读到的最后一个字符回退通常作为编译器的扫描器,包含在内是因为 Java 编译器的需要,我们几乎不会用到。

3.1.2 OutputStream

该类同样作为字节输出流的抽象基类,其类别决定了输出所要去往的目标:字节数组、文件或管道。

类功能如何使用ByteArrayOutputStream在内存中创建缓冲区,所有送往”流“的数据都要放置在此缓冲区用于指定数据的目的地:将其与 FilterInputStream 对象相连以提供有用接口FileOutputStream用于将信息写至文件用于指定数据的目的地:将其与 FilterInputStream 对象相连以提供有用接口PipedOutputStream任何写入其中的信息都会自动作为相关 PipedInputStream 的输出。实现管道化概念用于指定多线程的数据的目的地:将其与 FilterInputStream 对象相连以提供有用接口FiflterOutputStream抽象类,作为装饰器的接口。其中装饰器为其他 OutputStream 提供有用功能见↓

同样的,FilterOutputStream 也是装饰器模式:

类功能如何使用DataOutputStream与 DataInputStream 搭配使用,因此可以按照可移植方式向流写入基本数据类型包含用于写入基本类型数据的全部接口PrintStream用于产生格式化输出。其中 DataOutputStream 处理数据的存储,PrintStream 处理显示可以用 boolean 值显示是否在每次换行时清空缓冲区。等等BufferedOutputStream代表”使用缓冲区“。可以调用 flush() 清空缓冲区与接口对象搭配。

3.1.3 序列化

关于序列化对象的输入和输出流:

对象的输出流: ObjectOutputStream   对象的输入流: ObjectInputStream

使用:

对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。

示例:

User.java

public class User implements Serializable{

private static final long serialVersionUID = 1L;

String uid;

String pwd;

public User(String uid,String pwd) {

// TODO Auto-generated constructor stub

this.uid = uid;

this.pwd = pwd;

}

@Override

public String toString() {

// TODO Auto-generated method stub

return "id = " + this.uid + ", pwd = " + this.pwd;

}

}

解释一下 serialVersionUID 这个成员变量的作用:

它是用来记录 class 文件的版本信息,是 JVM 通过类的信息来算出的一个数字,如果我们不显式指定它,当序列话之后我们把这个 User 类改变了,比如增加一个方法,这时 serialVersionUID 的值也会随之改变,这样序列化文件中记录的 serialVersionUID 和项目中的不一致,就找不到对应的类来反序列化。

而当我们显式指定 serialVersionUID 的值后,JVM 就不会再计算这个 class 的 serialVersionUID 了,这样我们不用担心序列化后改变源文件后无法反序列化的问题了。

TestFile.java

public class TestFile {

static User user ;

public static void main(String[] args) {

File file = new File("D://user.txt");

writeObject(file);

readObject(file);

}

private static void writeObject(File file) {

user = new User("whdalive", "123...");

try {

FileOutputStream fOutputStream = new FileOutputStream(file);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fOutputStream);

objectOutputStream.writeObject(user);

objectOutputStream.close();

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

}

}

private static void readObject(File file) {

try {

FileInputStream fInputStream = new FileInputStream(file);

ObjectInputStream objectInputStream = new ObjectInputStream(fInputStream);

User user = (User) objectInputStream.readObject();

System.out.println("uid = " + user.uid + ", pwd = " + user.pwd);

} catch (Exception e) {

// TODO: handle exception

}

}

}

结果

//D://User.txt

 sr com.whdalive.io.User        L pwdt Ljava/lang/String;L uidq ~ xpt 123...t whdalive

//输出结果

uid = whdalive, pwd = 123...

3.2 字符流

这里由于 InputStream 和 Reader 类似,OutputStream 和 Writer 类似,只不过是面向的数据流不同,InputStream/OutputStream 是字节流,而 Reader/Writer 是字符流。因此只需要记忆对应关系即可。

字节流字符流InputStreamReader适配器:InputStreamReaderOutputStreamWriter适配器:OutputStreamWriterFileInputStreamFileReaderFileOutputStreamFileWriterStringBufferInputStream(已过时)StringReader无对应的类StringWriterByteArrayInputStreamCharArrayReaderByteArrayOutputStreamCharArrayWriterPipedInputStreamPipedReaderPipedOutputStreamPipedWriter

以下是“过滤器”的对应:

过滤器对应类FilterInputStreamFilterReaderFilterOutputStreamFilterWriterBufferedInputStreamBufferedReaderBufferedOutputStreamBufferedWriterDataInputStream使用DataInputStream(当需要使用 readline() 时,使用 BufferedReader)PirntStreamPrintWriterLineNumberInputStream(已弃用)LineNumberReaderStreamTokenizerStreamTokenizer(使用接收 Reader 的构造器)PushbackInputStreamPushbackReader

4. 如何选择 I/O 流

输入 vs 输出

输入:InputStream、Reader输出:OutputStream、Writer字节(音频文件、图片、歌曲等) vs 字符(涉及到中文文本等)

字节:InputStream、OutputStream字符:Reader、Writer数据来源和去处

文件

读:FileInputStream、FileReader写:FileOutputStream、FileWriter数组

byte[]:ByteArrayInputStream、ByteArrayOutputStreamchar[]:CharArrayReader、CharArrayWriterString

StringReader、StringWriter标准I/O

System.inSystem.outSystem.err格式化输出

printStream、printWriter

5. 典型使用实例

5.1 标准输入(键盘输入)显示到标准输出(显示器)

public class TestFile {

public static void main(String[] args) {

displayInput();

}

private static void displayInput() {

String ch;

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

try {

while ((ch =  in.readLine())!= null){

System.out.println(ch);

}

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

}

}

}

5.2 将文件内容打印到显示器

public class TestFile {

public static void main(String[] args) {

displayFile();

}

private static void displayFile() {

File file = new File(".\\src\\com\\whdalive\\io\\User.java");

String string;

StringBuilder sb = new StringBuilder();

BufferedReader bufferedReader;

try {

bufferedReader = new BufferedReader(new FileReader(file));

while((string = bufferedReader.readLine())!=null) {

sb.append(string + "\n");

}

bufferedReader.close();

System.out.println(sb.toString());

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

}

}

}

5.3 将标准输入保存到文件

public class TestFile {

public static void main(String[] args) {

copyScan();

}

private static void copyScan() {

Scanner in = new Scanner(System.in);

FileWriter out;

String string;

try {

out = new FileWriter("D://log.txt");

while(!(string = in.nextLine()).equals("Q")) {

out.write(string + "\n");

}

out.flush();

out.close();

in.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

总结

关于 Java I/O 本篇还远远不是全部,本文只是简单介绍了 BIO 的内容(即 JDK 1.0 就加入的 java.io 包)。虽然类的扩展性很好,但是代价也在此:实现一个输入输出,需要使用的类过多。尽管如此,只要分类记忆还是比较容易记住的,多学多用,掌握 Java I/O 不是什么特别难的问题。

愿本文对大家有所帮助。

共勉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值