流
流是一组有顺序,有起点和重点的字节集合,是对数据传输的总称或抽象,流的本质是数据传输。IO有两类,分为BIO(Blocking IO)和NIO(Non-Blocking IO),不过我们平常说的IO指的是BIO.
基础知识
字节:计算机存储数据的单元,一个8位的二进制数;
字符:人们使用的一个记号,例:1,2,3,S,¥等;
字符编码:是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合进行配对。
编码方式:
ASCII编码:美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定;
UTF-8编码:最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。
File类介绍
File类翻译过来是一个文件,实际上它并不是一个文件,定义为Path更为合适,这个Path可以是文件的路径也可以是文件夹的路径,因为当我们new File的时候,只是创建了一个路径,这个路径如果创建成功,没有后缀名就是文件夹,有后缀名则创建了一个空文件。
注:当我们通过new File()来创建一个文件时,并不是一定创建成功,而且失败也不会报异常,我们可以通过File的exists()方法判断是否创建成功,不成功则调用createNewFile()创建新文件,未成功会报异常:
File file = new File("D:");
if(file.exists()){
//进行相关操作
}else{
try {
boolean result = file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
- 路径:
绝对路径:带有盘符的路径 e.g windows: D:\JAVA
相对路径:不带盘符的路径 e.g .(当前目录) ..(上一级目录)
- 分隔符:
- windows: "/" "\" 都可以
-
linux/unix: "/"
注意:因为Java中"\"代表转义字符所以推荐都使用"/",但由于Windows系统函数的行为会与时俱进发生变化,对于可移植的程序来说,应该使用程序所运行平台的文件分隔符,通过File.separator来获取,表示跨平台分隔符 。
- 创建与删除
boolean createNewFile();//创建具体的文件
boolean mkdir();//创建单个目录
boolean mkdirs();//创建多个目录
boolean delete();//删除File复制代码
- 判断方法
boolean canRead();//判断文件是否可读
boolean canWrite();//判断文件是否可写
boolean exists();//判断文件是否存在
boolean isDirectory();//判断是否是目录
boolean isFile();//判断是否是文件
boolean isAbsolute();//判断是否是绝对路径复制代码
- 获取方法
String getName();//返回文件或者是目录的名称
String getPath();//返回路径
String getAbsolutePath();//返回绝对路径
String getParent();//返回父目录,如果没有父目录则返回null
long lastModified();//返回最后一次修改的时间
long length();//返回文件的长度
File[] listRoots();// 列出所有的根目录(Window中就是所有系统的盘符)
String[] list() ;//返回一个字符串数组,给定路径下的文件或目录名称字符串
String[] list(FilenameFilter filter);//返回满足过滤器要求的一个字符串数组
File[] listFiles();//返回一个文件对象数组,给定路径下文件或目录
- 文件过滤
File[] listFiles(FilenameFilter filter);//返回满足过滤器要求的一个文件对象数组
其中包含了一个重要的接口FileNameFilter,该接口是个文件过滤器,包含了一个accept(File dir,String name)方法,该方法依次对指定File的所有子目录或者文件进行迭代,按照指定条件,进行过滤,过滤出满足条件的所有文件。
示例:指定位置返回所有后缀名是txt的文件
// 文件过滤
File[] files = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String filename) {
return filename.endsWith(".txt");
}
});
示例:输出指定位置含有关键字的文件
public static void searchFile(String filePath,String filter){
File file = new File(filePath);
//该文件不存在,返回
if(!file.exists()) return;
//文件含有关键字,打印
if(file.isFile() && file.getName().contains(filter)){
System.out.println(file.getAbsoluteFile());
}
//注意文件判空
File[] list = file.listFiles();
if(list == null)return;
for (File f:list) {
if(f.isDirectory()){//是目录,递归继续遍历下一级
searchFile(f.toString(),filter);
}
if(f.isFile() && f.getName().contains(filter) && !f.isHidden()){
System.out.println(f.getAbsoluteFile()+" " + f.getName());
}
}
}
流的分类
- 根据数据流向划分,可以分为:输入流和输出流
输入流:由数据源到程序(数据源:鼠标,磁盘,网络等)
输出流:由程序到目标源(目标源:屏幕,网络,磁盘等)
InputStream和OutputStream构成了输入/输出(I/O)的层次结构基础。
- 根据处理单元划分,分为:字节流和字符流
字节流:(机器可识别)二进制文件流
字符流:人工可以识别的(因为码表的不同,有了对对象高效操作的流对象)
因为面向字节的流不便于处理以Unicode形式存储的信息(每个字符由多个字节来表示),所以从抽象类Reader和Writer中继承出来了一个专门用于处理Unicode字符的类的单独层次结构。
区别:
读写单位不同:字节流以字节(8bit)为单位,而字符流以字符为单位(根据码表,一次可能读写多个字节);
处理对象不同:字节流可以处理任何类型的数据(音频和视频),字符流只能处理字符类型的数据;
- 根据流的角色划分,分为:节点流和处理流
节点流:直接与数据源相连(低级流);
处理流:通过节点流与数据源相连(高级流),主要指FilterInputStream,FilterOutputStream和FilterReader,FilterWriter的子类;
如上图总结 ,常用的处理流:
缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter增加缓冲功能,避免频繁操作磁盘,提高效率;
转换流:InputStreamReader,OutputStreamWriter,字节流与字符流进行相互转换(需要InputStream或OutputStream作为参数);
数据流:DataInputStream,DataOutputStream将基础数据类型写入或读出;
读写操作:
写字节
String filePath = "D:"+File.separator+"hello.txt";
//打开字节文件输出流:FileOutputStream 异常:文件目录不正确就会抛出FileNotFoundException
FileOutputStream outputStream = new FileOutputStream(filePath);
//往输出流中写入数据
//每次写入一个字节
outputStream.write(99);
//每次写入一个byte数据的数据
byte[] bytes = "abcd".getBytes();
outputStream.write(bytes);
//写入指定长度的数组 byte b[]:表示读取数据源, off,在数据源中读取起始位置 len:表示从起始位置读物数据的长度
outputStream.write(bytes, 1, 1);
//刷新缓冲区操作
outputStream.flush();
//数据写完将流关闭
outputStream.close();
读字节
String filePath = "D:"+File.separator+"hello.txt";
//打开文件输入流 异常:FileNotFoundException
FileInputStream inputStream = new FileInputStream(filePath);
//读取数据 每次读取一个字节数据,返回的是字节
int read = inputStream.read();
System.out.println((char) read);
byte[] bytes = new byte[1024];
//读取数据,数据写入byte数组,返回值表示读取的有效数据个数 数据读到结束位置返回-1
int read1 = inputStream.read(bytes);
inputStream.read(bytes, 4, inputStream.available());
System.out.println(new String(bytes));
//当前流的有效数据
inputStream.available();
//数据源跳跃读取 n<inputStream.available()
inputStream.skip(3);
//标记位置
inputStream.mark(4);
//判断当前是否支持mark操作
inputStream.markSupported();
//重置操作
inputStream.reset();;
//关闭输入流
inputStream.close();
读写字符流与字节流操作基本一致,通过FileWriter和FileReader对象,调用相关read或write方法;
Object流
ObjetcInputStream和ObjectOutputStream
对象操作设计序列化和反序列化
序列化:将对象转化为字节流的过程( ObjectOutputStream)
反序列化:将字节流转化为对象的过程(ObjetcInputStream)
特点:
1、在Java中只要一个类实现了java.io.Serializable接口,就可以被序列化;
2、通过 ObjectOutputStream和 ObjectInputStream进行序列化和反序列化;(非节点流需要通过其它流连接数据源)
3、虚拟机是否允许反序列化,不仅要取决于类路径和功能代码是否一致,还要取决于两个类的序列化ID是否一致(即 private static final long serialVersionUID);
4、父类对象要序列化,则父类也要实现java.io.Serialzable接口;
5、tansient关键字用于控制变量序列化,一个变量有该关键字修饰,则可以阻止该对象被序列化到文件中,反序列化后时,该关键字修饰的变量会变成初始值;(int型是0,对象型是null);
作用:
1、网络数据传输中,敏感数据不进行传输;
2、提高序列化效率;
6、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等, 希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时, 才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全;
IO中的清流--RandomAccessFile类
RandomAccessFile是Java中输入,输出流体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。
该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。
方法
构造方法
RandomAccessFile(String name, String mode)
model各个参数详解
r 代表以只读方式打开指定文件
rw 以读写方式打开指定文件
rws 读写方式打开,并对内容或元数据都同步写入底层存储设备
rwd 读写方式打开,对文件内容的更新同步更新至底层存储设备
特有方法
getFilePointer() 返回文件记录指针的当前位置
seek(long pos) 将文件记录指针定位到pos的位置
功能
-
1.读取任意位置的数据
-
2.追加数据
-
3.任意位置插入数据
示例:从指定位置追加写内容
public static void main(String[] args) {
String path = "D:\\test.txt";
String content = "abcd";
addContentsToFile(path,2,content);
}
/**
* 当RandomAccessFile向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,
* 则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。
*/
public static void addContentsToFile(String filePath,int position,String contents){
try {
File tmp = File.createTempFile("tmp",null);
tmp.deleteOnExit();
RandomAccessFile r = new RandomAccessFile(filePath,"rw");
FileInputStream temIn = new FileInputStream(tmp);
FileOutputStream temOut = new FileOutputStream(tmp);
//将原文件指定位置后的内容写入临时文件中
r.seek(position);
int t;
while((t = r.read()) != -1){
temOut.write(t);
}
//将追加内容插入到指定位置
r.seek(position);
r.write(contents.getBytes());
//将临时文件的内容写入原文件
while((t = temIn.read()) != -1){
r.write(t);
}
} catch (IOException e) {
e.printStackTrace();
}
}
操作流需要明确的4个问题(老师总结):
问题1:操作时进行读还是写(数据源到当前程序、当前程序到数据目的)
源:读数据 inputStream \reader
目的:写数据 outputStream\ writer
问题2:明确操作的是字节还是字符
源:
字节:InputStream
字符:Reader
目的:
字节:OutputStream
字符:Writer
(明确操作那个基类的流)
问题3:数据做在的具体设备
源:
磁盘数据:File
内存数据:char、array
键盘:System.in
网络:socket
目的:
磁盘数据:File
内存数据:char、array
屏幕:System.out
网络:socket
(明确具体实现类)
问题4:是否需要额外功能
考虑效率: BufferXXX
流转换:inputStreamReader\outputStreamWriter