一、简介
Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列,也就是大家习惯称呼的字节流和字符流。
网上有个很好的比喻:
数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,看不到数据流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据(不能随机读取)。不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。
简而言之:数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
当程序需要读取数据的时候,就会建立一个通向数据源的连接,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会建立一个通向目的地的连接。
字节流:数据流中最小的数据单元是字节 ASCII 编码数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
二、概览
Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。
Java I/O主要包括如下3层次:
- 流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
- 非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
- 其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
主要类如下:
- File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
- InputStream(字节流,二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(字节流,二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(字符流,文本格式操作):抽象类,基于字符的输入操作。
- Writer(字符流,文本格式操作):抽象类,基于字符的输出操作。
- RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
三、用法用例
1.I/O的开始——File
I/O流本质是对文件的处理。Java提供了对文件的常见操作,创建,重命名,删除
创建:
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。
删除:
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。
文件夹相关:
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
2.InputStream——输入流
他是一个抽象类,常用子类FileInputStream。核心方法read(),包含三个重载方法;close(),流使用结束后必须关闭!!!
2. 读入方法:read
跟读入相关的方法是这个类的核心方法。有3种重载的形式,下面分别介绍。
2.1 read()
|
读取输入流的下一个字节。这是一个抽象方法,不提供实现,子类必须实现这个方法。该方法读取下一个字节,返回一个0-255之间的int类型整数。如果到达流的末端,返回-1. 调用该方法的时候,方法阻塞直到出现下列其中一种情况:1)遇到流的尾部(end of the stream)。2)有数据可以读入。3)抛出异常。 面向字节的操作时,可能需要像这样比较底层的字节操作。我们也可以一次读入多个字节,使用下面的重载形式。
2.2 read(byte[] b)
public int read(byte b[]) throws IOException
试图读入多个字节,存入字节数组b,返回实际读入的字节数。如果传递的是一个空数组(注意数组长度可以为0,即空数组。比如 byte[] b = new byte[0]; 或者byte[] b = {};)那么什么也没读入,返回0.
如果到达流尾部,没有字节可读,返回-1;如果上面两种情况都没有出现,并且没有I/O错误,则至少有1个字节被读入,存储到字节数组b中。实际读入的第一个字节存在b[0],往后一次存入数组,读入的字节数最多不能超过数组b的长度。如果读入的字节数小于b的长度,剩余的数组元素保持不变。具体地,如果读入的字节数为k,则k个字节分别存在 b[0]到b[k-1],而b[k]到b[b.length-1]保持原来的数据。
2.3 read (byte[] b, int off, int len)
|
这个方法跟上一个功能类似,除了读入的数据存储到b数组是从off开始。len是试图读入的字节数,返回的是实际读入的字节数。如果len=0,则什么也不读入,返回0;如果遇到流尾部,返回-1.否则至少读入一个字节。
假设实际读入k个字节,则k个字节分别存储在b[off]到b[off+k-1],而b[off+k]往后的元素保持不变。b[off]之前也保持不变。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
解析来看一下第三个read方法的源代码:
public int read(byte b[], int off, int len) throws IOException {
if (b == null) { // 检测参数是否为null
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException(); // 数组越界检测
} else if (len == 0) {
return 0; //如果b为空数组,返回0
}
int c = read(); // 调用read()方法获取下一个字节
if (c == -1) {
return -1;
} // 遇到流尾部,返回-1
b[off] = (byte)c; //读入的第一个字节存入b[off]
int i = 1; // 统计实际读入的字节数
try {
for (; i < len ; i++) { // 循环调用read,直到流尾部
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c; // 一次存入字节数组
}
} catch (IOException ee) {
}
return i; // 返回实际读入的字节数
}
我们看到方法可能抛出IOException异常,如果第一个字节无法读入且原因不是到达流尾部,或者流已经被关闭,或者其他IO错误,则抛出这个异常。
上面三个读入方法都可能出现阻塞,在2.1中已经介绍了阻塞解除的条件。理解这三个方法很重要的一点是:方法只是尝试读入我们想要的字节数,但是能否成功, 会受到数据源的影响。另外一点就是读入的数据存到哪里,第一个方法作为返回值,第二、三个方法存入到指定数组的指定位置,返回的是实际读入的字节数。后两个方法真正的读入工作都是通过调用抽象方法read()来完成的,资格抽象方法在子类中实现。
3.OutputStream——输出流
抽闲类,常用子类FileOutputStream,ByteArrayOuputStream,核心方法write(),也包含了3个重载方法;close(),流使用结束后必须关闭!!!onflush(),刷新此输出流并强制写出所有缓冲的输出字节。
(1) write(byte[]),写入一个byte数组的内容;
(2)write(byte[],int off,int len) 将指定 byte 数组中从偏移量 off
开始的 len
个字节写入此输出流。
(3)write(int b) 将指定的字节写入到输出流
示例:复制文件
public static void testMethod2() {
51
52 File fileIN = new File("d:/TEST/MyFile.jpg"); //定义输入文件
53 File fileOUT = new File("d:/TEST/MyFileCopy.jpg"); //定义输出文件
54
55 FileInputStream fis = null;
56 FileOutputStream fos = null;
57
58 try {
59
60 fis = new FileInputStream(fileIN); //输入流连接到输入文件
61 fos = new FileOutputStream(fileOUT); //输出流连接到输出文件
62
63 byte[] arr = new byte[2048]; //该数组用来存入从输入文件中读取到的数据
64 int len; //变量len用来存储每次读取数据后的返回值
65
66 while( ( len=fis.read(arr) ) != -1 ) {
67 fos.write(arr, 0, len);
68 }//while循环:每次从输入文件读取数据后,都写入到输出文件中
69
70 } catch (IOException e) {
71 e.printStackTrace();
72 }
73
74 //关闭流
75 try {
76 fis.close();
77 fos.close();
78 } catch (IOException e) {
79 e.printStackTrace();
80 }
81 }
File相当于管道,InputStream和OutPutStream相当于2股水流,字节流和字符流沿着管道从文件流出到程序或者从程序流入到文件。
四、常见类之间的转换
1.String转字节流
String s="abc";
byte[] b=s.getbyte();
2.byte[ ]转String
String s=new String(byte[]);
3.byte[]写入到输入流InputStream
(1)byte[]转换为InputStream
InputStream sbs = new ByteArrayInputStream(byte[] buf);
(2)InputStream转换为InputStreambyte[]
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100]; //buff用于存放循环读取的临时数据
int rc = 0;
while ((rc = inStream.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in_b = swapStream.toByteArray(); //in_b为转换之后的结果
4.byte[ ]转换输出流OutputStream——write(byte[ ])
五、BufferedOutputStream 缓冲的输出流
它提供了和FileOutputStream类同样的写操作方法,不同的是:
它把所有的输出先全部写入缓冲区中,当缓冲区满了或者该输出流关闭的时候,它再一次性的输出到流。或者也可以显式的调用flush() 方法主动将缓冲区输出到流。通过减小系统写数据的时间而提高了性能。
// 构造函数,接收的是一个**节点流**
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
它的使用方式:
File file = new File("G:/a2.txt");
FileOutputStream out = new FileOutputStream(file);
BufferedOutputStream bufferedOut = new BufferedOutputStream(out);
bufferedOut.write(byteData);
// 需要注意的是,如果下面的两句不写上,是不会写入到文件中去的,因为此时要写入的数据还在缓冲区
bufferedOut.flush(); // 将缓冲区的字节写入到文件中,然后清空缓冲区
bufferedOut.close(); // 关闭的时候也会检查缓冲区,缓冲区有数据,会先写入文件清空缓冲区,再关闭。
其他,Reader和Writer字符流操作在此不再详细介绍,感兴趣可以在,查看官方API文档,获取用法。