11.3 I/O 类使用
由于在 IO 操作中, 须要使用的数据源有非常多, 作为一个 IO 技术的刚開始学习的人从读写文件開始学习 IO 技术是一个比較好的选择由于文件是一种常见的数据源, 并且读写文件也是程序猿进行 IO 编程的一个基本能力本章 IO 类的使用就从读写文件開始
11.3.1 文件操作
文件 (File) 是 最常见的数据源之中的一个, 在程序中常常须要将数据存储到文件里, 比如图片文件声音文件等数据文件, 也常常须要依据须要从指定的文件里进行数据的读取当然, 在实际使用时, 文件都包括一个的格式这个格式须要程序猿依据须要进行设计读取已有的文件时也须要熟悉相应的文件格式, 才干把数据从文件里正确的读取出 来
文件的存储介质有非常多, 比如硬盘光盘和 U 盘等由于 IO 类设计时, 从数据源转换为流对象的操作由 API 实现了所以存储介质的不同对于程序猿来说是透明的和实际编写代码无关
11.3.1.1 文件的概念
文件是计算机中一种主要的数据存储形式, 在实际存储数据时, 假设对于数据的读写速度要求不是非常高, 存储的数据量不是非常大时, 使用文件作为一种持久数据存储的方式是比較好的选择
存储在文件内部的数据和内存中的数据不同, 存储在文件里的数据是一种持久存储, 也就是当程序退出或计算机关机以后, 数据还是存在的, 而内存内部的数据在程序退出或计算机关机以后, 数据就丢失了
在不同的存储介质中, 文件里的数据都是以一定的顺序依次存储起来, 在实际读取时由硬件以及操作系统完毕对于数据的控制保证程序读取到的数据和存储的顺序保持一致
每一个文件以一个文件路径和文件名称称进行表示, 在须要訪问该文件的时, 仅仅须要知道该文件的路径以及文件的全名就可以在不同的操作系统环境下, 文件路径的表示形式是不一样的, 比如在 Windows 操作系统中一般的表示形式为 C:\windows\system, 而 Unix 上的表示形式为 / user/my 所以假设须要让 Java 程序能够在不同的操作系统下执行, 书写文件路径时还须要比較注意
11.3.1.1.1 绝对路径和相对路径
绝对路径是指书写文件的完整路径, 比如 d:\java\Hello.java, 该路径中包括文件的完整路径 d:\java 以及文件的全名 Hello.java 使用该路径能够唯一的找到一个文件, 不会产生歧义
可是使用绝对路径在表示文件时, 受到的限制非常大且不能在不同的操作系统下执行, 由于不同操作系统下绝对路径的表达形式存在不同
相对路径是指书写文件的部分路径比如 \ test\Hello.java, 该路径中仅仅包括文件的部分路径 \ test 和文件的全名 Hello.java 部分路径是指当前路径下的子路径比如当前程序在 d:\abc 下执行, 则该文件的完整路径就是 d:\abc\test 使用这样的形式能够更加通用的代表文件的位置使得文件路径产生一定的灵活性
在 Eclipse 项目中执行程序时, 当前路径是项目的根目录, 比如工作空间存储在 d:\javaproject, 当前项目名称是 Test, 则当前路径是: d:\javaproject\Test 在控制台以下执行程序时当前路径是 class 文件所在的目录假设 class 文件包括包名则以该 class 文件最顶层的包名作为当前路径
另外在 Java 语言的代码内部书写文件路径时须要注意大写和小写, 大写和小写须要保持一致, 路径中的目录名称区分大写和小写由于 \ 是 Java 语言中的特殊字符所以在代码内部书写文件路径时, 比如代表 c:\test\java\Hello.java 时, 须要书写成 c:\test\java\Hello.java 或 c:/test/java/Hello.java 这些都须要在代码中注意
11.3.1.1.2 文件名称称
文件名称称一般採用文件名称. 后缀名的形式进行命名当中文件名称用来表示文件的作用, 而使用后缀名来表示文件的类型这是当前操作系统中常见的一种形式, 比如 readme.txt 文件, 当中 readme 代表该文件时说明文件, 而 txt 后缀名代表文件时文本文件类型在操作系统中, 还会自己主动将特定格式的后缀名和相应的程序关联在双击该文件时使用特定的程序打开
事实上在文件名称称仅仅是一个标示, 和实际存储的文件内容没有必定的联系, 仅仅是使用这样的方式方便文件的使用
在程序中须要存储数据时, 假设自己设计了特定的文件格式则能够自己定义文件的后缀名来标示自己的文件类型
和文件路径一样, 在 Java 代码内部书写文件名称称时也区分大写和小写, 文件名称称的大写和小写必须和操作系统中的大写和小写保持一致
另外在书写文件名称称时不要忘记书写文件的后缀名
11.3.1.2 File 类
为了非常方便的代表文件的概念, 以及存储一些对于文件的基本操作, 在 java.io 包中设计了一个专门的类 File 类
在 File 类中包括了大部分和文件操作的功能方法, 该类的对象能够代表一个详细的文件或目录, 所以曾经曾有人建议将该类的类名改动成 FilePath, 由于该类也能够代表一个目录, 更准确的说是能够代表一个文件路径
以下介绍一下 File 类的基本使用
1File 对象代表文件路径
File 类的对象能够代表一个详细的文件路径在实际代表时, 能够使用绝对路径也能够使用相对路径
以下是创建的文件对象演示样例
public File(String pathname)
该演示样例中使用一个文件路径表示一个 File 类的对象, 比如:
- File f1 = new File(d:\test\1.txt);
- File f2 = new File(1.txt);
- File f3 = new File(e:\abc);
这里的 f1 和 f2 对象分别代表一个文件 f1 是绝对路径, 而 f2 是相对路径 f3 则代表一个目录目录也是文件路径的一种
public File(String parent, String child)
也能够使用父路径和子路径结合, 实现代表文件路径比如:
File f4 = new File(d:\\test\\,1.txt);
这样代表的文件路径是: d:\test\1.txt
2File 类常常用法
File 类中包括了非常多获得文件或目录属性的方法, 使用起来比較方便, 以下将常见的方法介绍例如以下:
acreateNewFile 方法
public boolean createNewFile() throws IOException
该方法的作用是创建指定的文件
该方法仅仅能用于创建文件, 不能用于创建目录, 且文件路径中包括的目录必须存在
bdelect 方法
public boolean delete()
该方法的作用是删除当前文件或目录
假设删除的是目录, 则该目录必须为空假设须要删除一个非空的目录, 则须要首先删除该目录内部的每一个文件和目录, 然后在能够删除这个须要书写一定的逻辑代码实现
cexists 方法
public boolean exists()
该方法的作用是推断当前文件或目录是否存在
dgetAbsolutePath 方法
public String getAbsolutePath()
该方法的作用是获得当前文件或目录的绝对路径比如 c:\test\1.t 则返回 c:\test\1.t
egetName 方法
public String getName()
该方法的作用是获得当前文件或目录的名称
比如 c:\test\1.t, 则返回 1.t
fgetParent 方法
public String getParent()
该方法的作用是获得当前路径中的父路径
比如 c:\test\1.t 则返回 c:\test
gisDirectory 方法
public boolean isDirectory()
该方法的作用是推断当前 File 对象是否是目录
hisFile 方法
public boolean isFile()
该方法的作用是推断当前 File 对象是否是文件
ilength 方法
public long length()
该方法的作用是返回文件存储时占用的字节数该数值获得的是文件的实际大小, 而不是文件在存储时占用的空间数
jlist 方法
public String[] list()
该方法的作用是返回当前目录下全部的文件名称和目录名称
说明, 该名称不是绝对路径
klistFiles 方法
public File[] listFiles()
该方法的作用是返回当前目录下全部的文件对象
lmkdir 方法
public boolean mkdir()
该方法的作用是创建当前文件目录, 而不创建该路径中的其他目录假设 d 盘下仅仅有一个 test 目录, 则创建 d:\test\abc 目录则成功, 假设创建 d:\a\b 目录则创建失败, 由于该路径中 d:\a 目录不存在假设创建成功则返回 true, 否则返回 false
mmkdirs 方法
public boolean mkdirs()
该方法的作用是创建目录, 假设当前路径中包括的父目录不存在时, 也会自己主动依据须要创建
nrenameTo 方法
public boolean renameTo(File dest)
该方法的作用是改动文件名称在改动文件名称时不能改变文件路径, 假设该路径下已有该文件, 则会改动失败
osetReadOnly 方法
public boolean setReadOnly()
该方法的作用是设置当前文件或目录为仅仅读
3File 类基本演示样例
以上各方法实现的測试代码例如以下:
import java.io.File;
* File 类使用演示样例
- */
- public class FileDemo {
- public static void main(String[] args) {
- // 创建 File 对象
- File f1 = new File("d:\\test");
- File f2 = new File("1.txt");
- File f3 = new File("e:\\file.txt");
- File f4 = new File("d:\\","1.txt");
- // 创建文件
- try{
- boolean b = f3.createNewFile();
- }catch(Exception e){
- e.printStackTrace();
- }
- // 推断文件是否存在
- System.out.println(f4.exists());
- // 获得文件的绝对路径
- System.out.println(f3.getAbsolutePath());
- // 获得文件名称
- System.out.println(f3.getName());
- // 获得父路径
- System.out.println(f3.getParent());
- // 推断是否是目录
- System.out.println(f1.isDirectory());
- // 推断是否是文件
- System.out.println(f3.isFile());
- // 获得文件长度
- System.out.println(f3.length());
- // 获得当前目录下全部文件和目录名称
- String[] s = f1.list();
- for(int i = 0;i < s.length;i++){
- System.out.println(s[i]);
- }
- // 获得文件对象
- File[] f5 = f1.listFiles();
- for(int i = 0;i < f5.length;i++){
- System.out.println(f5[i]);
- }
- // 创建目录
- File f6 = new File("e:\\test\\abc");
- boolean b1 = f6.mkdir();
- System.out.println(b1);
- b1 = f6.mkdirs();
- System.out.println(b1);
- // 改动文件名称
- File f7 = new File("e:\\a.txt");
- boolean b2 = f3.renameTo(f7);
- System.out.println(b2);
- // 设置文件为仅仅读
- f7.setReadOnly();
- }
- }
4File 类综合演示样例
以下以两个演示样例演示 File 类的综合使用第一个演示样例是显示某个目录下的全部文件和目录原理是输出当前名称然后推断当前 File 对 象是文件还是目录, 假设则获得该目录下的全部子文件和子目录并递归调用该方法实现第二个演示样例是删除某个目录下的全部文件和目录原理是推断 是否是文件, 假设是文件则直接删除, 假设是目录则获得该目录下全部的子文件和子目录, 然后递归调用该方法处理全部子文件和子目录, 然后将空文件 夹删除
则測试时慎重使用第二个方法, 以免删除自己实用的数据文件演示样例代码例如以下:
- import java.io.File;
- /**
- * 文件综合使用演示样例
- */
- public class AdvanceFileDemo {
- public static void main(String[] args) {
- File f = new File("e:\\Book");
- printAllFile(f);
- File f1 = new File("e:\\test");
- deleteAll(f1);
- }
- /**
- * 打印 f 路径下全部的文件和目录
- * @param f 文件对象
- */
- public static void printAllFile(File f){
- // 打印当前文件名称
- System.out.println(f.getName());
- // 是否是目录
- if(f.isDirectory()){
- // 获得该目录下全部子文件和子目录
- File[] f1 = f.listFiles();
- // 循环处理每一个对象
- int len = f1.length;
- for(int i = 0;i < len;i++){
- // 递归调用处理每一个文件对象
- printAllFile(f1[i]);
- }
- }
- }
- /**
- * 删除对象 f 下的全部文件和目录
- * @param f 文件路径
- */
- public static void deleteAll(File f){
- // 文件
- if(f.isFile()){
- f.delete();
- }else{ // 目录
- // 获得当前目录下的全部子文件和子目录
- File f1[] = f.listFiles();
- // 循环处理每一个对象
- int len = f1.length;
- for(int i = 0;i < len;i++){
- // 递归调用处理每一个文件对象
- deleteAll(f1[i]);
- }
- // 删除当前目录
- f.delete();
- }
- }
- }
关于 File 类的使用就介绍这么多其他的方法和使用时须要注意的问题还须要多进行练习和实际使用
11.3.1.3 读取文件
尽管前面介绍了流的概念可是这个概念对于刚開始学习的人来说, 还是比較抽象的以下以实际的读取文件为样例介绍流的概念以及输入流的基本使用
依照前面介绍的知识将文件里的数据读入程序, 是将程序外部的数据传入程序中, 应该使用输入流 InputStream 或 Reader
而由于读取的是特定的数据源文件则能够使用输入相应的子类 FileInputStream 或 FileReader 实现
在实际书写代码时, 须要首先熟悉读取文件在程序中实现的过程
在 Java 语言的 IO 编程中读取文件是分两个步骤: 1 将文件里的数据转换为流, 2 读取流内部的数据当中第一个步骤由系统完毕仅仅须要创建相应的流对象就可以, 对象创建完毕以后步骤 1 就完毕了第二个步骤使用输入流对象中的 read 方法就可以实现了
使用输入流进行编程时代码一般分为 3 个部分: 1 创建流对象, 2 读取流对象内部的数据, 3 关闭流对象以下以读取文件的代码演示样例:
- import java.io.*;
- /**
- * 使用 FileInputStream 读取文件
- */
- public class ReadFile1 {
- public static void main(String[] args) {
- // 声明流对象
- FileInputStream fis = null;
- try{
- // 创建流对象
- fis = new FileInputStream("e:\\a.txt");
- // 读取数据, 并将读取到的数据存储到数组中
- byte[] data = new byte[1024]; // 数据存储的数组
- int i = 0; // 当前下标
- // 读取流中的第一个字节数据
- int n = fis.read();
- // 依次读取兴许的数据
- while(n != -1){ // 未到达流的末尾
- // 将有效数据存储到数组中
- data[i] = (byte)n;
- // 下标添加
- i++;
- // 读取下一个字节的数据
- n = fis.read();
- }
- // 解析数据
- String s = new String(data,0,i);
- // 输出字符串
- System.out.println(s);
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- try{
- // 关闭流, 释放资源
- fis.close();
- }catch(Exception e){}
- }
- }
- }
在该演示样例代码中首先创建一个 FileInputStream 类型的对象 fis:
fis = new FileInputStream(e:\a.txt);
这样建立了一个连接到数据源 e:\a.txt 的流并将该数据源中的数据转换为流对象 fis, 以后程序读取数据源中的数据, 仅仅须要从流对象 fis 中读取就可以
读取流 fis 中的数据须要使用 read 方法该方法是从 InputStream 类中继承过来的方法该方法的作用是每次读取流中的一个字节假设须要读取流中的全部数据, 须要使用循环读取, 当到达流的末尾时, read 方法的返回值是 - 1
在该演示样例中, 首先读取流中的第一个字节:
int n = fis.read();
并将读取的值赋值给 int 值 n, 假设流 fis 为空, 则 n 的值是 - 1, 否则 n 中的最后一个字节包括的时流 fis 中的第一个字节该字节被读取以后, 将被从流 fis 中删除
然后循环读取流中的其他数据, 假设读取到的数据不是 - 1, 则将已经读取到的数据 n 强制转换为 byte 即取 n 中的有效数据最后一个字节, 并存储到数组 data 中然后调用流对象 fis 中的 read 方法继续读取流中的下一个字节的数据
一直这样循环下去, 直到读取到的数据是 - 1, 也就是读取到流的末尾则循环结束
这里的数组长度是 1024 所以要求流中的数据长度不能超过 1024 所以该演示样例代码在这里具有一定的局限性假设流的数据个数比較多, 则能够将 1024 扩大到合适的个数就可以
经过上面的循环以后就能够将流中的数据依次存储到 data 数组中, 存储到 data 数组中有效数据的个数是 i 个即循环次数
事实上截至到这里 IO 操作中的读取数据已经完毕然后再依照数据源中的数据格式这里是文件的格式, 解析读取出的 byte 数组就可以
该演示样例代码中的解析, 仅仅是将从流对象中读取到的有效的数据也就是 data 数组中的前 n 个数据转换为字符串, 然后进行输出
在该演示样例代码中, 仅仅是在 catch 语句中输出异常的信息, 便于代码的调试在实际的程序中, 须要依据情况进行一定的逻辑处理比如给出提示信息等
最后在 finally 语句块中, 关闭流对象 fis, 释放流对象占用的资源, 关闭数据源, 实现流操作的结束工作
上面详细介绍了读取文件的过程, 事实上在实际读取流数据时还能够使用其他的 read 方法以下的演示样例代码是使用另外一个 read 方法实现读取的代码:
- import java.io.FileInputStream;
- /**
- * 使用 FileInputStream 读取文件
- */
- public class ReadFile2 {
- public static void main(String[] args) {
- // 声明流对象
- FileInputStream fis = null;
- try{
- // 创建流对象
- fis = new FileInputStream("e:\\a.txt");
- // 读取数据, 并将读取到的数据存储到数组中
- byte[] data = new byte[1024]; // 数据存储的数组
- int i = fis.read(data);
- // 解析数据
- String s = new String(data,0,i);
- // 输出字符串
- System.out.println(s);
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- try{
- // 关闭流, 释放资源
- fis.close();
- }catch(Exception e){}
- }
- }
- }
该演示样例代码中仅仅使用一行代码:
int i = fis.read(data);
就实现了将流对象 fis 中的数据读取到字节数组 data 中该行代码的作用是将 fis 流中的数据读取出来, 并依次存储到数组 data 中, 返回值为实际读取的有效数据的个数
使用该中方式在进行读取时, 能够简化读取的代码
当然, 在读取文件时, 也能够使用 Reader 类的子类 FileReader 进行实现, 在编写代码时, 仅仅须要将上面演示样例代码中的 byte 数组替换成 char 数组就可以
使用 FileReader 读取文件时, 是依照 char 为单位进行读取的, 所以更适合于文本文件的读取, 而对于二进制文件或自己定义格式的文件来说还是使用 FileInputStream 进行读取, 方便对于读取到的数据进行解析和操作
读取其他数据源的操作和读取文件相似最大的差别在于建立流对象时选择的类不同而流对象一旦建立, 则主要的读取方法是一样, 假设仅仅使用最主要的 read 方法进行读取则使用基本上是一致的这也是 IO 类设计的初衷, 使得对于流对象的操作保持一致, 简化 IO 类使用的难度
程
主要的输出流包括 OutputStream 和 Writer 两个, 差别是 OutputStream 体系中的类 (也就是 OutputStream 的子类) 是依照字节写入的, 而 Writer 体系中的类 (也就是 Writer 的子类) 是依照字符写入的
使用输出流进行编程的步骤是:
1 建立输出流
建立相应的输出流对象, 也就是完毕由流对象到外部数据源之间的转换
2 向流中写入数据
将须要输出的数据, 调用相应的 write 方法写入到流对象中
3 关闭输出流
在写入完毕以后, 调用流对象的 close 方法关闭输出流, 释放资源
在使用输出流向外部输出数据时, 程序猿仅仅须要将数据写入流对象就可以, 底层的 API 实现将流对象中的内容写入外部数据源, 这个写入的过程对于程序猿来说是透明的, 不须要专门书写代码实现
在向文件里输出数据也就是写文件时, 使用相应的文件输出流, 包括 FileOutputStream 和 FileWriter 两个类以下以 FileOutputStream 为样例说明输出流的使用
演示样例代码例如以下:
- import java.io.*;
- /**
- * 使用 FileOutputStream 写文件演示样例
- */
- public class WriteFile1 {
- public static void main(String[] args) {
- String s = "Java 语言";
- int n = 100;
- // 声明流对象
- FileOutputStream fos = null;
- try{
- // 创建流对象
- fos = new FileOutputStream("e:\\out.txt");
- // 转换为 byte 数组
- byte[] b1 = s.getBytes();
- // 换行符
- byte[] b2 = "\r\n".getBytes();
- byte[] b3 = String.valueOf(n).getBytes();
- // 依次写入文件
- fos.write(b1);
- fos.write(b2);
- fos.write(b3);
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- try{
- fos.close();
- }catch(Exception e){}
- }
- }
- }
该演示样例代码写入的文件使用记事本打开以后, 内容为:
Java 语言 100
在该演示样例代码中, 演示了将一个字符串和一个 int 类型的值依次写入到同一个文件里在写入文件时, 首先创建了一个文件输出流对象 fos:
fos = new FileOutputStream(e:\out.txt);
该对象创建以后, 就实现了从流到外部数据源 e:\out.txt 的连接
说明: 当外部文件不存在时系统会自己主动创建该文件, 可是假设文件路径中包括未创建的目录时将出现异常
这里书写的文件路径能够是绝对路径也能够是相对路径
在 实际写入文件时, 有两种写入文件的方式: 覆盖和追加当中覆盖是指清除原文件的内容, 写入新的内容默认採用该种形式写文件追加是指在已有文件 的末尾写入内容, 保留原来的文件内容, 比如写日志文件时, 一般採用追加在实际使用时能够依据须要採用适合的形式能够使用:
public FileOutputStream(String name, boolean append) throws FileNotFoundException
仅仅须要使用该构造方法在构造 FileOutputStream 对象时将第二个參数 append 的值设置为 true 就可以
流对象创建完毕以后, 就能够使用 OutputStream 中提供的 wirte 方法向流中依次写入数据了最主要的写入方法仅仅支持 byte 数组格式的数据所以假设须要将内容写入文件, 则须要把相应的内容首先转换为 byte 数组
这里以例如以下格式写入数据: 首先写入字符串 s 使用 String 类的 getBytes 方法将该字符串转换为 byte 数组然后写入字符串 \ r\n, 转换方式同上该字符串的作用是实现文本文件的换行显示, 最后写入 int 数据 n, 首先将 n 转换为字符串再转换为 byte 数组这样的写入数据的顺序以及转换为 byte 数组的方式就是流的数据格式也就是该文件的格式由于这里写的都是文本文件所以写入的内容以明文的形式显示出来, 也能够依据自己须要存储的数据设定特定的文件格式
事实上, 全部的数据文件, 包括图片文件声音文件等等, 都是以一定的数据格式存储数据的在保存该文件时, 将须要保存的数据依照该文件的数据格式依次写入就可以, 而在打开该文件时, 将读取到的数据依照该文件的格式解析成相应的逻辑就可以
最后, 在数据写入到流内部以后, 假设须要马上将写入流内部的数据强制输出到外部的数据源, 则能够使用流对象的 flush 方法实现假设不须要强制输出, 则仅仅须要在写入结束以后关闭流对象就可以在关闭流对象时, 系统首先将流中未输出到数据源中的数据强制输出, 然后再释放该流对象占用的内存空间
使用 FileWriter 写入文件时, 步骤和创建流对象的操作都和该演示样例代码一致仅仅是在转换数据时须要将写入的数据转换为 char 数组对于字符串来说, 能够使用 String 中的 toCharArray 方法实现转换, 然后依照文件格式写入数据就可以
对于其他类型的字节输出流 / 字符输出流来说, 仅仅是在逻辑上连接不同的数据源, 在创建对象的代码上会存在一定的不同, 可是一旦流对象创建完毕以后, 主要的写入方法都是 write 方法也须要首先将须要写入的数据依照一定的格式转换为相应的 byte 数组 / char 数组然后依次写入就可以
所以 IO 类的这样的设计形式仅仅须要熟悉该体系中的某一个类的使用以后, 就能够触类旁通的学会其他同样类型的类的使用, 从而简化程序猿的学习使得使用时保持统一
来源: http://www.bubuko.com/infodetail-2525213.html