I:input O:output 流:像水流一样传输数据
用于读写数据的(可以读写文件,或网络中的数据…),存储和读写数据的解决方案。
比如,对游戏历史最高分进行记录,那么就需要用到IO流在硬盘中进行读写。
按流的方向分为:输入流(读取),输出流(写出)。
按操作文件的类型:字节流(所有类型的文件),字符流(纯文本文件)。
纯文本文件:Windows自带的记事本打开能读懂的,是指只包含字符信息(如字母、数字、标点符号等),而不包含格式化信息(如字体、字号、颜色、段落格式等)的文件。
比如:.md文件、.txt记事本文件。
IO流的体系结构
但因为底下的都是抽象类,没办法直接创建他们的对象,所以要看它们的子类,子类该怎么取名,以InputStream为例,先把父类名拿出来,我想从本地文件里读取数据,文件名为File,两则一结合,FileInputStream操作本地文件的字节输入流,第一个单词File表示它的作用,第二个单词InputStream表示它的继承结构。
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中,是字节流的基本流。
书写步骤:
细节1:参数是字符串表示的路径或者是File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,则会清空文件
//1.创建对象
FileOutputStream fos = new FileOutputStream("D:\\java\\basic-code\\myio\\src\\a.txt");
细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符。
//2.输入数据
fos.write(97);
每次使用完流之后都要释放资源,释放资源是为了解决资源的占用。
//3,释放资源
fos.close();
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
第一个方法:
fos.write(97);
第二个方法:
byte[] bytes = {97,98,99,100,101};
fos.write(bytes);//abcde
第三个方法:
参数一:数组
参数二:起始索引
参数三:长度
fos.write(bytes,1,2)//b c
换行写
示例:
//2.输入数据
String str = "hahaxixi";
byte[] byte1 = str.getBytes();
fos.write(byte1);
//再次写出一个换行符就可以了
String wrap = "\r\n";
byte[] byte3 = wrap.getBytes();
fos.write(byte3);
//输入数据
String str2 = "111";
byte[] byte2 = str2.getBytes();
fos.write(byte2);
//3,释放资源
fos.close();
//hahaxixi
//111
换行符:
Windows:\r\n
Linux:\n
Mac:\r
细节:在windows操作系统当中,java对回车换行进行了优化。虽然完整的是\backslash r \backslash n ,但是我们写其中一个\r或者\n.java也可以实现换行,因为java在底层会补全。建议:不要省略,还是写全了。
续写
如果想要续写,打开续写开关即可
开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件
手动传递true:表示打开续写,此时创建对象不会清空文件
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。
书写步骤:
细节:如果文件不存在,则直接报错。
FileInputStream fis = new FileInputStream("D:\\java\\basic-code\\myio\\src\\a.txt");
输出的结果为字母a的ASCll码值,如果想输出为字符,则强转类型。
细节1: 一次读一个字节,读出来的是数据在ASCII上对应的数字
细节2:读到文件末尾了,read方法返回-1。
int b1 = fis.read();
System.out.println(b1);//97
细节:每次使用完流之后都要释放资源
fis.close();
核心思想:边拷边写
while((b = fis.read()) != -1){
fos.write(b);
}
但是FileInputStream一次读写一个字节(速度慢)
怎样读取多个字节方法
public int read(byte[] buffer);
注:一次读一个字节数组的数据,每次读取会尽可能把数组装满
1024的整数倍
try…catch…finally异常处理->捕获异常
没有这个文件,读取不到数据就是空指针异常
接口:AutoCloseable->特定的情况下,可以自动释放资源
jdk9方案
创建流对象1;
创建流对象2;
try(流1;流2){
可能出现的异常代码;
}catch(异常类名 变量名){
异常的处理代码;
}
字符流->会出现乱码
ASCll字符集
计算机的存储规则(英文)
a -> 110 0001(97) -> (编码)0110 0001二进制
ASCll编码规则,前面补0,补齐8位。
Windows系统默认使用的就是GBK
字符集
Unicode:万国码(包含大多数国家)
UTF-8编码规则:用1-4个字节保存
0xxxxxxx(英文)
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx(汉字)
那么UTF-8是一个字符集吗?
UTF-8是Unicode字符集的一种编码方式
为什么会乱码?
字符流:一次只能读取一个字节
如何不产生乱码?
为什么拷贝不会出现乱码?
拷贝 字符源没有丢失,记事本在读取的时候,要用到的数据集跟编码方式跟数据源保持一致,这样就不会报错。
Java中编码的构造方法
public byte[] getBytes();//默认
public byte[] getBytes(String charsetName);//指定
Java中编码的成员方法
String(byte[] bytes);
String(byte[] bytes,String charsetName);
字符流的底层就是字节流
字符流=字节流+字符集
特点:
输入流:一次读一个字节,遇到中文时,一次读多个字节
输出流:底层会把数据指定的编码方式进行编码,变成字节再写到文件中。
底层:关联文件,并创建缓冲区(长度为8192的字节数组)
底层:
判断缓冲区中是否有数据可以读取
缓冲区没有数据,就从文件中获取数据装到缓冲区中,每次尽可能装满缓冲区,如果文件中也没有数据,返回-1.
缓冲区有数据,就从缓冲区中读取
空参的read方法:一次读取一个字节,遇到中文一次读取多个字节,把字节解码转成二进制返回。
有参的read方法:把读码,解码,强转合并,强转之后的字符放到数组中
bb:缓冲区
若超出8192个内容,剩余的内容会再次进入缓冲区,(没有理解清楚,继续补充)
情况一:装满了
情况二:手动刷新(flash)
情况三:close
flush和close方法
public void flush();//将缓冲区中的数据,刷新到本地
public void close();释放资源/关流
解法一:
BufferedReader逐行读取文件内容,提供readLinst()方法,用collections.sort排序
解法二:
用TreeMap集合存储,他有自动排序功能,写出就用Entry
字节流的基本流:一次读取一个字节数组
//创建对象
FileInputStream fis = new FileInputStream("D:\\图集\\OIP-C (1).jpg");
FileOutputStream fos = new FileOutputStream("D:\\java\\basic-code\\myio\\src\\视频.txt");
//边读边写
int len;
byte[] bytes = new byte[1024*1024*5];
while((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
//释放资源
fos.close();
fis.close();
软件运行次序
实现一个验证程序运行次数的程序
当程序运行超过3次时给出提示”本软件只能免费使用3次,欢迎您注册会员后使用“,一开始会想到在程序中添加计数器,但它只存在内存中,程序重启后回归0,所以必须把它存在文件中。
用BufferedReader读取一行
字符转换输入流:InputStreamReader
将字节流转换为字符流存入内存
字符转换输出流:OutputStreamWriter
将字符流转换为字节流存入目的地
字符流和字节流之间的桥梁
原来的方式:利用转换流按照指定字符编码读取(了解)
//1.创建对象并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("myio\\gbkfile.txt"),"GBK");
//2.读写数据
int ch;
while((ch = isr.read()) != -1){
System.out.println((char)ch);
}
jdk11之后FileReader添加了一个新的构造,可以指定字符编码
FileReader fr = new FileReader("myio\\b.txt", Charset.forName("gbk"));
int ch;
while((ch = fr.read()) != -1){
System.out.print((char)ch);
}
fr.close();
利用转换流按照指定字符编码写出(了解)
OutputStreamWriter osw = new OutputStreamWriter(new fileoutputStream("myio\\b.txt"),"GBk");
osw.write("你好你好");
osw.close();
替代方案
FileWriter fw = new FileWriter("myio\\b.txt",Charset.forName("GBK"));
fw.writer("你好你好");
fw.close();
练习:将本地文件中的GBK文件,转成UTF-8
jdk11以前的版本
InputStreamReader isr = new InputStreamReader(new FileInputStream("myio\\b.txt"),"GBK");
OutputStreamWriter osw = new OutputStreamWriter(new fileoutputStream("myio\\b.txt"),"UTF-8");
int b;
while((b = isr.read()) != -1){
osw.write(b);
}
osw.close();
isr.close();
替代版本
FileReader fr = new FileReader("myio\\b.txt", Charset.forName("gbk"));
FileWriter fw = new
FileWriter("myio\\b.txt",Charset.forName("UTF-8"));
int b;
while((b = fr.read()) != -1){
fw.write(b);
}
fw.close();
fr.close();
练习:利用字符串读取文件中的数据,每次读一整行,而且不会出现乱码
BufferedReader br = new BufferedReader(new InputStreeamReader(new FileInputStream("myio\\b.txt")));
String line;
while((line = br.readLine()) != -1){
System.out.println(line);
}
br.close;
序列化流:ObjectOutputStream
序列化流
可以把Java中的对象写到本地文件中,所以我们要先创建对象,再将对象通过序列化流写到文件中,写到文件中的对象,我马上看不懂的,所以需要通过反序列化流读取。
应用场景:
在游戏中,每个角色都有各自的属性值,如果只是单纯的将它们写到文件中,很容易被其他人修改,为了避免这一操作,我们就要用到序列化流将其变成看不懂的文本,这样别人就无法修改角色的属性。
序列化流又称对象操作输出流
构造方法:
关联基本流,将基本流包装成高级流
public ObjectOutputStream(OutputStream out);
成员方法:
把对象序列化写到文件当中去
public final void writeObject(object obj);
代码展示
public static void main(String[] args) throws IOException {
//序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myio\\src\\c.txt"));
oos.writeObject(new Student("张三",23,"男"));
oos.close();
}
直接写出,会出现NotSerializableException异常
解决方案:需要让JavaBean类实现Serializable接口,实现完了之后不需要重写它的抽象类,查看源码,发现它里面没有抽象方法,所以我们可以认为它是标记型接口,一旦实现了这个接口,那么就表示当前的Student类可以被序列化。
反序列化流:ObjectInputStream
可以把序列化到本地文件中的对象,读取到程序中来
构造方法
public ObjectInputStream(InputStream out);//把基本流转换为高级流
成员方法
public Object readObject();//把序列化到文件中的对象,读取到程序中
代码展示
public static void main(String[] args) throws IOException, ClassNotFoundException {
//反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myio\\src\\c.txt"));
Student o = (Student)ois.readObject();
System.out.println(o);
ois.close();
}
若修改javaBean类里面的属性,代码会报错,是因为java会根据类里面的内容计算出一个long类型的序列号,就相当于版本号,它会与对象一起写到本地文件中,如果对JavaBean类对象进行修改,版本号也会发生变化,文件中的版本号就与它不匹配,就会报错。所以为了不让版本号发生变化,我们可以定义一个版本号。
private static final long serialVersionUID = -6357601841666449654L;
细节汇总:
分类:打印流一般指PrintStream,PrintWrite两个类。
特点一:打印流只操作文件目的地,不操作数据源
特点二:特有的写出方法可以实现,数据原样写出
特点三:特有的写出方法,可以实现自动刷新,自动换行
无缓冲区,开不开自动刷新都一样。
构造方法:
public PrintStream(OutputStream/File/String);//关联字节输出流
public PrintStream(String fileName, Charset charset);//指定字符编码
成员方法
public void write(int b);
public void println(xxx,xxx);
public void print(xxx,xxx);
字符流底层有缓冲区,想要就可以自动打开。
构造方法
public PrintWriter(write/File/String);//关联字节输出流/文件/文件路径
public PrintWriter(String fileName,Charset charset);//指定字符编码
public PrintWriter(Writer w,boolean autoFlush);//自动刷新(字符流底层有缓冲区,想要自动刷新就必须打开)
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset);// 指定字符编码且自动刷新
特有方法
public void println(Xxx xx);//打印任意数据,自动刷新,自动换行
public void print(Xxx xx);//打印任意数据,不换行
public void printf(String format, Object…args);//带有占位符的打印语句,不换行
应用场景
获取打印流的对象,此打印流在虚拟机启动的时候,由虚拟机创建,默认指向控制台。
系统的标准输出流,是不能关闭的,在系统中是唯一的。
PrintStream ps = System.out;
//调用打印流中的方法println
ps.println("123");
是专门用于读取ZIP格式压缩文件的输入流。它允许逐个读取ZIP文件中的条目(ZipEntry
),并解压每个条目到指定位置。
构造方法:
ZipInputStream(InputStream in);//创建一个新的ZIP输入流
ZipInputStream(InputStream in,Charset charset);//创建一个新的ZIP输入流
InputStream
,通常是FileInputStream
。getNextEntry()
方法逐个读取ZIP文件中的条目(ZipEntry
)。ZipEntry
可以是文件或目录。read()
方法读取内容并写入到目标文件。closeEntry()
关闭当前条目,继续读取下一个条目。ZipEntry
getName()
获取条目的名称(路径)。isDirectory()
判断是否为目录。代码演示:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipStreamDemo1 {
public static void main(String[] args) throws IOException {
//1.创建一个File表示要解压的的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");
//调用方法
unzip(src,dest);
}
private static void unzip(File src, File dest) throws IOException {
//解压的本职:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if(entry.isDirectory()){
//文件夹:需要目的地dest处创建一个同样的文件夹
File file = new File(dest, entry.toString());
file.mkdirs();
}else{
//文件:需要读取压缩包中的文件,并把他存放在目的地dest文件夹中
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();
}
}
是 Java 中用于创建 ZIP 文件的输出流。它允许你将多个文件或目录打包成一个 ZIP 文件,并支持压缩和设置文件属性等功能。
构造方法:
ZipOutputStream(OutputStream out);//创建一个新的ZIP输出流
ZipOutputStream(OutputStream out, Charset charset);//创建一个新的ZIP输出流
OutputStream
,通常是 FileOutputStream
。putNextEntry(ZipEntry entry)
方法添加一个新的条目(文件或目录)。write(byte[] b, int off, int len)
方法写入条目的内容。closeEntry()
方法完成当前条目的写入。close()
方法关闭 ZipOutputStream
,完成 ZIP 文件的创建。代码演示(将单个文件打包成一个压缩包):
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipStreamDemo2 {
public static void main(String[] args) throws IOException {
//将aaa.txt文件打包成一个压缩包
//1.创建File对象表示要压缩的文件
File src = new File("D:\\b.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3调用方法
toZip(src,dest);
}
private static void toZip(File src, File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
ZipEntry entry = new ZipEntry("b.txt");
//3.把ZipEntry对象放在压缩包中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包中
FileInputStream fis = new FileInputStream(src);
int b;
while ((b = fis.read()) != -1) {
zos.write(b);
}
zos.closeEntry();
zos.close();
}
}