本文是学习Java时所记录的学习笔记,本节包含了IO流的概念、具体的写法、以及NIO、NIO2的IO流相关知识。参考了《疯狂Java讲义》和网上视频教程。欢迎留言、私信交流~~
文章目录
IO流简介
什么是IO流?
Java的IO流是实现输入/输出的基础。掌握IO流就是掌握数据读取、存储数据。Java的IO部分,需要理解各个类型流之间的关系。
IO流分类概述
- java.io包下主要包括输入、输出流两种IO流。
- 每种输入、输出流又可分为字节流和字符流两大类。
- 字节流以字节为单位来处理输入、输出操作。
- 字符流以字符来处理输入、输出操作。
- Java的IO流使用了装饰器设计模式,将IO流分成底层节点流和上层节点流。
- 节点流用于和底层的物理存储节点直接关联。
- 包装流用于把不同物理节点流包装成统一的处理流。
IO流分类表
- java.io.*;
分类 类名 说明 文件处理 File类 用于操作文件和目录的类,可以新建、删除、重命名文件和目录。但File不能访问文件内容本身,如需要访问内容,则需要使用输入流/输出流。 输入流/抽象基类 InputStream类 属于字节输入流类,本身不能创建实例,但他是输入流的模板,它的子类通常会使用它的输入流方法(read方法)。 输入流/抽象基类 Reader类 属于字符流类,本身不能创建实例,但他是输入流的模板,它的子类通常会使用它的输入流方法(read方法)。 输入流/节点流 FileInputStream类 字节流,该类会直接和指定文件关联,可以进行数据读取。 输入流/节点流 FileReader类 字符流,该类会直接和文件关联,可以进行数据读取。 输出流/抽象基类 OutputStream类 属于字节输出流类,本身不能创建实例,但他是输出流的模板,它的子类通常会使用它的输出流方法(write方法)。 输出流/抽象基类 Writer类 属于字符输出流类,本身不能创建实例,但他是输出流的模板,它的子类通常会使用它的输出流方法(write方法)。 输出流/节点流 FileOutputStream类 字节流,该类会直接和文件关联,可以进行数据写入(输出)。 输出流/节点流 FileWrite类 字节流,该类会直接和文件关联,可以进行数据写入(输出)。 处理流/打印流 PrintStream类 字节输出流。 处理流/打印流 PrintWriter类 字符输出流。 处理流 ObjectInputStream类 字节输入流,主要负责对象输入。 处理流 ObjectOutputStream类 字节输出流,主要负责对象输出。 转换流 InputStreamReader类 负责将字节输入流转换成字符输入流。 转换流 OutputStreamWriter类 负责将字节输出流转换成字符输出流。 包装流 BufferedReader类 该类具有缓冲功能,可以一次读取一行文本(以换行符为标志),如果没有读到换行符,则程序堵塞,等到读到换行符为止。 推回输入流 PushbackInputStream类 字节输入流,unread方法将字节数组内容推回到缓冲区里,允许重复读取刚刚读取的内容。 推回输入流 PushbackReader类 字符输入流,unread方法将字符数组内容推回到缓冲区里,允许重复读取刚刚读取的内容。 输入流/输出流 RandomAccessFile类 该类是输入/输出流体系中功能最丰富的文件内容访问类,与其他输入/输出流不同的是,它可以直接跳转到文件任意的地方读写数据。如果只需要访问文件部分内容,推荐使用该类。(该类包含了一个记录指针,用于标识当前度写出的位置)
IO流的具体实现
File类
File tmp = File.createTempFile("tmp",null); //创建一个临时文件(可以设置该临时文件将在JVM退出时被删除)
tmp.deleteOnExit(); //指定该临时文件在JVM退出时被删除
基本输出流实现流程
FileWriter写数据
FileWriter fw = new FileWriter("e:\\test\\a.txt"); //新建输入流
FileWriter fw = new FileWriter("e:\\test\\a.txt",true); //true表示追加内容的模式,在a.txt后面追加内容。
基本输入流实现流程
FileReader读数据
FileReader fr = new FileReader("e:\\fr.txt");
基本输入流方法 | 说明 |
---|---|
int read() | 一次读取一个字符,返回int值 |
int read(char[] cbuf) | 读取内容到cbuf中,返回的是实际读取的字符个数 |
ObjectInputStream/ObjectOutputStream类使用方法
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(a.txt)); //创建一个ObjectOutputStream输出流
oos.writeObject(stu1); //将stu1对象写入输出流。
ObjectInputStream ois = new ObjectInputStream(FileInputStream(a.txt)); //创建一个ObjectInputStream输入流
Person p = (Person)ois.readObject(); //从输入流中读取一个Java对象,并将其类型强制转换为Person类
RandomAccessFile类使用方法
RandomAccessFile raf = newRandomAccessFile("a.txt","r"); //以只读的方式打开相对路径中的a.txt文件。
raf.seek(300); //设置raf的文件记录指针位置为第300字节处,后面讲从这个位置进行读写。
raf.write("追加的内容!\r\n".getBytes()); //追加内容。注意:需要将文件指针后面的内容存入一个临时文件中,插入新内容后,再把临时文件的内容追加到后面。
基本输入输出案例1
//复制a.txt的内容到b.txt
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class shurushuchu {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String str1;
while ((str1 = br.readLine())!=null)
{
bw.write(str1);
bw.newLine();
bw.flush();
}
bw.close();
br.close();
}
}
基本输入输出案例2
//复制图片
import java.io.*;
class filedemo
{
public static void main(String[] args) throws IOException
{
FileInputStream i1 = new FileInputStream("filedemo\\atat.jpg");
FileOutputStream o1 = new FileOutputStream("filedemo\\btbt.jpg");
int len;
byte[] str = new byte[1024];
while ((len=i1.read(str))!=-1)
{
o1.write(str);
}
i1.close();
o1.close();
}
}
基本输入输出案例3
//列出*.java
import java.io.*;
class filedemo1
{
public static void main(String[] args) throws IOException
{
File f1 = new File("filedemo");
File[] str = f1.listFiles();
for (File a : str )
{
if (a.isFile())
{
if (a.getName().endsWith(".java"))
{
System.out.println(a.getName());
}
}
}
}
}
标准输入输出流案例1
//用字节输入流输入,通过转换流输出字符。
import java.io.*;
class outdemo
{
public static void main(String[] args) throws IOException
{
BufferedReader br = new BufferedReader(new FileReader("test.java"));
Writer w = new OutputStreamWriter(System.out);
String line;
while ((line = br.readLine())!=null)
{
w.write(line);
w.write("\r\n");
}
w.close();
br.close();
}
}
对象操作流案例1
class shurudemo1
{
public static void main(String[] args) throws IOException,ClassNotFoundException
{
FileOutputStream fos = new FileOutputStream("filedemo\\a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ArrayList<students> list = new ArrayList<students>();
list.add(new students("h1",18));
list.add(new students("h2",19));
list.add(new students("h3",20));
list.add(new students("h4",21));
oos.writeObject(list);
fos.close();
oos.close();
FileInputStream fis = new FileInputStream("filedemo\\a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Object a1 = ois.readObject();
ArrayList<students> list1 = (ArrayList<students>)a1;
for (students b:list1)
{
System.out.println(b);
}
fis.close();
ois.close();
}
}
IO流相关知识
对象序列化
什么是对象序列化?
- 序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输。序列化机制使得对象可以脱离程序的运行而独立存在。
- Serializable接口:对象的序列化,指将Java对象写入IO流中。
- Deserialize接口:对象的反序列化,指从IO流中恢复Java对象。
- Externalizable接口:对象的序列化,该方式由程序员决定存储和恢复对象数据。
Serializable接口
下面程序定义了一个Person类,这个Person类就是一个普通的Java类,只是实现了Serializable接口,该接口标识该类的对象是可序列化的。
public class Person implements java.io.Serializable{
private String name;
private int age;
public Person(String name,int age){
System.out.println("有参数的构造器");
this.name = name;
this.age = age;
}
//省略get、set方法等
}
Externalizable接口
- 该接口有两个需要实现的方法:void readExternal(ObjectInput in)和void writeExternal(ObjectOutput out)
- 当使用Externalizable时,程序会先使用public的无参数构造器创建实例,然后才执行readEternal方法进行反序列化。因此必须提供public的无参数构造器。
对象序列化相关知识
- 如果想要类是可序列化的,该类必须试下两个接口之一:Serializable/Externalizable
- Serializable:标记接口,实现该接口无需实现任何方法,它只表明该类的实例是可序列化的。
- 使用反序列化恢复Java时,必须提供该Java对象所属类的class文件,否则会引发ClassNotFoundException异常。
- 当一个可序列化类有多个父类时,父类要么有无参数的构造器,要么也是可序列化的。否则反序列化时将抛出InvalidClassException异常。
- 如果类的成员变量类型不是基本类或String类,而是另一个引用类型。那么该引用类型必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。
- 如果多次序列化同一个对象,系统不会重复生成,只会在第二次及以后输出一个序列化编号。
- 使用序列化机制向文件写入多个Java对象,使用反序列化机制恢复对象时必须按实际写入顺序读取。
- 只有第一次调用writeObject()方法来输出对象时才会将对象转换成字节序列,并写入到ObjectStream。后面进程中即使该对象的实例变量发生改变,重新写入也不会产生新的序列化编号。
- transient关键字修饰:用于修饰实例变量,可以指定Java序列化时无需理会该实例变量。
- 为了安全性,可以将实例变量包装成StringBuffer,并将其字符序列反转后写入。在读取时也要进行相应动作。
序列化版本控制
- 通过以下方法保证两个class文件的兼容性(防止项目升级后class文件不兼容)
- Java序列化机制允许为序列化类提供一个private static final的serialVersionUID值。
public class Test{ //为该类指定一个serialVersionUID类变量值 private static final long serialVersionUID = 512L; }
- 可以通过JDK安装路径下的bin目录下的serialver.exe工具获得类的serialVersionUID类变量的值:
serialver Person。(加上-show可以显示图形化界面) //输出: //Person: static final long serialVersionUID = -2595800114629327570L;
NIO
- JDK1.4开始,Java提供了一系列盖紧的输入/输出处理的新功能,这些功能被统称为新IO(New IO,简称NIO)。
- 两个核心功能:Channel(通道)/Buffer(缓冲)
- Channel与传统的InputStream和OutputStream最大区别在于它提供了一个map()方法。通过map()方法,可以直接将“一块数据”映射到内存中。可以说NIS是面向块的处理。
- Buffer可以理解成一个容器,本质是一个数组。发送到Channel的对象必须先放到Buffer中。
- 新IO还提供了:Charset类(Unicode字符映射成字节序列以及逆映射)、Selector类(支持非阻塞式输入/输出)
Buffer类
- 抽象类Buffer类(三个重要概念:容量(capacity)、界限(limit)和位置(position))
- 常用子类ByteBuffer类:可以在底层字节数组上进行get/set操作。
- 常用子类CharBuffer类:可以在底层char数组上进行get/set操作。
- 不常用子类XxxBuffer类:和ByteBuffer类似,对应于不同的基本数据。
- Buffer类有三个重要概念:容量(capacity)、界限(limit)和位置(position)。
- 容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量。
- 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。limit后面的数据不可读也不能写。
- 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引。(类似于IO流中的指针)
- 例子
CharBuffer buff = new CharBuffer.llocate(8); //创建容量为8的CharBuffer对象 buff.capacity(); //返回buff的容量大小。 buff.limit(); //返回buff的界限的位置。 buff.position(); //返回buff中的position值。 buff.put('a'); //放入元素'a'。 buff.flip(); //进入输出数据状态(可以理解为输入数据完成)。该方法会将limit设置为position所在位置,并将position设为0。 buff.get(); //取出第一个元素。 buff.clear(); //再次进入输入状态(可以裂解为读取数据完成)。该方法会将position设置为0,limit设置为capacity。
Channel类
- 抽象类Channel类:类似于传统的流对象,但是Channel可以将指定文件的部分或者全部直接映射成Buffer。必须通过Buffer进行读写,该类只和Buffer进行交互。
- 常用类FileChannel类:文件操作的Channel。
- 不常用类XxxChannel类:和FileChannel类似,Xxx按功能来命名,例如SocketChannel、ServerSocketChannel和DatagramChannel等等。
- 常用方法
方法名 说明 造器 Channel是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel。 appedByteBuffer map(FileChannel.MapMode mode,long position,long size) 第一个参数是映射时的模式(只读、读写等),第二个、第三个参数用于控制将Channel的哪些数据映射成ByteBuffer。 ock() 试图锁定文件,如果无法得到文件锁,程序将一直阻塞。(带参数的可以设定锁定的内容范围,以及是否共享锁) ryLock() 试图锁定文件,如果不堵塞则获得文件锁,否则返回null。(带参数的可以设定锁定的内容范围,以及是否共享锁) - 例子
File f = new File("a.txt"); FileChannel inChannel = new FileInputStream(f).getChannel(); //创建FileInputStream,以该文件输入流创建FileChannel。 FileChannel outChannel = new FileOutputStream("b.txt").getChannel(); //创建FileOutputStream,以该文件输出流创建FileChannel。 MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length()); //将FileChannel里的全部数据映射成ByteBuffer。 Charset charset = Charset.forName("GBK"); //使用GBK的字符集来创建解码器。 outChannel.write(buffer); //将buffer的数据输出。 buffer.clear(); //恢复buffer的limit、positon位置。 CharsetDecoder decoder = charset.NewDecoder(); //创建解码器对象。 CharBuffer charBuffer = decoder.decode(buffer); //将ByteBuff转换成CharBuffer
字符集和Charset
- Charset类
方法名 说明 vailableCharsets() 该方法可以获取当前JDK所支持的所有字符集。 ystem类的getProperties() 该方法可以访问本地系统的文件编码格式。 harset cs = Charset.forName(“GBK”); 创建Charset对象。 ewDecoder() 返回CharsetDecoder对象,进行解码,把字节序列转换成字符序列。 ewEncoder() 返回CharsetEncoder对象,进行加码,把其他序列转换成字节序列。 用decode()、encode() 可以直接进行转换。 - 常用字符串别名
别名 说明 BK 简体中文字符集。 IG5 繁体中文字符集。 SO-8859-1 ISO拉丁字母表No.1,也叫做ISO-LATIN-1。 TF-8 8位UCS转换格式。 TF-16BE 16位UCS转换格式,Big-endian(最低地址存放高位字节)字节顺序。 TF-16LE 16位UCS转换格式,Little-endian(最高地址存放低位字节)字节顺序。 TF-16 16位UCS转换格式,字节顺序由可选的字节顺序表示来标识。
文件锁
- FileLock类-用于锁定文件,控制其他进程修改文件。
用方法 说明 sShared() 判断获得的锁是否为共享锁。 elease() 释放文件锁。
Java7的NIO.2
- 新增Files(操作文件相关)、Paths(平台路径相关)两个工具类。
- 新增Files类下的方法可以方便的遍历指定目录下的所有文件和子目录。
walkFileTree(Path start,FileVisitor<? super Path> visitor) //遍历start下的所有文件和目录。FileVisitResult参数是一个枚举,代表访问之后的后续行为。
- 新增Paths类提供了一个方法监听文件系统的变化。
regiter(WatchService watcher,WatchEvent.Kind<?>... events) //用watcher监听该path代表的目录下的变化。events参数指定要监听哪些类型的事件。
- javanio.file.attribute包下提供了大量的工具类。通过这些工具类,可以非常简单地读取、修改文件属性。
- 例子
Pths.get("C:/").regiter(watchService,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY); //为C:盘根路径注册监听
- WatchService类代表一个文件系统监听服务。
WatchService watchService = FileSystems.getDefault().newWatchService(); //获取文件系统的WatchService对象 WatchKey poll() //获取下一个WatchKey,如果没有WatchKey发生就立即返回null。 WatchKey take() //获取下一个WatchKey,如果没有WatchKey发生就一直等待。 reset() //重设WatchKey
- WatchKey类:事件相关的类。
方法名称 说明 ontext() 返回产生事件的文件名称。 ind() 返回具体发生的事件名称。
- WatchService类代表一个文件系统监听服务。
其他
参考资料
- 《疯狂Java讲义(第4版)》 李刚