I/O流
目录
在Java中,把不同类型的输入、输出源(键盘、文件、网络等)抽象为流 (Stream)
文本文件(.txt、.java、.c、.cpp)使用字符流,非文本文件(.jpg、.mp3、.avi、.doc、.ppt)用字节流
体系
分类
-
输入流、输出流
-
字节流(8 bit)、字符流(16 bit)
-
节点流、过滤器(处理流)
字符流不能处理图片等字节数据
节点流(或文件流)
read(byte[] buffer); //InputStream
write(byte[] buffer,0,len);//OutputStream
read(char[] buffer); //Reader
write(char[] buffer,0,len);//Writer
缓冲流(处理流一种)
read(byte[] buffer); //BufferedInputStream
write(byte[] buffer,0,len); //BufferedOutputStream
read(char[] buffer);readLine();//BufferedReader
write(char[] buffer,0,len); //BufferedWriter
字符流
Reader
File类具有查询文件属性、状态和文件名等功能,但不能访问文件内容
相对路径
public static void main(String[] args) {
//C:\Users\HanYiting\IdeaProjects\TestFile\src\hello.txt
File file=new File("src\\hello.txt");//相对于当前工程
System.out.println(file.getAbsoluteFile());
}
public static void TestFileReader(){
File file=new File("hello.txt");//相对于当前model
}
FileReader读入数据
public static void TestFileReader() throws IOException {
File file=new File("src\\hello.txt");//实例化File类的对象
FileReader fr=new FileReader(file);//提供具体的流
int data;//read()返回值为int类型,返回读入的一个字符,没有则返回-1
while((data=fr.read()) != -1){
System.out.print((char)data);
}
if(fr != null) fr.close();
}
垃圾回收机制中,对于物理上的连接,如数据库连接、输入输出流连接,Socket连接不能自动关闭,需要手动关闭输入输出流
选中后ctrl+Alt+T快捷键可选择try/catch/finally快速做异常处理 ↓
public static void TestFileReader(){
FileReader fr= null;//提供具体的流
try {
File file=new File("src\\hello.txt");//实例化File类的对象
fr = new FileReader(file);
int data;
while((data=fr.read()) != -1){
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {}
try {
if(fr != null) fr.close();
} catch (IOException e) {
e.printStackTrace();
} finally {}
}
注意,fr.close()也要做异常处理
read的重载:read(char[] buf)的用法
char[] cbuf=new char[5];
fr.read(cbuf)//会5个5个读,没有则返回-1
/*int len;
char[] cbuf=new char[5];
while((len = fr.read(cbuf)) != -1){//
System.out.print(cbuf);
}helloworld123→helloworld123ld,因为最后只能读3个,没有新的字符可以覆盖后面两个字符*/
所以用的时候用String,然后每次输出注意截取String str=new String(cbuf,0,len)
Writer
public static void TestFileWriter() throws IOException {
File file=new File("hello1.txt");//没有则会默认创建一个
FileWriter fw=new FileWriter(file);//构造器还有参数FileWriter(file,true),true就不覆盖,在原有文件后追加
fw.write("Hei Boy");
fw.close();
}
write同样也有多个重载方法,详情去看定义
字节流
处理文本文件
public static void TestFileInputStream(){
File file=new File("src\\hello.txt");
FileInputStream fis=new FileInputStream(file);
byte[] buffer=new byte[5];
int len;
while((len=fis.read(buffer))!=-1){
String str=new String(buffer,0,len);
System.out.print(str);
}
fis.close();
}//再做一下异常处理
处理非文本文件
public static void FileStream(){
FileInputStream fis= null;
FileOutputStream fos= null;
try {
File srcFile=new File("src\\picture.png");
File destFile=new File("src\\1.png");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
byte[] buffer=new byte[10];
int len;
while ((len=fis.read(buffer))!=-1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
} finally {}
}
}
}
缓冲流
缓冲是为了提高读写效率
造流先造内层再造外层,关的时候先关外层再关内层。关闭外层时,内层流会自动关闭,所以只写外层的close即可
public static void BufferedTest() throws IOException {//没改try-catch
File srcfile=new File("src\\hello.txt");
File destFile=new File("src\\hei.txt");
FileInputStream fis=new FileInputStream(srcfile);
FileOutputStream fos=new FileOutputStream(destFile);
BufferedInputStream bis=new BufferedInputStream(fis);
BufferedOutputStream bos=new BufferedOutputStream(fos);
byte[] buffer=new byte[10];
int len;
while ((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
bos.close();
bis.close();
}
通过时间分别测试缓冲流和普通的字节流复制视频时间,缓冲流明显快
long start=System.currentTimeMillis();
...
long end=System.currentTimeMillis();
System.out.println(end-start);//缓冲流157,字节流9391
Buffer内部提供了一个缓冲区也就是说缓冲区大小8192(=1024×8)
bos.flush();//刷新缓冲区,缓冲区满时会自动清空
可以通过匿名的方式简化
BufferedReader br=new BufferedReader(new FileReader(new File("src\\hello.txt")));
其他方法
String data;
data=br.eadLine();//BufferedReader,读取一行但不读取换行符,记得自己加\n,没有值时返回null
//bw.newLine();BufferedWriter的方法,换行用
对象流
ObjectInputStream和ObjectOutputStream:用于存储和读取基本数据类型数据或对象的处理流。可以把java中的对象写入到数据源中,也能把对象从数据源中还原出来
-
序列化:用ObjectOutputStream类保存基本数据类型数据或对象
-
反序列化:用ObjectInputStream类读取基本类型数据或对象
-
不能序列化static和transient修饰的成员变量
序列化与反序列化
对象的序列化机制:允许把内存中的java对象转换成语平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网路将这种二进制流传输到另一个网络节点,当其他程序获取了这种二进制流,就可以恢复成原来的java对象
-
实现了Serializable接口的对象转化为字节数据
-
序列化是RMI(远程方法调用)过程的参数和返回值都必须实现的机制。RMI是JavaEE的基础,因此序列化是JavaEE平台的基础
-
如果要让某个对象支持序列化机制,则需要让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现Serializable、Externalizable两接口之一,否则会抛出异常
对String类进行序列化和反序列操作
import java.io.*;
public class Test1 {
public static void main(String[] args) {
//testObjectOutputStream();
testObjectInputStream();
}
public static void testObjectOutputStream(){
ObjectOutputStream oos= null;
try {
oos = new ObjectOutputStream(new FileOutputStream("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();
} finally {
}
}
}
}
public static void testObjectInputStream(){
ObjectInputStream ois=null;
try {
ois=new ObjectInputStream(new FileInputStream("object.dat"));
Object obj=ois.readObject();
String str=(String) obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
}
}
要想一个java对象可序列化的,需要满足相应的要求
public class Person implements Serializable {
public static final long serialVersionUID=12344L;//从声明里看到必须提供一个这样的UID
private String name;
private int age;
...
}
-
需要当前类提供一个全局常量serialVersionUID
-
serialVersionUID用来表名类的不同版本的兼容性,目的是以序列化对象进行版本控制,有关各版本序列化时是否兼容。
-
如果不显示的定义这个静态变量,它的值是由java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID也可能变化。因此最好显示声明
-
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性,在反序列化时,JVM会把传来的字节流的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同则认为一致,可以进行反序列化,否则会出现序列化版本不一样的异常(InvalidCastException)
-
修改上面代码的部分
oos.writeObject(new Person("Tom",23));//序列化写入
oos.flush();
Person p=(Person) ois.readObject();//反序列化读取
System.out.println(p);//Person{name='Tom', age=23}
注意,ObjectInputStream和ObjectOutputStream不能序列化static和transient修饰的成员
随机文件存取流
RandomAccessFile类
-
直接继承与java.long.Object类,并且实现了DataInput、DataOutput这两个接口,也就是说这个类可以读也可以写
public static void test1() throws IOException {
RandomAccessFile raf1=new RandomAccessFile(new File("123.png"),"r");
RandomAccessFile raf2=new RandomAccessFile(new File("1234.png"),"rw");
byte[] buffer=new byte[1024];
int len;
while ((len = raf1.read(buffer)) != -1){
raf2.write(buffer,0,len);
}
raf1.close();
raf2.close();
}
作为输出流时,文件不存在则会自动创建。如果存在,会对原有文件内容进行覆盖(默认从头覆盖)
public static void test2() throws IOException {
RandomAccessFile raf1=new RandomAccessFile(new File("hello.txt"),"rw");//abcdefghijk
raf1.write("zxyuv".getBytes());//zxyuvfghijk,前面被覆盖了
raf1.close();
}
seek可以改变指针位置
raf1.seek(3);//将指针调到角标为3的位置,从3开始覆盖
raf1.write("xyz".getBytes());//abcxyzghijk
想不覆盖就先把后面内容读出来,最后再写回去
public static void test2() throws IOException {
RandomAccessFile raf1=new RandomAccessFile(new File("hello.txt"),"rw");//abcdefghijk
raf1.seek(3);//将指针掉到3,保存指针后的数据
StringBuilder builder=new StringBuilder((int) new File("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());
raf1.close();
}
NIO
IO是面向流的,NIO是面向缓冲区的。NIO更加高效
Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
早期java只提供了File类,但是File类功能有限,出错时大部分仅返回失败不提供异常信息。NIO.2引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版,实际引用的资源也可以不存在
Path path = Paths.get("index.html");