**
FileOutputStream
**
FileOutputStream fos = new FileOutputStream("C:\\a.txt");
//参数是字符串表示的路径或者是File对象都是可以的
//如果文件不存在会创建一个新的文件,但是要保证父级路径正确
//如果文件已经存在会清空文件
fos.write(97);
//方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
byte[] bytes = {97,98,99,100,101};
fos.write(bytes);
fos.write(bytes,1,2);//从1索引开始写两个字符
fos.close();
怎么换行写?
\r\n是换行符,直接写入文件就好了。
String wrap = "\r\n";
byte[] bytes = wrap.getBytes();
fos.write(bytes);
那怎么不被清空,可以续写文件呢?
FileOutputStream有续写开关,即创建对象的第二个参数,需要手动传递。
FileOutputStream fos = new FileOutputStream("C:\\a.txt",append:true);
FileInputStream
**
public int read();
public int read(byte[] buffer);
//如果文件不存在直接报错
FileInputStream fis = new FileInputStream("C:\\a.txt");
//读到文件末尾read方法返回-1,读取一次就移动一次指针
System.out.println(fis.read());
fis.close();
FileInputStream fis = new FileInputStream("C:\\a.txt");
int b;
while((b=fis.read())!=-1){
System.out.println(b);
}
fis.close();
一次读取一个数组大小,实际上read返回实际读取大小,数据存在数组中,可能会覆盖数组之前的数据
byte[] bytes = new byte[2];
int l;
FileInputStream fis = new FileInputStream("C:\\a.txt");//abcde
l=fis.read(bytes);//2
System.out.println(new String(bytes));//ab
l=fis.read(bytes);//2
System.out.println(new String(bytes));//cd
l=fis.read(bytes);//1
System.out.println(new String(bytes));//ed
fis.close();
正确写法
byte[] bytes = new byte[2];
int l;
FileInputStream fis = new FileInputStream("C:\\a.txt");//abcde
l=fis.read(bytes);
System.out.println(new String(bytes,0,l));//ab
l=fis.read(bytes);
System.out.println(new String(bytes,0,l));//cd
l=fis.read(bytes);
System.out.println(new String(bytes,0,l));//e
fis.close();
UTF-8 中文采用三个字节存储 1110XXXX 10XXXXXX 10XXXXXX如此格式,X用二进制编码填充。
**
**
字节缓冲流
**
基本用法,跟上面差不多
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(" "));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(" "));
byte[] bytes =new byte[1024];
int len;
while((len=bis.read(bytes))>0){
bos.write(bytes,0,len);
}
bos.close();
bis.close();
为啥会比基本字节流效率高?
因为字节缓冲的输入和输出流的缓冲区不是一个同一个数组,但是都在内存之中。速度提高的原因是减少了IO次数,因为数据从IO中取出至多8192B的数据放在缓冲区,数据取用的时候都先从内存中的缓冲区去拿。
字符流
**
FileReader
总体跟上面原理和操作差不多。
碰到英文每次读一个字节,中文读多个字节。
public int read()方法读取后在底层进行解码并转成十进制。
public int read(char[] buffer)
原理:创建字符流对象的时候,系统会在内存开辟一个大小为8192的缓冲区,并从文件读取数据尽量填满缓冲区。读取数据的时候如果缓冲区有数据先从缓冲区拿,否则读取文件。
FileReader fr= new FileReader("C:\\a.txt");
fr.read();
FileWriter fw = new FileWriter("C:\\a.txt");//会清空文件
int ch;
while((ch=fr.read())!=-1){//虽然文件被清空,但是缓冲区可能还有数据
System.out.println(ch);
}
fw.close();
fr.close();
FileWriter
原理:创建字符流对象的时候,系统会在内存开辟一个大小为8192的缓冲区,数据首先写到缓冲区,如果缓冲区满了自动加载到文件。可以调用flush方法手动将缓冲区数据加载到文件,close方法关闭文件前会检查缓冲区是否还有未加载数据。
字符缓冲流
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("路径"));
BufferedWriter bw = new BufferedWriter(new FileWriter("路径",true));
String t;
while((t=br.readLine())!=null){//读一行
bw.write(t);
bw.newLine();//换行
}
bw.close();
br.close();
}
转换流
将路径文件以GBK形式读出,UTF-8形式写入
FileReader fr = new FileReader(“路径”,Charset.forName(“GBK”));
FileWriter fr = new FileWriter(“路径”,Charset.forName(“UTF-8”));
序列化流/反序列化流//读写对象
JAVAbean类需要实现Serializable接口
student stu = new student(1,"123");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\asus tky\\IdeaProjects\\com.TX.Game\\" +
"src\\com\\TX\\ui\\1.txt"));
oos.writeObject(stu);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\asus tky\\IdeaProjects\\com.TX.Game\\" +
"src\\com\\TX\\ui\\1.txt"));
System.out.println(ois.readObject());
ois.close();
若修改javabean类版本号可能会变,就不可序列化。所以直接版本号固定住。
private static final long serialVersionUID = -7133239478319723051L;
private transient int age;
transient关键字可以把某个关键字不序列化到本地。
解压流
public static void unzip(File src,File dest) throws IOException {//解压
ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
ZipEntry ze;
while((ze = zis.getNextEntry())!=null){
if(ze.isDirectory()){
File file = new File(dest,ze.toString());//在dest上创建文件夹
file.mkdirs();
}else{
FileOutputStream fos = new FileOutputStream(new File(dest,ze.toString()));
int b;
while((b=zis.read())!=-1){
fos.write(b);
}
fos.close();
zis.closeEntry();
}
zis.close();
}
}
压缩流
public static void main(String[] args) throws IOException, ClassNotFoundException {
File src = new File("C:\\Users\\asus tky\\Desktop\\aaa");
File desP = src.getParentFile();//将压缩包放在同级目录下
File dest = new File(desP,src.getName()+".zip");
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
zip(src,zos);
zos.close();
}
public static void zip(File src,ZipOutputStream zos) throws IOException {
File[] files = src.listFiles();
for (File file : files) {
if(file.isFile()){
ZipEntry ze = new ZipEntry(src.getName()+"\\"+file.getName());
zos.putNextEntry(ze);//开始编写新的ZIP文件条目并将流定位到条目数据的开头。
FileInputStream fis = new FileInputStream(file);
int b;
while((b=fis.read())!=-1){
zos.write(b);//将一个字节数组写入当前ZIP条目数据。
}
fis.close();
zos.closeEntry();
}else if(file.isDirectory()){//文件夹直接递归
zip(file,zos);
}
}
}
JAVA的IO中体现的设计模式
主要体现了两个模式,一个是装饰模式,一个是适配器模式。
- 装饰模式用于对象的功能增强,体现了设计模式的开闭原则。以往对象之间的功能增强是通过继承来实现的,但是装饰模式摒弃了这种思想,采用组合方式,降低了复杂性。比如,BufferInputStream增强了fileInputStream。但是
BufferInputStream和fileInputStream都继承于InputStream类,也就是继承了同一个抽象类或者某个接口。 - 适配器模式主要用于不同接口对象之间的协作功能。比如字节流FileInputStream和字符流InputSteamReader他们的接口就不一样,要想实现这两个的协作就要使用适配器来实现字节流和字符流之间的相互转换。JAVA的IO中有两个适配器InputStreamReader和OutputStreamWriter,他们就能分别实现字节流到字符流和字符流到字节流的转换。
NIO
多路复用,跟传统的阻塞IO相比,他大大提高了IO的效率。NIO有三个关键组件,即缓冲区,通道,选择器。缓冲区支持读写,也就是内存输出到外部或外部数据输入到内存,他们的数据都是先放在缓冲区当中的。NIO的通道是一个双向通道,选择器应该注册他想要监听的通道和对应的socket。
目前实现的方式有三种:select,poll,epoll;
当外设准备好了数据,他会通知选择器,如果是select和poll,由于选择器不知道是哪个socket准备好了,就要去遍历查找;epoll就不用,选择器直接就能知道。
零拷贝
DMA:直接内存存取,可以用于内存和内存之间,内存与外设之间的数据拷贝,速度比较快。DMA有一个重要组件DMA控制器,相当于一个小CPU,CPU只要初始化DMA相关组件,具体的复制操作直接交给DMA,CPU就可以去做其他事情。
我们思考磁盘中的数据到网卡上经历了两次DMA拷贝和两次拷贝,一共四次。假如数据在用户台没有被改变,那么将进入用户态就是多余的。所以我们就思考能不能不进入用户态,在内核态就完成处理,并且这个过程我们应该尽量少得消耗CPU。
那能不能直接将内核缓冲区的数据直拷贝到网卡当中直接丢弃socket缓冲区呢?显然不行,因为socket缓冲区需要和传输层交互,比如TCP协议,他需要知道本次我要取的数据的长度等等信息,所以我们实现将数据从内核缓存区DMA拷贝到网卡同时告知socket缓冲区本次拷贝数据的信息。
零拷贝就是让用户态和内核态之间的数据不再通过拷贝的方式传输,使用了「内存映射」,做到了内核态和用户态数据的零拷贝。