IO流概述
IO流:存储和读取数据的解决方案
I:input O:ourput 流:像水流一样传输数据
IO流作用?
用于读写数据(本地文件,网络)
IO流按照流向可以分类为哪两种流?
输出流:程序->文件
输入流:文件->程序
IO流按照操作文件的类型可以分类哪两种流?
字节流:可以操作所有类型的文件
字符流:只能操作纯文本文件(用winods系统自带的记事本打开并且能读懂的文件)
IO流体系
FileOutputStream
1.创建字节流对象(相当于在程序和文件建立了一条通道)
2.写入数据
3.释放资源(相当于把这条通道拆除)
package IODemo; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class demo1 { public static void main(String[] args) throws IOException { FileOutputStream fos=new FileOutputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"); //1.创建字节流对象 fos.write(97);//2.写入数据 fos.close();//3.释放资源 } }
细节注意:
①创建字节流对象:
细节1:参数是字符串表示的路径或者是File对象都是可以
细节2 :如果文件不存在会创建一个新的文件,但是要保证父级路径要存在
细节3:如果文件已经存在,则会清空文件
我们可以看下源码中两种不同的构造方法[细节1:参数是字符串表示的路径或者是File对象都可以]
1.参数是String
public FileOutputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null, false);//ctrl+b this跟进发现跳入的是下面的方法,new File(name)也会将其转为File类型 }2.参数是File:
public FileOutputStream(File file, boolean append) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkWrite(name); } if (name == null) { throw new NullPointerException(); } if (file.isInvalid()) { throw new FileNotFoundException("Invalid file path"); } this.fd = new FileDescriptor(); fd.attach(this); this.append = append; this.path = name; open(name, append); }细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径要存在
可以看到b.txt不存在,运行之后:会创建一个b.txt
其次,如果我们把父级路径改为不存在的:可以看到找不到指定路径,控制台报错
细节3:如果文件已经存在,则会清空文件
运行之前b.txt的内容
运行之后b.txt的内容:
可以看到会将其内容清空,然后写入。
②写数据:
细节:write方法的参数是正数,但是实际上写到本地文件中的是整数的ASCII对应的字符
上面的细节3也可以看到,写入的内容都是以ACSII对应字符的形式。
③释放资源:
细节:每次使用完流之后都要释放资源
如果不释放资源,你想如果java程序一直占用这个文件,你想对其删除操作,就会显示被占用中。
FileOutpurStream写数据的三种方式:
方法名称 | 说明 |
void write(int b) | 一次写一个字节数据 |
void write(byte []b) | 一次写一个字节数组数据 |
void write(byte[] b,int off,int len) | 一次写一个字节数组的部分数据 |
代码演示:
可以看到下面通过把字符串转化为byte数组,然后写入字节数组数据
下面则是利用void write(byte[]b,int off,int len)的方法,off从索引几开始,然后截取长度为len
换行和续写
换行:
package IODemo; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class demo3 { public static void main(String[] args) throws IOException { /* 换行符 windows \r\n linux \n mac \r */ FileOutputStream fos=new FileOutputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"); String s="wohao"; byte[]bytes=s.getBytes(); String s1="\r\n"; byte[]bytes1=s1.getBytes(); String s2="shuai"; byte[]byte2=s2.getBytes(); fos.write(bytes); fos.write(bytes1); fos.write(byte2); fos.close(); } }
结果输出:
续写:
查看构造方法,打开续写开关就可以续写
public FileOutputStream(String name, boolean append) throws FileNotFoundException { this(name != null ? new File(name) : null, append); }再次对其运行,可以看到文件内容进行了续写。
FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来
1.创建字节输入流对象
2.读数据
3.释放资源
package IODemo; import java.io.FileInputStream; import java.io.IOException; public class demo4 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"); //创建字节流对象 int read=fis.read();//读取数据 System.out.println(read); fis.close();//释放资源 } }
细节注意:
①创建字节输入流对象
细节1:如果文件不存在,就直接报错 (这里和字节输出流类似,只不过这里文件不存在直接就会报错,不会去创建一个新的文件,大家可以想象一下,如果java把它设成不存在会创建一个新的文件,那么创建的新文件也是空的,所以读取什么呢?)
②写数据
细节1:一次读一个字节,读出来的是数据在ASCII对应的数字
细节2:读到文件末尾了,read方法返回-1
package IODemo; import java.io.FileInputStream; import java.io.IOException; public class demo4 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"); int read1=fis.read(); int read2=fis.read(); System.out.println(read1); System.out.println(read2); fis.close(); } } a.txt内容: w 控制台结果: 119 -1
可以看到,写数据时,每次都是都是从文件里读取一个字节,读取的数据对应的就是ASCII的数组,当没有数据的时候读取的就是-1
③释放资源
细节:每次使用完流之后都要释放资源
FileInputStream循环读取
package IODemo; import java.io.FileInputStream; import java.io.IOException; public class demo5 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("day04_code\\src\\IODemo\\b.txt"); int b; b=fis.read();//每次read()指针都会移动 while(b!=-1){ //当读取到文件末尾是就会跳出 System.out.print((char)b); b=fis.read(); } } }控制台输出:
wohaoshuaiw
文件拷贝(综合练习)
拷贝过程代码:
运行结果如下:
FileInputStream弊端:
一次读写一个一个字节
解决方案:
FileInputStream一次读多个字节方法:
方法名称 | 说明 |
public int read() | 一次读写一个字节数据 |
public int read(byte [] buffer) | 一次读一个自己数组数据 |
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满
数组本身会占用内存空间,长度一般为1024整数倍,一般合理设置
package IODemo; import java.io.FileInputStream; import java.io.IOException; public class demo7 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("day04_code\\src\\IODemo\\a.txt"); byte []bytes=new byte[2]; int len; //一次读取多个字节数据,具体读多少,跟数组长度有关 //返回值:本次读取到了多少个字节数据 //a.txt内容是abcde len=fis.read(bytes); System.out.println(new String(bytes));//输出结果是ab System.out.println(len);//输出结果是2 len=fis.read(bytes); System.out.println(new String(bytes)); //输出结果是cd System.out.println(len);//输出结果是2 len=fis.read(bytes); System.out.println(new String(bytes));//输出结果是ed System.out.println(len);//输出结果是1 fis.close(); } } 控制台输出: ab 2 cd 2 ed 1
分析:
记事本的内容是abcde,可以看到我们的长度是5,但是我们创建的字节数组是2,也就是说每次读取两个字节,为什么最后一次读取读了一个字节但是输出读取的内容确是ed呢?
从下面两幅图片可以看到:
我们当读取第二次的时候,读取的内容则是cd,字节数组的内容是cd,当我们读取到第三次的时候,指针已经在e的位置了,如果要再进行读取,那么读取的长度为1,但是字节数组的内容现在是cd,读取e只把字节数组索引0的位置进行了替换,所以字节数组的内容为ed,最终读取的内容也是ed。
那么,面对这种情况我们肯定想让最后一次读取的内容是长度为1,我们该如何解决呢?
可以看到,String类提供了这种构造方法,可以从
参数2可以从数组0索引开始读取
参数3代表读取的长度
public String(byte bytes[]) { this(bytes, 0, bytes.length); }我们只需要修改代码:
package IODemo; import java.io.FileInputStream; import java.io.IOException; public class demo7 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("day04_code\\src\\IODemo\\a.txt"); byte []bytes=new byte[2]; int len; //一次读取多个字节数据,具体读多少,跟数组长度有关 //返回值:本次读取到了多少个字节数据 //a.txt内容是abcde len=fis.read(bytes); System.out.println(new String(bytes,0,len)); System.out.println(len); len=fis.read(bytes); System.out.println(new String(bytes,0,len)); System.out.println(len); len=fis.read(bytes); System.out.println(new String(bytes,0,len)); System.out.println(len); fis.close(); } } 控制台运行结果: ab 2 cd 2 e 1
根据上面的内容进行更改:
public class demo8 { public static void main(String[] args) throws IOException { FileInputStream fis=new FileInputStream("day04_code/src/IODemo/a.txt"); FileOutputStream fos=new FileOutputStream("day04_code/src/IODemo/copy2.txt",true); byte bytes[]=new byte[2]; int len; while((len=fis.read(bytes))!=-1){ fos.write(bytes,0,len); } fos.close(); fis.close(); } }
字节流和字符流的应用场景
字符集详解
记住几个点:
1.计算机中,任意数据都是以二进制的形式来存储
2.计算机中最小的存储单元是一个字节
3.ASCII字符集中,一个英文占一个字节
4.简体中文版windows,默认使用GBK字符集
5.GBK字符集完全兼容ASCII字符集
一个英文占一个字节,二进制第一位是0
一个中文占两个字节,二进制高位字节第一位是1
UniCode字符集:
Unicode字符集的UTF-8编码格式:
一个英文占一个字节,二进制第一位是0,转成十进制是正数
一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数
为什么会有乱码?
原因1:读取数据未读完整个汉字
原因2:编码和解码方式不同
如何不产生乱码:
1.不要用字节流读取文本文件
字符输入流
package IODemo;
import java.io.FileReader;
import java.io.IOException;
//用字符流读取文件
//read()
//字符流的底层也是字节流,默认是一个字节一个字节读取数据
//如果遇到中文就会一次会去读多个,GBK一次读两个字节,UTF-8一次读三个字节
public class demo10 {
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\c.txt");
int ch;
// while ((ch=fr.read())!=-1){//无参的read方法
// System.out.println((char)ch);
// }
char[]chars=new char[2];
int len;
while((len=fr.read(chars))!=-1){//有参的read的方法
System.out.println(new String(chars,0,len));
}
fr.close();
}
}
2.编码解码时使用同一个码表,同一个编码方式
package IODemo;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
//Idea默认编码方式是utf-8
public class demo9 {
public static void main(String[] args) throws UnsupportedEncodingException {
//编码
String str="ai你呦";
byte[]bytes1=str.getBytes();//UTF-8编码方式
System.out.println(Arrays.toString(bytes1));
byte[] bytes2=str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
//解码
String str2=new String(bytes1);//默认是UTF-8所以解码没有乱码
System.out.println(str2);
//如果解码方式和编码不一样
String str3=new String(bytes1,"GBK");//编码使用utf-8,解码则是GBK出现乱码,因为GBK汉字使用两个字节来表示
System.out.println(str3);
}
}
控制台输出:
[97, 105, -28, -67, -96, -27, -111, -90]
[97, 105, -60, -29, -33, -49]
ai你呦
ai浣犲懄
字符输出流
FileWriter构造方法
①创建字符输出流对象
细节1:参数是字符串表示的路径或者File对象都是可以
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关
②写数据
细节:如果write方法的参数是整数,但是实际写到本地文件中的是整数在字符集上对应的字符
package IODemo; import java.io.FileWriter; import java.io.IOException; public class demo11 { public static void main(String[] args) throws IOException { FileWriter fw=new FileWriter("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"); fw.write(25105); fw.close(); } }
a.txt内容:我
③释放资源
细节:每次使用完之后都要释放资源
字符流原理解析:
①创建字符输入流对象
底层:关联文件,并创建缓冲区[减少了文件读取的次数](长度为8192字节数组)
②读取数据:
底层:
1.判断缓冲区中是否有数据可以读取
2.缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件中也没有了数据,返回-1
3.缓冲区有数据:就从缓冲区中读取
空参的read方法:一次读取有一个字节,遇到中文一次读取多个字节,把字节解码并转成十进制返回
有参的read方法:把读取字节,解码,强转三步合并,强转之后的字符放到数组中
FileWriter
字节缓冲流
基本方法
方法名称 | 说明 |
public BufferedInputStream(InputStream is) | 把基本流包装成高级流,提高读取数据的性能 |
public BufferedOutputStream(OutputStream os) | 把基本流包装成高级流,提高写出数据的性能 |
原理:底层自带了长度为8192的缓冲区提高性能[减少了文件的读取次数,减少了io操作文件的次数]
特有方法:
字符缓冲输入流特有方法 | 说明 |
public String readLine() | 读取一行数据,如果没有数据可读,会返回NULL |
字符缓冲输出流特有方法 | 说明 |
public void newLine() | 跨平台的换行 |
package IODemo;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class demo18 {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\csb.txt"));
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\newcsb.txt"));
String str;
List<String> list=new ArrayList<String>();
while((str=br.readLine())!=null){ //readLine读取一行数据
list.add(str);
}
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//o1和o2的序号
//.是通配符 所以转义字符
int index1=Integer.parseInt(o1.split("\\.")[0]);
int index2=Integer.parseInt(o2.split("\\.")[0]);
return index1-index2;
}
});
for (int i = 0; i < list.size(); i++) {
bw.write(list.get(i));
bw.newLine();//跨平台换行
}
bw.close();
br.close();
}
}
转换流
转换流:是字符流和字节流之间的桥梁
作用:
.指定字符集读写数据(JDK11之后已淘汰)
.字节流想要使用字符流中的方法
应用场景:
比如我们要爬取一个网页上百家姓,我们要把网页上的内容爬取下来,然后再利用这则表达式进行具体的格式化,获取我们想要的数据。网页上的内容有文字还有视频等等,对于非纯文本的内容我们需要用字节流来进行操作,又因为我们是百家姓所以转为字符流更好,也可以解决乱码问题。比如下面的爬取代码:
public static String webCrawler(String net) throws IOException { StringBuilder sb=new StringBuilder(); URL url=new URL(net); URLConnection conn=url.openConnection(); // InputStreamReader isr=new InputStreamReader(conn.getInputStream());//字节流转换为字符流 int ch; while((ch=isr.read())!=-1){ sb.append((char)ch); } isr.close(); return sb.toString(); }
序列化流/对象操作输出流
可以把java的对象写到本地文件中
字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。
序列化操作:
一个对象想要序列化,必须满足两个条件
·该类必须java.io.Serializable接口,该接口是一个标记接口,不实现此接口的类不能进行序列化或反序列化,会抛出NotSerializableException
·如果类中有属性不需要可序列化,在该属性必须注明是瞬态的,即使用transient关键字修饰
·文件中的版本号要与javabean版本号一致,固定版本号serialVersionUID
public class Student implements Serializable { private static final long serialVersionUID = 464104926358852317L; //private static final long serialVersionUID=1L; private String name; private int age; //瞬态的 不会被序列化 private transient String address; private int sex;
下面则是创建进行序列化操作:[对Student对象进行序列化]
package IODemo;
import java.io.*;
public class demo22 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student stu=new Student("zhangsan",20,"南京",1);
//创建序列化流对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"));
oos.writeObject(stu);
oos.close();
}
}
重构对象进行反序列化
package IODemo;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class demo23 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//反序列化流
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\a.txt"));
Student stu1=(Student)ois.readObject();
System.out.println(stu1);
System.out.println(stu1.getAddress());
ois.close();
}
}
控制台输出:
Student{ name = zhangsan, age = 20, address = null}
null可以看到 进行序列化之后能够重构对象,因为address被transient修饰,所以没有被序列化成功,重构对象也获取不到
固定版本号:
如果我们创建了一个javaBean,初始版本号为1,对其进行序列化操作,会产生对应的序列化的代码版本1,但是实际开发中,业务可能会变,当我们修改了javaBean之后,那么javaBean对应的版本号则是2,进行反序列化操作时就会出现版本号不对应出现错误,怕怕出InvalidClassException异常。解决的办法就是:固定版本号
打印流
不能操作数据源,只能操作目的地(不能读,只能写)
字节打印流
字符打印流:字符流底层有缓冲区,想要自动刷新需要开启
public class demo27 { public static void main(String[] args) throws IOException { PrintWriter pw=new PrintWriter(new FileWriter("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\c.txt"),true); pw.println("字符打印流"); System.out.println("1"); //需要手动刷新 有缓冲区 //获取打印流对象,此打印流在虚拟机启动时候 由虚拟机创建,默认指向控制台 //特殊打印流,系统中的标准输出流 PrintStream ps=System.out; ps.print("123"); ps.close();//平常写的输出语句就是一个打印流 System.out.println("456"); } }控制台输出:
1
123
Process finished with exit code 0
压缩流
package IODemo;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class demo28 {
public static void main(String[] args) throws IOException {
//压缩流 解压缩流
//压缩流:文件中数据写进压缩包中 输出流
//1.创建一个File表示要解压的压缩包
File src=new File("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\tests.zip");
//2.创建一个File表示要解压的目的地
File dest=new File("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\tests");
unzip(src,dest);
}
public static void unzip(File src,File dest) throws IOException {
//解压的本质 把压缩包里面的文件或者文件夹读取出来,按照层架拷贝到目的地当中
ZipInputStream zip=new ZipInputStream(new FileInputStream(src));
//获取到压缩包里面每一个zip对象
ZipEntry entry;
while((entry=zip.getNextEntry())!=null){
//文件夹:需要在目的地处创建一个同样的文件夹
//文件:需要读取压缩包中的文件,把他存放到目的地dest文件夹,按层级目录存取
if(entry.isDirectory()){
//父级 子集
File file=new File(dest,entry.toString());
file.mkdirs();
}
else{
FileOutputStream fos=new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b=zip.read())!=-1){
fos.write(b);
}
fos.close();
zip.closeEntry();
}
}
zip.close();
}
}
压缩:
package IODemo;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class demo30 {
public static void main(String[] args) throws IOException {
//压缩整个文件夹
//1.确定压缩目的地 压缩来源
File src=new File("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\test");
File dest=new File("D:\\develop\\代码\\JAVA代码\\java基础\\java入门\\day04\\day04_code\\src\\IODemo\\");
ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(new File(dest,src.getName()+".zip")));
toZip(src,zos,src.getName());
zos.close();
}
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1.获取压缩流的对象
//2.压缩的位置
//3.压缩流内部的位置
//一层层压缩
File[] lists=src.listFiles();
for(File file:lists){
System.out.println(list);
//如果是文件夹
if(list.isDirectory()){
//创建ZIPEntry对象表示 压缩包里的每一个文件和文件夹
//递归
//他这里其实创建一个路径
toZip(file,zos,name+"\\"+file.getName());
}
else{
int b;
ZipEntry zipEntry=new ZipEntry(name+"\\"+file.getName());
zos.putNextEntry(zipEntry);
FileInputStream fis=new FileInputStream(file);
while((b=fis.read())!=-1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}
//如果是文件
}
}
}
常用工具包:
Commons.io
hutool
以上内容来自:IO流-01-IO流的概述_哔哩哔哩_bilibili
标签:java,字节,----,IO,import,new,public,String From: https://blog.csdn.net/qq_44766305/article/details/142492790