文件与IO流:
IO流这里有很多类,都是针对文件进行相关操作,因为我们想永久地保存数据。下面我就给大家大概介绍一下IO流:
流类图结构:
看到上图,我们会发现IO流所包含的类实在是太多了,但是实际上它们都是延伸出来的,其实大同小异,整个类的拓展体现了装饰者模式,这个后面会具体说。然后让我们来看下这里都有哪些重要的知识点:
File类:
- File类:表示文件和目录路径名的抽象表示形式,是唯一与文件本身有关的操作类
- list列出当前目录下的所有文件名;listFiles列出当前目录下的所有文件,以File对象返回
字符流和字节流:
- 流的本质是数据传输,根据数据传输特性将流抽象为各种类;根据处理数据类型的不同分为:字符流和字节流,根据数据流向不同分为:输入流和输出流
- 字节输出流(抽象类):OutputStream(表示输出字节流的所有类的超类)
- 字节输入流(抽象类):InputStream(表示输入字节流的所有类的超类)
- 输入输出字节流操作原理:每次只会操作一个字节(从文件中读取或写入)
- 字节数组大小不能为单数,因为一个汉字两个字节,单数会有乱码。如若读取数据长度没有字节数组大,会保留上次读取的数据
- 文件字符操作流会自带缓存,默认大小为1024字节,在缓存满后,或手动刷新缓存,或关闭流时会把数据写入文件中;字节操作流,默认每次执行写入操作会直接把数据写入文件中
- 字符流的内部实现还是字节流
OutputStreamWriter:可以将输出的字符流转换为字节流的输出形式,InputStreamReader:将输入的字节流转换为字符流输入形式
我们来实际演示一下字节输入流的使用:
public class ByteStreamDemo {
private static void in(){
File file = new File("c:/test/vince.txt");
try {
InputStream in = new FileInputStream(file);
byte[] bytes = new byte[12]; // char[]字符输入流
StringBuilder buf = new StringBuilder();
int len = -1; // 表示每次读取的字节长度
// 把数据读入到数组中,并返回读取的字节数,当不等于-1时,表示读取到数据,等于-1表示文件已读完
while((len = in.read(bytes))!=-1){
// 根据读取到的字节数组,再转换为字符串内容,添加到StringBuilder中
buf.append(new String(bytes,0,len));//避免后面的重复出现
}
System.out.println(buf);
// 关闭输入流
in.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args){
in();
}
}
字符输出流:
public class CharStreamDemo {
private static void out() {
File file = new File("c:\\test\\vince.txt");
Writer out = null;
try {
out = new FileWriter(file,true);
out.write(",村花到我家");
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
out();
}
}
缓存输入输出流:
- 缓存的目的:解决在写入文件操作时,频繁的操作文件所带来的性能降低的问题
- BufferedOutputStream内部默认的缓存大小是8KB,每次写入时存储到缓存中的byte数组中,当数组存满时,会把数组中的数据写入文件,并且缓存下标归零
- FileReader:内部使用InputStreamReader(sun.nio.cs.StreamDecoder),解码过程,byte->char,默认缓存大小是8KB
- BufferedReader。BufferedWriter:默认缓存大小是8K,但可以手动指定缓存大小,把数据直接读取到缓存中,减少每次转换过程,效率更高
缓存输出流:
private static void byteWriter(){
File file = new File("c://test//vince.txt");
try {
OutputStream out = new FileOutputStream(file);
//构造一个字节缓冲流
BufferedOutputStream bos = new BufferedOutputStream(out);
String info = "小河还是流水哗啦啦";
bos.write(info.getBytes());
bos.close(); //会自动关闭out
// out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
对象流:
- ObjectOutputStream将java对象的基本数据类型和图形写入OutputStream;ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化
- 如果一个类创建的对象,需要被序列化,那么该类必须实现Serializable接口,Serializable是一个标记接口,没有任何定义,为了告诉JVM该类对象可以被序列化
- 什么时候对象需要被序列化?
- 把对象保存到文件中(存储到物理介质)
- 对象需要在网络上传播
- 对象序列化:把对象写入文件:实际写入的是类名、属性名、属性类型、属性值等;
反序列化:从文件中把对象的内容读取出来,还原成对象 - 在属性前加修饰词transient表示这个属性在序列化中被忽略
对象序列化:
首先我们需要构造一个实例化对象:
public class Dog implements Serializable {
private String name;
private int age;
private String sex;
public Dog(){}
public Dog(String name,int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
然后就是把它写入文件的操作:
public class ObjectStreamDemo {
private static void writeObject() {
Dog dog = new Dog("wangwang",2,"nan");
File file = new File("c:/test/dog.obj");
OutputStream out = null;
try {
out = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(out);//ObjectInputStream ois
oos.writeObject(dog);//Dog dog = (Dog)ois.readObject();
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
writeObject();
}
}
其余流:
- 想单个字节地处理字符串和文件无关系时,可以用字节数组流,它基于内存操作,内部维护一个字节数组,无需关闭
- 数据流:与机器无关的操作JAVA的基本数据类型,按字节写入的,会浪费空间
- 字符串流:以一个字符串为数据源,来构造一个字符流;作用:在WEB开发中,我们经常要从服务器上获取数据,数据的返回格式是通过一个字符串(XML,JSON),我们需要把这个字符串构造成一个字符流然后再用第三方的数据解析器来解析数据
- 管道流测试:一个线程写入,一个线程读取;作用:用于线程之间的数据通讯
- RandomAccessFile是IO包的类,从Object直接继承而来,可对文件进行读取和写入
- Properties可读可写:主要用于读取Java的配置文件,ResouceBundle只读
装饰者模式:
- 装饰者模式:动态地将责任附加到对象上,想要拓展功能,装饰者提供有别于继承的另一种选择
通俗点说,就比如FileoutputStream是毫无修饰的流,而BufferdOutputStream、DataOutputStream等则是FileoutputStream的装饰流
我们来看一个具体的例子:
举例:我们现在要来装饰豆浆,要往里面添加糖和黑豆
//先声明饮品的接口
public interface Drink {
float cost();
String description();
}
//然后是豆浆
public class SoyaBeanMilk implements Drink {
@Override
public float cost() {
return 10f;
}
@Override
public String description() {
return "纯豆浆";
}
}
//然后是重点,装饰者
public Decorator(Drink drink){
this.drink = drink;
}
@Override
public float cost() {
return drink.cost();
}
@Override
public String description() {
return drink.description();
}
}
public class SugarDecorator extends Decorator {
public SugarDecorator(Drink drink){
super(drink);
}
@Override
public float cost() {
return super.cost()+1.0f;
}
@Override
public String description() {
return super.description()+"糖";
}
}
public class BlackBeanDecorator extends Decorator {
public BlackBeanDecorator(Drink drink){
super(drink);
}
@Override
public float cost() {
return super.cost()+2.0f;
}
@Override
public String description() {
return super.description()+"黑豆";
}
}
//装饰开始
public class Test {
public static void main(String[] args){
Drink drink = new SoyaBeanMilk();
SugarDecorator sugar = new SugarDecorator(drink);
BlackBeanDecorator blackbean = new BlackBeanDecorator(sugar);
System.out.println("你点的豆浆是:"+blackbean.description());
System.out.println("花了:"+blackbean.cost());
}
}
常见的字符编码:
- iso8859-1:编码属于单字节编码,最多只能表示0-255的字符范围,主要在英文上应用
- GBK/GB2312:中文的国际编码,专门用来表示汉字,是双字节编码
- unicode:java中就是使用此编码方式,是使用16进制表示的编码,此编码不兼容iso8859-1编码,容易占用空间
- UTF:兼容iso8859-1编码,是不定长编码,可以节省空间,在中文网页中使用
- 通常产生乱码的情况是;两个不兼容的编码相互转换
NIO(新技术):
- NIO将最耗时的I/O操作(即填充和提取缓冲区)转移回操作系统,可以极大地提高速度,以块的方式处理数据。在NIO库中,所有数据都是用缓冲区处理的
- 通道Channel是一个对象,可以通过它读取和写入数据
- 通道映射技术:
其实就是一种快速读写技术,它将通道所连接的数据节点中的全部或部分数据直接映射到内存的一个Buffer中,而这个内存Buffer块就是节点数据的映像,你直接对这个Buffer进行修改会直接影响到节点数据,而这个Buffer也不是普通的Buffer,叫做MappedBuffer,即镜像Buffer,对该Buffer进行修改会直接影响到实际的节点(更新到节点);
map原型:MappedByteBuffer map(MapMode mode, long position, long size);//将节点中从position开始的size个字节映射到返回的MappedByteBuffer中
mode印出来映射的三种模式,在这三种模式下得到的将是三种不同的MappedByteBuffer:三种模式都是Channel的内部类MapMode中定义的静态常量,这里以FileChannel举例:
i.FileChannel.MapMode.READ_ONLY:得到的镜像只能读不能写(只能使用get之类的读取Buffer中的内容);
ii.FileChannel.MapMode.READ_WRITE:得到的镜像可读可写(既然可写了必然可读),对其写会直接更改到存储节点;
iii.FileChannel.MapMode.PRIVATE:得到一个私有的镜像,其实就是一个(position, size)区域的副本罢了,也是可读可写,只不过写不会影响到存储节点,就是一个普通的ByteBuffer了!
只有RandomAccessFile获取的Channel才能开启任意的这三种模式 - 比较IO操作的性能比较:
- 内存映射最快
- NIO读写文件
- 使用了缓存的IO流
- 无缓存的IO流
我们来体会一下NIO的便利:
public class NIODemo {
public static void main(String[] args){
//创建一个字节缓冲区,申请内存空间为8字节
ByteBuffer buf = ByteBuffer.allocate(8);
System.out.println("position="+buf.position());
System.out.println("limit="+buf.limit());
System.out.println("capacity="+buf.capacity());
buf.put((byte)10);
buf.put((byte)20);
buf.put((byte)30);
buf.put((byte)40);
System.out.println("position="+buf.position());
System.out.println("limit="+buf.limit());
System.out.println("capacity="+buf.capacity());
//缓冲区反转
buf.flip();
System.out.println("position="+buf.position()); //position=0
System.out.println("limit="+buf.limit());//limit=position=4
System.out.println("capacity="+buf.capacity());
//告知在当前位置和限制之间是否有元素
if(buf.hasRemaining()){
//返回当前位置与限制之间的元素数
for(int i=0;i<buf.remaining();i++){
byte b = buf.get(i);
System.out.println(b);
}
}
}
}
IO流的相关知识基本介绍完了,因为这里的内容太多,所以我们需要多做练习。