Java笔记---IO流
本站字数:108k 本文字数:5.6k 预计阅读时长:24min 访问次数:次
- I/O是输入输出的缩写,I/O技术用于处理设备之间的数据传输,如读写文件,网络通讯等等
- Java程序中,对于数据的输入输出操作以”流(Strem)“的方式进行
- java.io包下面提供了各种”流“类和接口,用以获取不同种类的数据,并通过标准的方法输入成输出数据
流的分类
- 根据数据的流向分为输入流,输出流。
- 根据操作数据单位分为:字节流(8 bit), 字符流。
- 根据流的角色不同分为:节点流,处理流。
IO流体系
分类 |
字节输入流 |
字节输出流 |
字符输入流 |
字符输出流 |
抽象基类 |
InputStrem |
OutputStream |
Reader |
Writer |
访问文件 |
FileInputStrem |
FIleOutputStream |
FileReader |
FileWriter |
访问数组 |
ByteArrayInputStrem |
ByteArrayOutputStream |
CharArrayReader |
CharArrayWriter |
访问管道 |
PipedInputStrem |
PipedOutputStream |
PipedReader |
PipedWriter |
访问字符串 |
|
|
StringReader |
StringWriter |
缓冲流 |
BufferedInputStrem |
BufferedOutputStream |
BufferedReader |
BufferedWriter |
转换流 |
|
|
InputStreamReader |
OutputStreamWriter |
对象流 |
ObjectInputStrem |
ObjectOutputStream |
|
|
|
FliterInputStrem |
FliterOutputStream |
FliterReader |
FliterWriter |
打印流 |
|
PrintStream |
|
PrintWriter |
推回输入流 |
PushbackInputStrem |
|
PushbackReader |
|
特殊流 |
DataInputStrem |
DataOutputStream |
|
|
访问文件的是一种节点流。缓冲流的就是处理流。斜体的就是需要注意的。
FileReader读入数据的基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public void testFileReader() { File file = new File("IOTest/hello.txt"); FileReader fr = null; try { fr = new FileReader(file);
int data; while ((data = fr.read()) != -1) System.out.println((char) data); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }finally { try { if (fr != null) fr.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
说明:
- read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1。
- 异常的处理:为了保证流资源一定可以关闭,使用try - catch - finally处理
- 读入文件一定要存在,否则会抛出FileNotFountException。
对read()操作升级,使用read的重载方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public void testFileReader1() { File file = new File("IOTest/hello.txt"); FileReader fr = null;
try {
fr = new FileReader(file);
char[] cbuf = new char[5]; int len; while((len =fr.read(cbuf)) != -1) {
String str = new String(cbuf, 0, len); System.out.print(str); } } catch (IOException e) { e.printStackTrace(); } finally { try {
if (fr != null) fr.close();
} catch (IOException e) { e.printStackTrace(); } } }
|
FileWriter数据写出的基本操作
从内存中写出数据到硬盘的文件里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void testFileWriter() throws IOException { File file = new File("IOTest/hi.txt");
FileWriter fw = new FileWriter(file, true);
fw.write("I have a dream"); fw.write("you need to have a dream!");
fw.close(); }
|
说明:
- 输出操作,对应的文件如果不存在,并不会报错
- File对应的硬盘中的文件如果不存在,在输出过程中,会自动创建这个文件。
- File对应的硬盘中的文件如果存在,在输出过程中,会将原有的文件覆盖。
- 如果使用的构造器是:File(File file, boolean append) append参数为true时,则不会对原来文件覆盖,而是在,原有文件上追加。
文件的拷贝工作,利用FileWirter和FileReader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public void fileReaderWriter() { FileReader fr = null; FileWriter fw = null;
try { File srcFile = new File("IOTest/hello.txt"); File destFile = new File("IOTest/hi.txt");
fr = new FileReader(srcFile); fw = new FileWriter(destFile);
char[] cbuf = new char[5]; int len; while((len = fr.read(cbuf)) != -1){ fw.write(cbuf, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally {
try { if (fw != null) fw.close(); } catch (IOException e) { e.printStackTrace(); } try { if (fr != null) fr.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
非文本文件的复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public void fileIOStream() { FileInputStream fi = null; FileOutputStream fo = null;
try { File srcFile = new File("IOTest/pic1.png"); File destFile = new File("IOTest/pic2.png");
fi = new FileInputStream(srcFile); fo = new FileOutputStream(destFile);
byte[] bbuf = new byte[5]; int len; while((len = fi.read(bbuf)) != -1){ fo.write(bbuf, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fo != null) fo.close(); } catch (IOException e) { e.printStackTrace(); } try { if (fi != null) fi.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
实例:指定文件下文件的复制操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public void testCopyFile() { long start = System.currentTimeMillis();
String srcPath = "IOTest/test.exe"; String destPath = "IOTest/dest.exe"; copyFile(srcPath, destPath);
long end = System.currentTimeMillis();
System.out.println("所用时间为:"+(start - end)+"毫秒"); }
public void copyFile(String srcPath, String destPath) { FileInputStream fi = null; FileOutputStream fo = null; try { File srcFile = new File(srcPath); File destFile = new File(destPath);
fi = new FileInputStream(srcFile); fo = new FileOutputStream(destFile);
int len; byte[] buffer = new byte[1024]; while ((len = fi.read(buffer)) != -1){ fo.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(fo != null) fo.close(); } catch (IOException e) { e.printStackTrace(); } try { if(fi != null) fi.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
缓冲流
缓冲流的相关类:
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWriter
前两种是字节流,后两种是字符流。缓冲流是一种处理流,上文提到的File一揽子的流是节点流,或者文件流。
作用:提高流的读取,写入的速度。
实例:指定文件复制的缓冲流实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public void BufferedStreamTest() { long start = System.currentTimeMillis(); BufferedInputStream bis = null; BufferedOutputStream bos = null; try { File srcFile = new File("IOTest/test.exe"); File destFile = new File("IOTest/dest.exe");
FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(destFile); bis = new BufferedInputStream(fis); bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (bis != null) bis.close(); } catch (IOException e) { e.printStackTrace(); } try { if (bos != null) bos.close(); } catch (IOException e) { e.printStackTrace(); } } long end = System.currentTimeMillis(); System.out.println("所用时间为:"+(end - start)+"毫秒"); }
|
项目 |
文件流 |
缓冲流 |
文件大小 |
673MiB |
673MiB |
byte数组大小 |
1024 |
1024 |
运行时间 |
7175 ms |
1593 ms |
从上面的实验可知Buffered流速度更快。
转换流
转换流提供了在字节流和字符流之间的转换
用于转换不同的字符编码
转换流的相关类:
InputStreamReader:
将一个字节的输入流转换为字符的输入流。
OutputStreamWriter:
将一个字符的输出流转换为字节的输出流。
解码:字节、字节数组 ===> 字符、字符数组
编码:字符、字符数组 ===> 字节、字节数组
字符集:UTF-8,gbk等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public void test1() throws IOException { FileInputStream fis = new FileInputStream("IOTest/test.txt"); InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
char[] cbuf = new char[20]; int len; while ((len = isr.read(cbuf)) != -1){ String str = new String(cbuf, 0, len); System.out.print(str); }
isr.close(); }
|
转换流实现文件的转码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
public void test2() { InputStreamReader isr = null; OutputStreamWriter osw = null;
try { File file1 = new File("IOTest/test.txt"); File file2 = new File("IOTest/test-gbk.txt");
FileInputStream fis = new FileInputStream(file1); FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis, "UTF-8"); osw = new OutputStreamWriter(fos, "gbk");
char[] cbuf = new char[20]; int len; while ((len = isr.read(cbuf)) != -1) { osw.write(cbuf, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { if (isr != null) { try { isr.close(); } catch (IOException e) { e.printStackTrace(); } } if (osw != null) { try { osw.close(); } catch (IOException e) { e.printStackTrace(); } } }
}
|
字符集
- ASCII:美国标准信息交换码。
- ISO8859-1:拉丁码表,欧洲码表。
- GB2312:中国的中文码表,最多两个字节编码所有字符。
- GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多两个字节编码
- Unicode:国际编码,融合了目前人类是同的所有字符,位每个字符分配唯一的字符码。
- UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
标准的输入输出流
标准的输入输出流
System.in:标准的输入流,默认从键盘输入。
System.out:标准的输出流,默认从控制台输出。
System类的setIn(InputStream in) / setOut(PrintStream out) 方法重新指定输入和输出的方式
小练习:输入字符串,“e”结束程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public void StranderdIOStream() { BufferedReader br = null;
try { InputStreamReader isr = new InputStreamReader(System.in); br = new BufferedReader(isr);
while (true) { System.out.println("请输入字符串:"); String data = br.readLine(); if (data.equalsIgnoreCase("e") || data.equalsIgnoreCase("exit")){ System.out.println("程序结束"); break; }
String upperCase = data.toUpperCase(); System.out.println(upperCase); } } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
打印流(了解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void PrintStreamTest() { PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream(new File("IOTest/testOutput.txt")); ps = new PrintStream(fos, true); if (ps != null) System.setOut(ps);
for (int i = 0; i <= 255; i++) { System.out.print((char)i); if (i%50 == 0) System.out.println(); }
} catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (ps != null) ps.close(); } }
|
数据流(了解)
- 方便操作java语言的基本数据类型和String的数据,可以使用数据流。
- 数据流有两个类:
- DataInputStream和DataOutputStream
- 作用:用于读取和写出基本数据类型的变量或字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public void dataOutputStreamTest(){
DataOutputStream dos = null; try { dos = new DataOutputStream(new FileOutputStream("IOTest/data.txt"));
dos.writeUTF("ABC"); dos.flush(); dos.writeInt(345); dos.flush(); dos.writeBoolean(true); dos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (dos != null) { try { dos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
public void dataInputStreamTest() {
DataInputStream dis = null; try { dis = new DataInputStream(new FileInputStream("IOTest/data.txt")); String name = dis.readUTF(); int age = dis.readInt(); boolean sex = dis.readBoolean();
System.out.println("name = " + name); System.out.println("age = " + age); System.out.println("sex = " + sex); } catch (IOException e) { e.printStackTrace(); } finally { if (dis != null) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
注意!!! 在读取文件时,一定要读取顺序与写入顺序一致,否则文件读取时会出现错误。
对象流
- 数据流有两个类:
- ObjectInputStream和ObjectOutputStream
- 用于存储和读取基本数据类型数据或对象的处理流。把Java中的对象写入数据源中,也能把对象从数据源中还原回来。
- 序列化:用ObjectOutputStream类保存基本数据类型或对象的机制。
- 反序列化:用ObjectInputStream类读取基本数据或对象的机制
- ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
序列化机制
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二:进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
- 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
- 序列化是RMI ( Remote Method Invoke -远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础。
- 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
- Serializable
- Externalizable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
@Test public void testObjectOutputStream() {
ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("IOTest/object.dat"));
oos.writeObject(new String("Hello World")); oos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (oos != null) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
@Test public void testObjectInputStream() { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("IOTest/object.dat")); Object obj = ois.readObject(); String str = (String)obj;
System.out.println(str); } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
要想一个java类是可序列化的,需要满足相应的要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
class Person implements Serializable{
public static final long serialVersionUID = 7090187559208313972L;
private int age; private String name;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Person(int age, String name) { this.age = age; this.name = name; } }
|
serialVersionUID的理解
public static final long serialVersionUID
- serialVersionUID来表示类不同版本间的兼容性,简言之,其目的是以序列化对象进行版本控制,有关版本反序列化时是否兼容。
- 如果类没有显式定义这个静态变量,它的值式Java运行时环境根据类的内部细节自动生成的,若类的实例变量做了修改,serialVersionUID可能发生变化,因此建议显式声明
Java API中的相关描述
如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面为该类计算默认的serialVersionUID值,如Java™对象序列化规范中所述。但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不同,因此可能在反序列化期间导致意外的InvalidClassException
。因此,为了保证不同Java编译器实现之间的serialVersionUID值一致,可序列化的类必须声明一个显式的serialVersionUID值。还强烈建议显式serialVersionUID声明在可能的情况下使用private修饰符,因为此类声明仅适用于立即声明的类-serialVersionUID字段作为继承成员不起作用。数组类无法声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,无需匹配serialVersionUID值。
随机存储文件流
- RandomAccessFile的使用
- RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。
- RandomAccessFile既可以作为输入流,也可以作为出输出流。
- 创建RandomAccessFile需要指定一个mode参数:
- r:只读方式打开
- rw:打开以便读取写入
- rwd:打开以便读取写入,同步文件内容的更新
- rws:打开以便读取写入,同步文件内容和元数据的更新
- RandomAccessFile作为输出流时,如果文件不存在,则会自动创建文件
RandomAccessFile对文件的复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public void test1() {
RandomAccessFile raf1 = null; RandomAccessFile raf2 = null; try { raf1 = new RandomAccessFile(new File("IOTest/pic1.png"), "r"); raf2 = new RandomAccessFile(new File("IOTest/pic_1.png"), "rw");
byte[] buffer = new byte[1024]; int len; while((len = raf1.read(buffer)) != -1){ raf2.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { if (raf1 != null) { try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } if (raf2 != null) { try { raf2.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
RandomAccessFile对文件内容的覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public void test2() { RandomAccessFile raf = null; try { raf = new RandomAccessFile(new File("IOTest/hello.txt"), "rw"); raf.write("xyz".getBytes()); } catch (IOException e) { e.printStackTrace(); }finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }
@Test
public void test3() { RandomAccessFile raf = null; try { raf = new RandomAccessFile(new File("IOTest/hello.txt"), "rw");
raf.seek(3); raf.write("xyz".getBytes());
} catch (IOException e) { e.printStackTrace(); }finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
使用RandomAccessFile实现数据的插入效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void test4() { RandomAccessFile raf1 = null; try { raf1 = new RandomAccessFile(new File("IOTest/hello.txt"), "rw");
raf1.seek(3); StringBuilder builder = new StringBuilder((int)new File("IOTest/hello.txt").length()); byte[] buffer = new byte[20]; int len; while ((len = raf1.read(buffer)) != -1){ builder.append(new String(buffer, 0, len)); }
raf1.seek(3); raf1.write("xyz".getBytes());
raf1.write(builder.toString().getBytes());
} catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (raf1 != null) { try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
NIO
- Java NIO (New l0, Non-Blocking 10)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO 支持而向缓冲区的(I0是而向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
- Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
- java.nio.channels.Channel
- FileChannel:处理本地文件
- SocketChannel:TCP网络编程的客户端的Channel
- ServerSocketChannel:TCP网络编程的服务器端的Channel
- DatagramChannel:UDP网络编程中发送端和接收端的Channel
Path、Paths 和 Files 核心API
早期的Java只提供了一个File类来访问文件系统,但File类的功能 比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
在以前IO操作都是这样写的:
1 2
| import java.io.File; File file = new Fil("index.html");
|
但在Java7 中,我们可以这样写:
1 2 3
| import java.ni.file.Path; import java.nio.file.Paths; Path path = Paths.get("index.html");
|
同时,NIO.2在java.nio.file包下还提供了Files、Paths 工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。
Paths类提供的静态get()方法用来获取Path对象:
- static Path get(String first, String … more):用于将多个字符串串连成路径
- static Path get(URI ur):返回指定uri对应的Path路径
Files类
- java.nio.file.Files 用于操作文件或目录的工具类
- Files常用方法
- Path copy(Path src, Path dest, CopyOption … how)
- Path creatDirectory(Path path, FileAttribute<?> … attr)
- Path creatFile(Path path, FileAttribute<?> … attr)
- void delete(Path path)
- void deleteExists(Path path)
- Path move(Path src, Path dest, CopyOption … how)
- long size(Path path)