从今以后,别再过你应该过的人生,去过你想过的人生吧!
——梭罗
1.什么是IO流
现实中我们要对电脑上的很多文件进行读写等操作(比如查看复制图片),其底层就是要用到IO,它是(input 和output)缩写,用于实现对数据的输入与输出。Java IO是对数据输入输出进行操作的一些接口和类。
Java IO 的基本操作都是围绕着流(Stream)对象展开的,流是一个抽象的概念(想象成水流),它是一系列连续的字节或字符,数据从一个端点流入,从另一个端点流出。Java IO 从两个维度来区分流,即字节流和字符流。
流的分类:
方向上: 输入流(只可以读数据)和输出流(只可以写数据)
数据类型上:字节流(以字节来传输数据)和字符流(以字符来传输数据)
功能上:节点流(可以直接向io设备读取数据)和处理流(节点流的加强版)
2.Java IO的分类
总共可以分为四大类:字节输入流,字节输出流,字符输入流,字符输出流。
输入是读数据的,输出是写数据的。
咱们一起来看看具体的吧!
1.抽象基流(节点流,毫无疑问都是抽象类,开发中都是用它们的子类的)
输入流
InputStream
int read() 读取下一个字节
int read(byte [] b) 读取下一批字节,将其存入数组,返回读取的字节数
int read(byte [] b, int off, int len) 从off开始最多读取len个字节,将其存入数组,返回读取的字节数
Reader
int read() 读取下一个字符
int read(char [] c) 读取下一批字符,将其存入数组,返回读取的字符串数
int read(char [] c, int off, int len ) 从off开始最多读取len个字符,将其存入数组,返回读取的字符数
输出流
OutputStream
void write(int b) 输出指定的字节b
void write(byte [] b) 输出指定的字节数组b
void write( byte [] b, int off, int len ) 输出指定的字节数组,从off开始最多输出len个字节
Writer
void write(int c) 输出指定的字符
void write(char [] c) 输出指定的字符数组
void write(char [] c, int off, int len ) 输出指定的字符数组, 从off开始最多输出len个字符
void write (String str) 输出指定的字符串
void write(String str, int off, int len) 输出指定的字符串,从off开始最多输出len个字符串
注意:
1.上面的四个类都是抽象类,是常见io流的的父类
2.上面的四个类都定义了close()方法,你需要在使用完后调用此方法
3.无论是否发生异常,都要关闭它,通常在finally中关闭
4.上面的四个类都实现了Closeable接口,因此可以在使用try中创建流,方便自动关闭
2.文件流(节点流,即用来直接访问文件的类)
FileInputStream, FileoutputStream
FileReader, FileWriter
用文件流来读写文件
//定义一个方法,用字节流来拷贝文件,需要老的文件和新的文件
public static void copyFile(String newsrc, String oldsrc){
try (
//就是在try执行完毕后它会自动关闭,但是前提它必须实现AutoCloseable接口,自动回收资源
FileInputStream f = new FileInputStream(oldsrc); //输入流,读数据的
FileOutputStream f1 = new FileOutputStream(newsrc); //输出流,写数据,写入新的文件里面
)
{
byte[] bytes = new byte[128];
//创建一个能存128位字节数组
int len = 0; //数据读到的字节数
// 一个公式,读取文件中的数据,如果文件长度非常大要反复读,当里面已经没有数据了就读到0个字节,假,循环结束
while ((len = f.read(bytes, 0, 128)) >0) {
//从第0开始,最多读128个字节存入bytes数组,这里的len是实际读到的实际字节长度
f1.write(bytes, 0, len);
//写入指定的字节,从数组0开始,最多写len
}
System.out.println(Arrays.toString(bytes));
System.out.println("复制成功");
} catch (Exception e) {
new Exception("文件复制失败");
}
}
//定义一个方法,用字符流来拷贝文件,需要老的文件和新的文件
public static void copyFile1(String oldsrc, String newsrc){
try (
FileReader f = new FileReader(oldsrc); //字符输入流。读数据的
FileWriter f1 = new FileWriter(newsrc); //字符输出流,写入数据的
)
{
char[] c = new char[128];
int len = 0;
while ((len = f.read(c, 0, 128))>0) {
f1.write(c, 0, len);
}
System.out.println(Arrays.toString(c));
System.out.println("复制成功");
} catch (Exception e) {
new Exception("字符流复制失败");
}
}
结论:
读文件要输入流,即 输入流 f =new 输入流(“文件绝对地址”),然后再调用read方法读数据
写文件要用输出流,即输出流 f =new 输出流(“文件绝对地址”),然后再调用writer方法写数据
访问数组和访问管道和访问字符串的差不多,就不一一介绍啦!
3.缓冲流(处理流,实例化要传入节点流的实例,不是文件地址啦)
BufferedInputStream, BufferedOutputStream
BufferedReader, BufferedWriter
可能有人会问为什么用这个,解释一下,当我们使用字节流的时候每读一个字节就需要与磁盘交互一次,效率太低啦。缓冲流就是在内存中设置一个缓冲区用来存足够多的数据,再拿就在内存中拿啦,不用再跑磁盘里面去啦。(就相当于搬砖,有个小车)
// 创建复制方法,使用缓冲流
public static void copyFile(String oldsrc, String newsrc) {
try ( //自动关闭的
BufferedInputStream b = new BufferedInputStream(new FileInputStream(oldsrc)); //字节输入缓冲流
BufferedOutputStream b1 = new BufferedOutputStream(new FileOutputStream(newsrc)); //字节输出输出流,实例时用对象啦,不是地址
) {byte[] bytes = new byte[128];
//创建一个128位的存储数组
int len = 0;
while ((len = b.read(bytes, 0, 128))>0) {
b1.write(bytes, 0, len);
}
} catch (Exception e) {
new Exception("复制失败");
}
}
注意:
使用缓冲输入流时,它会把缓冲区填满,每一次掉用方法时从缓冲区读数据;
使用缓冲输出流时,每一次写入在缓冲区里,满了自动写到设备中,当然也可以调用flush()方法手动刷入(关闭流时会自动调)。
4.转换流(处理流,实例化时要有相应的实例对象)
InputStreamReader 是字节流转换成字符流OutPutStreamWriter是字符流转为字节流
使用
//从键盘录入打印到控制台
try (
//把键盘上打的文字的字节流--变成字符流,读
//InputStreamReader实例的时候需要传入一个低级流,而System.in是InputStream类型
BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
//然后把他们放到缓冲区里面,用字符输入缓冲流
//把字节---字符输出,System.out是OutputStream类型的,写
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
){
String line = null;
while ((line = d.readLine()) != null) {
//readLine读取一行字符
if(line.equalsIgnoreCase("out")) {break;} //如果匹配到out,就跳出
//否则
bufferedWriter.write(line); //把这个字节变成字符输出到终端
bufferedWriter.newLine(); //换行
bufferedWriter.flush(); //输入到硬盘}
} catch (Exception e) {
// TODO: handle exception
}
结论:
InputStreamReader类包含了一个底层输入流,可以从中读取原始字节。它根据指定的编码方式,将这些字节转换为Unicode字符。
OutputStreamWriter从运行的程序中接收Unicode字符,然后使用指定的编码方式将这些字符转换为字节,再将这些字节写入底层输出流中。
1. 其是字符流和字节流之间的桥梁
2. 可对读取到的字节数据经过指定编码转换成字符
3. 可对读取到的字符数据经过指定编码转换成字节
转换流的主要作用的是方便使用人员操作,便捷使用,方便用户在字节和字符之间进行操作。
5. 打印流(处理流,实例化时需要节点流才可以实例化)
PrintStream, PrintWriter 两个只负责输出(写)。
创建一个方法,使用打印流
public static void testprint(){
try(
FileOutputStream f = new FileOutputStream("D:\\JAVA文件\\API文件\\1.txt");
//创建一个字节输出流,写入数据
PrintStream pr = new PrintStream(f); //创建打印流,处理流需要节点流实例
) {
pr.println("晓看天色暮看云,行也思君,坐也思君"); //会打印到指定的地方
pr.println("真棒");
} catch (Exception e) {
e.printStackTrace();
}
}
//它不会打印在控制台上面了,直接到文件里面去了
结论:
1.都是处理流,实例化时需要对应的节点流,两个类用法差不多
2.System.out就是PrintStream类型的
都可以高效的把数据打印到文件中
6.RandomAccessFile(一个支持随机访问的类,只能访问文件)
之前我们那些io流读都是从文件开始到结束,并且要么只读只写,这个类就不一样了,既可以读也可以写,当我们新建了一个RandomAccessFile对象,这个指针指向文件的开始处,即0字节的位置,当读/写了n个字节,这个指针后移到n,除此之外,指针可以根据需要自由移动到指定位置。
两个构造方法:
1、RandomAccessFile(File file, String mode)
2、RandomAccessFile(String name, String mode)
mode为下面的一种:
使用
// 如果我现在想在12.txt文件再追假加几句话
try (
RandomAccessFile r = new RandomAccessFile("D:\\JAVA文件\\API文件\\IO流\\12.txt","rw");
//rw表示可以读,也可以写
){
//把指针定位到末尾,加数据,seek方法调整指针的位置
r.seek(r.length());
r.write("我以为忘了想念\n".getBytes());
//写数据,以指定字符集utf-8把字符串编码成字节
r.write("i like you ".getBytes());
//再把指针点位到开头
r.seek(0);
String line = null;
while ((line = r.readLine()) != null) { //解码,读一行,如果那一行为空则结束循环
String l = new String(line.getBytes("ISO8859-1"),"utf-8"); //用ISO8859-1来编码,再用utf-8解码,得到字符串
System.out.println(l);
}
} catch (Exception e) {
// TODO: handle exception
}
结论:
它的用法跟io流差不多
1. long getFilePointer() 返回文件指针当前所指向的位置
*2. void seek(long pos) 将文件指针定位到指定位置(pos)
7.NEW IO
它是什么?它是jdk1.4后推出的更高效处理文件读写的API,它是基于通道Channel和缓存Buffer来实现滴(说好听一点就是通道负责传输,缓存负责储存)!两个要一起用。
Buffer接口介绍
1.它下面有很多子类,最常用的是ByteBuffer,CharBuffer。同时它只能只能通过静态方法来实例化对象,public static CharBuffer allocate(int capacity)
2.它的四个核心成员变量:
*1.容量(capacity): Buffer可以存储的最大数据量,该值设置后不可以改变
* 2.界限(limit):Buffer中可以读/写数据的边界,limit之后的数据不能访问 (如果1000个存储量,存了600,那么limit就指向600,表示最多读到这)
* 3.位置(position):下一个可以被读写的数据的位置(就是当前数据的索引)
* 4.标记(mark):Buffer允许将位置直接定位到该标记处,这是一个可选的属性
* 上述变量满足如下的关系:0 <= mark <= position <= limit <= capacity
3.它的一些重要方法:
allocate():创建一个Buffer类的实例对象。
put():将数据写入缓冲区。
flip():将缓冲区切换成读模式。
get():从缓冲区读取数据。
clear():清空缓冲区,将缓冲区从读模式切换成写模式。
compact():将缓冲区从读模式切换成写模式。
4.用法
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(8); //创建一个容量位8字节的缓存
printBuffer(buffer, "创建Buffer对象"); //printBuffer方法为自定义打印方法
// 存放数据,put用于存放数据
buffer.put("A").put("B").put("C");
printBuffer(buffer, "数据存放");
//准备取出,切换为读模式 ,flip方法
buffer.flip();
printBuffer(buffer, "准备取出");
//取出数据 ,get方法用于读取一个byte
System.out.println("###:\t"+buffer.get()+", "+buffer.get());
printBuffer(buffer, "取出");
//重置指针,并没有清空数据
buffer.clear();
printBuffer(buffer, "重置指针");
//绝对位置(不影响指针,相当于从数
组里面取值)
System.out.println("###: \n"+buffer.get(0)+","+buffer.get(1));
printBuffer(buffer, "绝对取值");
}
Channel接口介绍
1. 它的子类也有很多,常用的为FileChannel(文件通道,读写)。它实例化有两种方法,一是各个Channel提供的open()方法, 二是FileInputStream,FileOutputsream,RandomAccessFile类提供了getChannel()方法,可以直接返回FileChannel。
2.它的一些常用方法
*map()方法用于将Channel对应的数据映射成ByteBuffer
* read(buffer)方法有一系列重载的形式,用于在此通道里面读字节序列到缓冲里面
*write(buffer)方法有一系列重载的形式,用于从缓冲里面写字节序列到通道中
3.它的使用
//追加内容到文件的方法
public static void appendF (String filesrc,String content ){
try (
FileChannel channel = new RandomAccessFile(filesrc, "rw").getChannel();
//使用RandomAccessFile来创建FileChannel对象
){
//追加内容首先把指针定位到文件末尾
channel.position(channel.size()); //得到文件的大小
//创建Buffer,把buffer加入到channel ,容量位1024字节
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(content.getBytes()); //把字符串内容转换位字节,然后存入buffer
buffer.flip(); //准备取值,要从缓冲里面拿数据
channel.write(buffer); //从缓冲区写数据到通道
System.out.println("添加成功");
} catch (Exception e) {
// TODO: handle exception
}
}
结论:
nio要基于Channel和Buffer一起使用才可以
8. 序列化机制
它是什么?简单的说就是可以把对象变成二进制字节序列,这些序列保存在磁盘上或网络中传输,并且允许它们恢复成之前的对象(对象<——>字节序列)
1.对象序列化,对象→字节序列
2.反序列化,字节序列→对象
3.要实现Seriazable接口才支持序列化,它只是一个标识里面没有任何方法,同时现在很多类都已经实现了这个类了,包装类,String,Date等
4.怎么用,当然啦,要使用对象流ObjectInputStream和ObjectOutputStream两个处理流
//自己定义一个类,可以被序列化这个类
class Car implements Serializable{
private String brand;
private String color;
// private int age;
public Car(String b ,String color ){
System.out.println("初始化成功");
this.brand = b;
this.color = color;
}
public String getB(){
return brand;
}
public String getC(){
return color;
}
public void setB(String b ){
this.brand = b;
}
public void setC(String c ){
this.color = c;
}
public String toString(){
return "Car{" + "brand:" + brand + "--color:"+color+"}";
}
}
序列化(对象变字节序列)
1.创建ObjectOutputStream,然后调用2.writeObject()方法
//定义一个方法,用来序列化
public static void testS(){
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JAVA文件\\API文件\\IO流\\122.txt"))
//它是处理流,把对象转换成字节,然后写到文件里面
){
oos.writeObject(new Car("兰博基尼", "红色")); //把对象变成字节写入文件,当打开文件时会看到乱码
oos.writeObject(new Car("宝马", "蓝色"));
oos.writeObject(new Car("红旗", "黑色"));
} catch (Exception e) {
new Exception("序列化错误");
}
}
反序列化(字节序列变对象)
1.创建ObjectInputStream对象,然后调用2.readObject()方法
//再定义一个方法,用来反序列化
public static void testD(){
try (
ObjectInputStream bj = new ObjectInputStream(new FileInputStream("D:\\JAVA文件\\API文件\\IO流\\122.txt"))
//把之前的里面的字节转化成对象
){
System.out.println(bj.readObject()); //它不用调用构造器
System.out.println(bj.readObject()); //把字节转成对象,并调用ToString方法
System.out.println(bj.readObject());
} catch (Exception e) {
// TODO: handle exception
}
}
结论:
一.序列化只是把对象中的成员变量转换为字节序列了,与其他的成员方法无关
* 1.该对象中引用类型的成员变量也必须是可序列化的(string可以)
* 2.该类的直接或间接的父类,要么具有无参构造器,要么也是可以序列化的
* 3. 一个对象只能被序列化一次,再次序列化时仅仅会输出它的序列号而已(每个被序列化的对象都有一个序列号,在序列化对象之前,程序会检测它是否被
* 序列化过,若没有程序会把它转化字节序列,若对象已经被序列化过,程序会直接输出它的序列号)
二 transient关键字
* transient关键字用于修饰成员变量,表示序列化时将会忽略它,反序列化时它会把这个成员变量输出为null
* transient关键字只能修饰成员变量,不能修饰类中的其他的内容
* 为什么要这个关键字:因为在某些场景里,不希望序列化某个成员变量 1.该成员变量是敏感信息,如银行密码,账号,怕传到网上去 2.该成员变量是引用类型,但它没有实现序列化接口
(◦˙▽˙◦)(◦˙▽˙◦)(◦˙▽˙◦)
这是我的总结与想法,就简单分享到这里啦,大家有什么问题评论区见,一起向前!