6.IO流
6.1 File
系统中我们一眼看到的就是文件或者文件夹
本质是一个路径(字符串);用字符串来表示这个路径不符合面向对象
File类对路径的字符串进行面向对象的封装,是文件和文件夹路径的抽象表现形式
基本操作:
-
构造方法
File(String pathname) 将给定的路径名字符串转为抽象路径名来创建一个File实例 例子: new File("D:java/testDoc/test.java") //文件路径 new File("D:java/testDoc") //文件夹路径 File(String parent, String child) 根据父路径字符串和子路径字符串创建一个File实 例子:new File("D:java","/testDoc/test.java") //文件 例子:new File("D:java","/testDoc") //文件夹
-
创建
-
创建文件夹
mkdir():创建文件夹 mkdirs():?创建多级文件夹
-
创建文件
createNewFile():创建文件
-
重命名
renameTo():给文件或文件夹重命名
-
-
判断
exists():判断此路径是否存在 isFile():判断是否文件 isDirectory():判断是否文件夹
-
获取
getAbsolutePath():获取绝对路径,包括根路径 getName():获取相对路径(文件名或者文件夹名).不包括根路径 length():获取文件的大小,单位是字节
-
删除
delete():删除文件 deleteOnExit():在程序退出的时候删除文件
-
示例代码:
//创建File实例对象,路径的抽象表现 File file = new File("D:java/testDoc/test.java"); // File file = new File("D:java/testDoc"); // File file = new File("D:java","testDoc/test.java"); //判断路径是否存在 if (!file.exists()){ //判断是否是文件路径 if (file.isFile()){ //创建文件 file.createNewFile(); } //判断是否是文件夹路径 if (file.isDirectory()){ //创建文件夹 file.mkdir(); } } //获取绝对路径 System.out.println(file.getAbsoluteFile()); // 输出 /D:java/testDoc/test.java //获取相对路径(文件名或者文件夹名) System.out.println(file.getName()); // 输出test.java
File类的高级获取
- 方法:
listFiles():返回一个File数组,包括了路径名下的的所有文件和文件夹路径名
listRoots():此方法是静态的,返回路径名下的所有根路径
listFiles(FileFilter filter):返回File数组,数组是有经过过滤器FileFilter筛选的
FileFilter 重写的方法: accept(File pathname) 返回true表示需包含在数组内,不要去掉
-
示例代码1:
if (file != null && file.isDirectory()) { //打印文件夹 System.out.println(file); //列出所有的文件和文件夹路径 File[] files = file.listFiles(); for (File f : files) { //递归调用,找出所有的文件 文件夹 testFileSearch(f); } } else { //打印文件 System.out.println(file); }
-
示例代码2
if (file != null && file.isDirectory()) { File[] files = file.listFiles(new FileFilter() { @Override public boolean accept(File file) { //.class结尾的需要保留,不要去掉 return file.getName().endsWith(".class"); } }); }
6.2 IO流基类
字节输入流 InputStream
字节输出流 OutputStream
字符输入流 Reader
字符输出流 Writer
特点:
-
四大基类都是抽象类,核心功能一句话总结:读进来,写出去
输出流:将数据写到目标中去
输入流:从源中读取数据
所有的流都是资源对象,最后使用完都必须关闭资源
字节流比较通用
-
IO流的操作模板
1):定义源或目标 源:输入流 目标:输出流 2):根据源和目标创建流对象 3):进行读/写操作 4):关闭资源
6.2.1 字节流
字节输入流 InputStream
字节输出流 OutputStream
字节文件流
1-1.FileOutputStream
将数据写到目标文件中
-
构造方法
FileOutputStream(File file) 创建向指定File对象的目标文件中写入数据的文件输出流 FileOutputStream(String name) 创建向指定名称的目标文件中写入数据的文件输出流 FileOutputStream(String name, boolean append) append 是否在源数据中追加
-
常用方法
write(int b) //将指定数量的字节写到输入流 write(byte[] b,int off, int len ) 将指定的byte数组从off索引开始的len个字节写到输出流
-
分析流对象的创建
1.创建了输出流对象 2.通知系统去检查目标文件,如果不存在则创建出来 3.将这个输出流对象与硬盘上的文件关联起来
-
示例代码:
String dest = "d:/java/test.txt"; //输出的目标文件路径 try { //创建流对象,如果文件不存在,则会自动创建文件 FileOutputStream fo = new FileOutputStream(dest); byte[] bytes = "hello".getBytes();//需要写到流的数据 fo.write(bytes,2,3); fo.close();//关闭流,释放资源,等待GC回收 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
1-2.FileInputStream
从源文件读取数据
-
构造方法
FileInputStream(String name) FileInputStream(File file) 创建从源文件中读取数据的输入流对象
-
常用方法
read() 从输入流中读取数据的下一个字节, 返回值是读取到的字节,如果为-1表示已读完 read(byte[] b) 从输入流中读取数据的一定数量字节,并存储到缓冲数组b中. 返回值是读取的字节个数,如果为-1表示已读完
-
示例代码:
//源 String path = "d:/java/in.txt"; //根据源创建输入流对象 try { FileInputStream fi = new FileInputStream(path); //定义一个缓冲数组 byte[] bytes = new byte[1024 * 1024]; fi.read(bytes); int len;//记录读取了多少个字节 while ((len = fi.read(bytes)) != -1) { //不等于-1 表示还有数据 System.out.println(new String(bytes, 0, len)); } fi.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
缓冲流
-
特点
缓冲字节流和普通字节流在使用上没有区别 ,在效率上缓冲流效率更高
-
为什么会比较效率?
缓冲流对象底层自带一个8k的缓冲区,这个缓冲区跟我们输入流的自定义的缓冲数组没有关系
缓冲流对象的缓冲区指的是管道的粗细,而自定义的缓冲数组指的的是读取到的数据储存的地方
缓冲流将管道加粗了,从而使输入和输出更加效率
字节缓冲流
2-1.BufferedInputStream
-
构造方法
BufferedInputStream(InputStream in)
-
示例代码
FileInputStream in = new FileInputStream("d:/java/test.java"); BufferedInputStream bufin = new BufferedInputStream(in);
2-2.BufferedOutputStream
-
构造方法
BufferedOutputStream(OutputStream out)
-
示例代码
FileOutputStream out = new FileOutputStream("d:/java/test.java"); BufferedOutputStream bufOut= new BufferedOutputStream(out);
对象流
-
作用
把内存中的对象输出到文件中或者把文件中保存的对象输入到内存
-
为什么保存到文件中?
目的是为了进程之间的数据做交流
-
序列化和反序列化
序列化:内存中的对象写出到文件
反序列化:将文件中的对象读进内存
-
序列化对象
保存的对象需要经过序列化,开启序列化可以通过实现Serializable接口
实现Serializable接口需要在对象中显示声明序列化ID;不显式声明时,会根据类上的信息产生不同的序列化ID,由于编译器的不同,可能导致反序列化出现意外
3-1.ObjectOutputStream
ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
序列化:把内存中的对象保存到硬盘中
-
构造方法
ObjectOutputStream(OutputStream out) 创建写入指定OutputStream的对象
-
常用方法
void writeObject(Object obj) 将对象写到流中
-
示例代码
public class ShareData implements Serializable { private static final long serialVersionUID = -1190188205817401171L; } ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); ShareData data = new ShareData(); out.writeObject(data); out.close();
3-2.ObjectInputStream
ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
反序列化:将文件中的对象读取到内存中
一个程序去读取另一个程序序列化的数据,达到进程通信
-
构造方法
ObjectInputStream(InputStream in)
-
常用方法
readObject()
-
示例代码
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); Object object = in.readObject(); in.close();
字节数组流
4-1.ByteArrayOutputStream
ByteArrayOutputStream extends OutputStream
-
特点
会将数据写入到底层缓冲区的byte数组,缓冲区会随着数据的不断写入而自动增长
-
使用场景:
在需要接收大量是数据时,有些数据不适合一次性全部接收,我们可以使用每次收集1小部分的形式,拿到零散的数据,再将零散的数据统一收集起来,最后统一使用,此时就要用到这个ByteArrayOutputStream
-
构造方法
ByteArrayOutputStream()
-
常用方法
byte[] toByteArray() //将缓冲数组的数据放到一个新的数组 String toString() 用默认字符集将缓冲区数据解码成字符串 String toString(String charsetName) 用指定字符集将缓冲区数据解码成字符串 void write(byte[] b, int off, int len) 将数组b写到流中
-
示例代码
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); //模拟每次有一部分数据过来 //这个方法就是将零散的数据先暂时保存起来 byteOut.write(new byte[]{'a', 'b', 'c'}); byteOut.write(new byte[]{'d', 'e', 'e'}); byteOut.write(new byte[]{100, 127, 32}); byte[] bytes = byteOut.toByteArray();//将保存的零散数据使用起来 System.out.println(new String(bytes)); System.out.println(byteOut.toString());//解码流的数据为字符串
4-2.ByteArrayIntputStream
ByteArrayInputStream extends InputStream
-
特点
包含一个内部缓冲区,该缓冲区包含从流中读取的字节
关闭 ByteArrayIntputStream无效,其方法在关闭后仍可被调用,而不会产生IOException
-
构造方法
ByteArrayInputStream(byte[] buf) 创建一个流对象,使用buf作为其缓冲区数组
-
示例代码
byte[] bs = new byte[]{9,98,100};//把字节数组转成流对象 ByteArrayInputStream byteIn = new ByteArrayInputStream(bs); byte[] buf = new byte[1024]; int len; while ((len = byteIn.read(buf)) != -1){ System.out.println(new String(buf,0,len)); }
6.2.2 字符流
字符输入流 Reader
字符输出流 Writer
-
为什么使用字符流
使用字节流读取文件可能会出现乱码的情况,因为字节流是一次只读取一个字节,而有些数据不是一个字节构成,如汉字占两个字节或3个字节,在解析的时候不知道用几个字节去解析,因此容易出现乱码.
-
字符流工作过程
字符流底层有一个1kb大小的缓冲区,读写都是跟缓冲区有关的
字符输入流的缓冲区的作用:用于读取数据后解析数据,如果解析成功就会存入缓冲数组中;如果解析失败,则会再读一个数据一起解析;如果要解析的字节在编码表中找不到对应的就会转成问号!
字符输出流的缓冲区作用: 先写入数据到缓冲区,然后调用flush()方法将数据从缓冲数组中写到文件中,或者缓冲数组满了也会写出去
1-1. FileReader
FileReader extends InputStreamReader
擅长从文件读取字符的类
-
构造方法
FileReader(String fileName)
-
常用方法
int read() 每次读取一个字符 int read(char[] cbuf) 将字符读入缓冲数组中
-
示例代码
String path = "d:/java/test.txt"; try { FileReader fr = new FileReader(path); char[] chrs = new char[3];//读取多少个字符到缓冲数组 int len;//记录读取了多少个字符 while ((len = fr.read(chrs)) != -1) { String s = new String(chrs, 0, len); System.out.println(s); } fr.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
1-2.FileWriter
FileWriter extends OutputStreamWriter
擅长写出字符到文件的类
-
构造方法
FileWriter(String fileName)
-
常用方法
void write(String str) 将字符串写出到文件 flush() 将缓冲的数据写出到文件 ,在关闭流之前调用
-
注意事项
字符流底层存在一个缓冲数组,调用write方法时,会将数据临时保存在缓冲数组中,当缓冲数组存满后,会自动把数组中的数据真正写到文件中.我们可以手动调用flush()方法,刷新缓存区,让数据写到文件
-
示例代码
String dest = "d:/java/write.txt"; try { FileWriter fw = new FileWriter(dest); String needData = "hossss"; fw.write(needData); //刷新数据 fw.flush(); fw.close(); } catch (IOException e) { e.printStackTrace(); }
转换字符流
特点:
- 将字节流转换成字符流
为什么要转换?
字节流是基本流,一般流对象都设计成字节流,比较通用
有时流对象并不是我们自己创建的,而是别的地方返回的,而返回的一般是字节流,如果字节流用来处理文本数据可能会出现乱码,所以必须将字节流转成字符流
2-1.InputStreamReader
转换输入流
-
构造方法
InputStreamReader(InputStream in) InputStreamReader(InputStream in, String charsetName) 指定字符集读取字节并解码为字符
-
示例代码
FileInputStream in = new FileInputStream("d:/java/test.java"); InputStreamReader reader = new InputStreamReader(in, "UTF-8"); char[] chars = new char[1024]; int len; while ((len = reader.read(chars)) != -1){ System.out.println(new String(chars,0,len)); } reader.close();
2-2.OutputStreamWriter
转换输出流
-
构造方法
OutputStreamWriter(OutputStream out) OutputStreamWriter(OutputStream out, String charsetName) 指定字符集将字符编码成字节写到流中
-
示例代码
FileOutputStream out = new FileOutputStream("d:/java/test.java"); OutputStreamWriter writer = new OutputStreamWriter(out,"UTF-8"); writer.write("你好啊"); writer.close();//转换流的close包含了刷新数据,关闭资源操作
字符缓冲流
3-1.BufferedReader
-
构造方法
BufferedReader(Reader in)
-
特有方法
String readLine() 读取一行文本
-
示例代码
BufferedReader reader =new BufferedReader(new FileReader("d:/java/test2.java")); String data; //记录读取到的内容 while ((data = reader.readLine()) != null){ System.out.println(data); } reader.close();
3-2.BufferedWriter
-
构造方法
BufferedWriter(Writer out)
-
特有方法
void newLine() 写入一个行分隔符(换行)
-
示例代码
BufferedWriter writer = new BufferedWriter(new FileWriter("d:/java/test.java")); writer.write("哈哈"); writer.newLine();//换行 writer.write("我是哈哈"); writer.flush(); writer.close();
6.3 文件拷贝
-
字节流拷贝
字节流可以读写文本文件和二进制文件
//需求:将test1.java的内容拷贝到test2.java FileInputStream in = new FileInputStream("d:/java/test1.java"); FileOutputStream out = new FileOutputStream("d:/java/test2.java"); byte[] bytes = new byte[1024]; int len; while ((len = in.read(bytes)) != -1) { //将读取到的数据写出去,边读边写 out.write(bytes, 0, len); } in.close(); out.close();
-
字符流拷贝
字符流只能拷贝纯文本文件,拷贝二进制文件肯定是打不开的
拷贝二进制文件的问题:
读取回来的字节数据在转换成字符的过程中,发现找不到对应的字符和它对应,而转成了?(63)只有一个字节,所以写出去的就丢失了字节,最终导致拷贝的数据变少了
//需求:将test1.java的内容拷贝到test2.java FileReader reader = new FileReader("d:/java/test1.java"); FileWriter writer = new FileWriter("d:/java/test2.java"); char[] chars = new char[1024]; int len; while ((len = reader.read(chars)) != -1) { writer.write(chars, 0, len); writer.flush(); } reader.close(); writer.close();
6.4 IO流异常处理
-
异常处理的核心:
所有的编译时期异常必须try-catch
保证流对象得到关闭
-
异常一般处理示例代码
FileReader reader = null; FileWriter writer = null; try { reader = new FileReader("d:/java/test1.java"); writer = new FileWriter("d:/java/test2.java"); //需求:将test1.java的内容拷贝到test2.java char[] chars = new char[1024]; int len; while ((len = reader.read(chars)) != -1) { writer.write(chars, 0, len); writer.flush(); } } catch (Exception e) { e.printStackTrace(); }finally { if (reader != null){ try { reader.close(); } catch (Exception e) { e.printStackTrace(); }finally { if (writer != null){ try { writer.close(); } catch (Exception e) { e.printStackTrace(); } } } } }
从上面的代码可以看出处理异常的代码逻辑比IO操作还多,结构嵌套很深
-
异常高级处理
-
try-with-resource
java1.7开始出现的跟资源有关的
相关的对象需要实现AutoCloseable接口,流对象都实现了此接口
应用了此语法的流对象不用关闭资源,会自动关闭
这个语法只能在Java7或者更高的版本使用
-
语法
try ( ... 创建AutoCloseable对象的代码 ... ) { 可能出现异常的代码 } catch (异常类型 变量名) { }
-
示例代码
try ( //创建资源对象的代码放在这里 FileReader reader = new FileReader("d:/java/test1.java"); FileWriter writer = new FileWriter("d:/java/test2.java"); ) { char[] chars = new char[1024]; int len; while ((len = reader.read(chars)) != -1) { writer.write(chars, 0, len); writer.flush(); } //不用关闭资源 //reader.close(); //writer.close(); } catch (Exception e) { e.printStackTrace(); }
-
6.5 字符的编码和解码
-
编码表
所谓编码表就是用一个整数去对应一个字符
如: ASCII编码表字符's' s --> 10 -->0000 1010(二进制)
- ASCII
- 是用**一个 8 位的字节**来表示空格、标点符号、数字、大小写字母或者控制字符的,其中最高位为 "0",其他位可以自由组合成 128 个字符的码表
- **ASCII的汉字是1个字节**
- IOS-8859-1
是国际标准化组织 ISO 字符编码标准 ISO-8859 的一部分,它在 ASCII 编码空置的 0xA0-0xFF 的范围内加入了其他符号字母以供西欧来使用,所以也叫 "西欧语言",另外 ISO 的编码还有其他的一些语言编码,这些都是**单字节 8 位编码**。
- GB*
- GB2312 共收录了七千个字符,由于 GB2312 支持的汉字太少而且不支持繁体中文,所以 GBK 对 GB2312 进行了扩展,对低字节不再做大于 127 的规定,以支持繁体中文和更多的字符,GBK 共支持大概 22000 个字符,GB18030 是在 GBK 的基础上又增加了藏文、蒙文、维吾尔文等主要的少数民族文字。
- **GBK的汉字是2个字节**
- Unicode
- 一个全球性的编码方案把所有字母和符号都统一编码进去
- UTF-8以字节为单位对Unicode进行编码
- **UTF-8的汉字是3个字节**
-
编码和解码
编码和解码都必须依靠一个工具作为桥梁,这个工具就是编码表
-
编码: 把字符变成字节(整数)
String类的编码方法:
byte[] getBytes() 使用平台默认字符集(一般默认GBK)将String编码为byte,并存到byte数组中 byte[] getBytes(String charsetName) 使用指定字符集将String编码为byte,并存到byte数组中
-
解码: 把字节(整数)变成字符
String类的解码方法:
String(byte[] bytes) 使用默认字符集将字节数组解码成一个String对象 String(byte[] bytes,String charsetName) 使用指定字符集将字节数组解码成一个String对象
编码和解码的字符集必须一致,不然在多个字节的数据中,会出现乱码
-
示例代码
String str = "你好啊"; byte[] bytes = str.getBytes();//使用默认字符集GBK进行编码 String s = new String(bytes); //使用默认字符集GBK进行解码 //编码和解码的字符集必须一致 byte[] bUtf = str.getBytes("UTF-8");//使用UTF-8编码 String sUtf = new String(bUtf,"UTF-8");//使用UTF-8解码
-
6.6 属性文件 properties
-
使用背景
一般来说,一些简单的改动都需要经过修改源码-编译-运行,这样拓展不强,不好维护,灵活性不够.
我们可以把一些程序中的变量提取出来,放在java代码的外部,用一个文件保存,这个文件是不需要编译的,在程序运行的时候再去动态的读取这些变量信息.
-
实际应用
在项目中使用属性文件,文件必须以.properties结尾,在程序运行时用流去读取信息,Java中专门去读取属性文件并解析的类Properties
-
Properties
Properties extends Hashtable<Object, Object>
Properties 可以从流中加载数据或者将数据保存在流中,属性列表中的键值都是字符串
-
构造方法
Properties()
-
常用方法
void load(Reader reader) 从输入字符流中读取属性键值对 String getProperty(String key) 获得指定键的属性值
-
属性文件的写法
src=e:/qq.exe dest=d:/java/test.java
-
示例代码
Properties properties = new Properties(); FileReader reader =new FileReader("local.properties"); //从流中加载属性文件 properties.load(reader); //根据属性键获得属性值 String src = properties.getProperty("src"); String dest = properties.getProperty("dest");
6.7 RandomAccessFile
RandomAccessFile implements DataOutput, DataInput, Closeable
支持对随机访问文件的读取和写入
-
特征:
可以读也可以写
底层是byte[]
包含一个索引叫文件指针
发送读或写操作后文件指针会往前移动
-
构造方法
RandomAccessFile(String name, String mode) RandomAccessFile(File file, String mode) mode参数: r:表示只支持读,调用write方法会抛出异常 rw:表示支持读写操作,如文件不存在,则会尝试创建文件
-
常用方法
long getFilePointer() 获得当前指针索引 seek(long offset) 设置指针位置
-
示例代码
//创建读写的流对象 RandomAccessFile rw = new RandomAccessFile("d:/java/test.txt", "rw"); rw.writeInt(100); rw.writeChar(98); long index = rw.getFilePointer();//获得指针索引 //进行读操作前,需要先将指针回到开始位置,因为之前进行写操作,指针已移动至当前写的位置 rw.seek(0); int a = rw.readInt(); char c = rw.readChar(); rw.close();
-
实际运用
可用于断点下载
操作步骤:
- 先获取到要下载的文件的大小
- 使用一个随机读写文件记录下载的最新位置
- 使用两个随机读取文件对要下载的文件进行关联,一个负责读,一个负责写
- 每次操作都记录下文件的最新位置并且存入硬盘
- 发送断点后,读取最新的记录,从最新的历史记录继续读取
6.8 正则表达式
-
使用场景
使用正确的规则对字符串进行相关操作(匹配/替换/切割)
-
基本字符
x 字符x \\ 反斜线字符
-
字符类(多个中匹配1个)
[abc] a b 或c [^abc] 任何字符,除了a,b或c [a-zA-Z] a到z,或 A到Z,两头的字母包含在内 [a-d[m-p]] a到d,或者m到p:[a-dm-p](并集) [a-z&&[def]] d,e或者f(交集)
-
特殊意义字符(类似关键字)
. 任何字符(与行结束符可能匹配也可能不匹配) \d 数字:[0-9] \D 非数字:[^0-9] \s 空白字符:[\t\n\x0B\f\r] \S 非空白字符:[^\s] \w 单词字符:[a-zA-Z_0-9] \W 非单词字符:[^\w]
-
数量词
X? X, 一次或者一次也没有, <=1 X* X, 零次或者多次, >=0 X+ X, 一次或多次, >=1 X{n} X, 恰好n次, =n X{n,} X, 至少n次, >=n X{n,m} X, 至少n次,但不超过m次, >=n <=m
-
字符串正则的常用方法
boolean matches(String regex) 判断此字符串是否匹配给定的正则表达式 String[] split(String regex) 根据给定的正则拆分字符串 String replaceAll(String regex,String replacement) 用指定的字符串替换所有匹配正则的内容
-
正则中需要注意的
匹配文本中含有的正则特殊字符: \\d , \\[ , \\{ , \\S 匹配文本的一个反斜杠: \\\\ 匹配文本中的\r\n制表符时: \r\n
-
示例代码
//1开头,11位数字,第二个数字必须为3 5 7 String tel = "13788888843"; String regex1 = "1[357]\\d{9}"; boolean ma = tel.matches(regex1); System.out.println(ma); String exe = "png-jpg;gif-jpeg"; String regex2 = "[-;]"; String[] split = exe.split(regex2); for (String s : split) { System.out.println(s); } String str = "你个S B"; String regex3 = "S.*B"; System.out.println(str.replaceAll(regex3,"*"));