JAVA IO流基础
JAVA IO流基础
File类概述和构造方法
- File是文件和目录路径名的抽象表示
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅只是一个路径名而已,它是可以存在的也是可以不存在的,将来是要通过具体的操作把这个路径的内容转换成具体存在的
public static void main(String[] args) {
File f1 = new File("D:\\java study\\javva.txt");
System.out.println(f1);
File f2 = new File("D:\\java study","javva.txt");
System.out.println(f2);
File f3 = new File("D:\\java study");
File f4 = new File(f3,"javva.txt");
System.out.println(f4);
}
File类创建功能
- 如果文件不存在,则创建文件返回true,如果文件存在,则返回false
- 如果目录不存在,则创建目录返回true,如果目录存在,则返回false
public static void main(String[] args) throws IOException {
File f1 = new File("D:\\java study\\javva.txt");
System.out.println(f1.createNewFile());
File f2 = new File("D:\\java study\\javva");
System.out.println(f2.mkdir());
File f3 = new File("D:\\java study\\jjjj\\html");
System.out.println(f3.mkdirs());
File类删除功能
- 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件,例如D:\java study\java.txt
- 相对路径:必须使用取自其他路径名的信息进行解释,例如:myFile\java.txt
- 删除目录时,如果一个目录中有内容(目录,文件),不能直接删除,应该先删除目录中的内容,最后才删除目录
- 如果目录下有一堆文件,就用list()获得当前目录下的所有文件,通过判断是文件还是目录,继续循环删除就行了
File类判断和获取功能
递归
- 以编程的角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
- 递归策略只需少量的程序就可以描述出解题过程中所需要的多次重复运算
递归解决问题要找到两个出口:
- 递归出口:否则会出现内存溢出
- 递归规则,与原问题相似的规模较小的问题
public static void main(String[] args) {
System.out.println(f(5));
}
//5! 5*4*3*2*1
public static int f(int n){
if(n==1){
return 1;
}else{
return n*f(n-1);
}
}
递归遍历目录
public static void main(String[] args) {
File f1 = new File("D:\\java study\\digui");
getAllFilePath(f1);
}
public static void getAllFilePath(File f1) {
File[] files = f1.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()){
getAllFilePath(f);
}else{
System.out.println(f.getAbsolutePath());
}
}
}
}
字节流
IO流概述和分类
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称,也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的
- 常见的应用:文件复制,文件上传,下载
- IO流分类
- 按照数据的流向
- 输入流:读数据
- 输出流:写数据
- 按照数据类型来分
- 字节输入流;字节输出流
- 字符输入流;字节输出流
- 按照数据的流向
- 一般来说IO流的分类是按照数据类型来分的
- 如果数据通过window自带的记事本打开,可以读懂里面的内容就是用字符流,否则就使用字节流,如果你不知道该使用哪种类型的流,就使用字节流
字节流写数据
-
字节流抽象基类
-
InputStream:这个抽象类是表示输入流的所有类的父类
-
OutputStream:这个抽象类表示输出流的所有类的父类
-
子类名都是以其父类名作为其后缀
-
-
FileOutputStream:文件输出流用于将数据写入File
- FileOutputStream(String name):创建一个文件输出流,用指定的名称写入文件。
-
使用字节输出流写数据额步骤
-
创建字节输出流对象(
-
1,调用系统功能创建了文件 2,创建了字节输出流对象 3.让字节输出流对象指向创建好的文件
-
调用字节流输出对象的写数据方法
-
释放资源
-
-
public static void main(String[] args) throws IOException { //创建字节流对象 FileOutputStream f1 = new FileOutputStream("javvva.txt"); /* 做了三件事情 1,调用系统功能创建了文件 2,创建了字节输出流对象 3.让字节输出流对象指向创建好的文件 */ //write(int b) 将指定的字节写入该文件输出流中。 f1.write(97); //和IO流相关的最后都要释放资源 //close() 关闭此文件输出流并释放与此流关联的任何系统资源。 f1.close(); }
字节流写数据的3种方法
-
write(int b)
将指定的字节写入该文件输出流中。一次写一个字节数据 -
write(byte[] b)
写b.length
字节从指定的字节数组来此文件输出流。一次写一个字节数组数据 -
write(byte[] b, int off, int len)
写len
字节指定字节数组中的起始偏移off
此文件输出流。 一次写一个字节数组的部分数据
public static void main(String[] args) throws IOException {
FileOutputStream f1 = new FileOutputStream("javvva.txt");
f1.write(97);
f1.write(98);
f1.write(99);
f1.write(100);
f1.write(101);
// write(byte[] b)写b.length字节从指定的字节数组来此文件输出流。一次写一个字节数组数据
byte[] b1 = {97,98,99,100,101};
f1.write(b1);
//byte[] getBytes():返回字符串对应的字节数组
byte[] b2 = "abcde".getBytes();
f1.write(b2);
//write(byte[] b, int off, int len)写 len字节指定字节数组中的起始偏移off此文件输出流,一次写一个字节数组的部分数据
f1.write(b2,0,b2.length);
f1.close();
}
字节流写数据的两个小问题
-
字节流写完数据如何换行?
- 写完数据后,追加输入换行符
- windows:\r\n或\n
- linux:\n
- mac:\r
- 写完数据后,追加输入换行符
-
字节流写数据如何实现追加输入呢?
-
创建字节流文件时,在后面追加boolean判定true
-
public static void main(String[] args) throws IOException { //创建字节流对象 FileOutputStream f1 = new FileOutputStream("javvva.txt",true); for (int i = 0; i < 10; i++) { f1.write("hello".getBytes()); f1.write("\n".getBytes()); } f1.close(); }
-
字节流写数据加异常处理
- finally:在异常处理时提供finally块来执行所有清楚操作,比如说IO流中的释放资源
- 特点:被finally控制的语句一定会执行,除非JVM退出
public static void main(String[] args) {
FileOutputStream f1 = null;
try {
f1 = new FileOutputStream("Z:\\javvva.txt",true);
f1.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(f1 !=null) {\\由于上面时空指针异常所以这里需要加一个判断增加程序健壮性
try {
f1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节流读数据(一次读一个数据)
- FileInputStream类:从文件系统中的文件获取输入字节
- 构造方法:
FileInputStream(String name)
打开一个连接到一个实际的文件创建一个FileInputStream
,文件由文件系统中的路径名name
命名。
- 使用字节输入流读数据的步骤
- 1,创建字节输入流对象
- 2,调用字节输入流对象的读数据方法
- 3,释放资源
public static void main(String[] args) throws IOException {
FileInputStream f11 = new FileInputStream("javvva.txt");
// //第一次读取数据
// int by = f11.read();
// System.out.println(by);
// System.out.println((char) by);
// //第二次读取数据
// by = f11.read();
// System.out.println(by);
// System.out.println((char) by);
//
// //如果到达文件的末尾没有数据了,继续读取则会返回-1
// int rr = f11.read();
// while (rr != -1) {
// System.out.print((char) rr);
// rr = f11.read();
// }
//优化上面
int rr;
while ((rr = f11.read()) != -1) {
System.out.print((char) rr);//能读取到换行这里不需要println
}
f11.close();
//字节流读数据的标准代码
}
字节流复制文本文件
public static void main(String[] args) throws IOException {
FileInputStream f1 = new FileInputStream("javvva.txt");
FileOutputStream f2 = new FileOutputStream("javvva1.txt");
//同文件夹则不能同名
int rr;
while ((rr = f1.read()) != -1) {
f2.write(rr);
}
f1.close();
f2.close();
}
字节流读数据(一次读一个字节数组数据)
public static void main(String[] args) throws IOException {
FileInputStream f1 = new FileInputStream("javvva.txt");
//数据为
/*
Hello
hello
*/
//read(byte[] b) 读到 b.length从输入流到字节数组数据字节。
byte[] b1 = new byte[5];
int rr = f1.read(b1);
//这里的read函数,就是把读到的数据存入数组b1中,而不是读取数组中的数据。f1最多能读到的数据个数为数组的长度
//而rr是判断b1数组的长度,若没数据则b1没长度则为-1
System.out.println(rr);
System.out.println(new String(b1, 0, rr));
//读第二次
rr = f1.read(b1);
System.out.println(rr);
System.out.println(new String(b1, 0, rr));//结果为Hel
//读第三次
rr = f1.read(b1);
System.out.println(rr);
//System.out.println(new String(b1));
/*
Hello\r\n
Hello\r\n
第一次:Hello
第二次:\r\nHel
第三次:lo\r\n
l
从始至终就只有bys一个数组,最后读取到的只有4个数据替换了第二次b1数组的前四个位置,最后的l则没有变化
为了不显示这样的数组只读一部分的话
用以下改进
*/
System.out.println(new String(b1, 0, rr));
//以上所有的输出都应该改成offset,这样数组长度设定就可以比较灵活且设定较大时(大于数据的长度)可以一次读完
//而设定的数组长度小于数据长度时,则可以避免上面的未能完全覆盖的情况,同时也能判定返回值是否为-1优化循环
//优化改进后的代码如下
byte[] b2 = new byte[1024];//1024和其整数倍
int len;
while ((len = f1.read(b2)) != -1) {//读取的数据已经没有的情况,数据长度则返回-1
System.out.print(new String(b2,0,len));//不用offset设定输出范围的话,会出现剩余数组空码
}
f1.close();
}
复制图片
public static void main(String[] args) throws IOException {
FileInputStream f1 = new FileInputStream("D:\\java study\\pic\\fuzhi.png");
FileOutputStream f2 = new FileOutputStream("fuzhi.png");
byte[] b1 = new byte[1024];
int len;
while ((len = f1.read(b1)) != -1) {
f2.write(b1,0,len);
}
f1.close();
f2.close();
}
字节缓冲流
-
构造方法
BufferedInputStream(InputStream in)
创建一个BufferedInputStream
和保存它的参数,输入流in
,供以后使用。 当BufferedInputStream
创建,内部缓冲区创建数组。从流中读取字节或跳过,内部缓冲区将根据需要从包含的输入流重新填充,一次很多字节。 -
构造方法
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,将数据写入到指定的基本输出流中。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用 -
输出目标的输出流将缓冲区中的内容输出到输出目标中,从而相比我们直接使用 OutputStream 的代码减少了该类中向输出流写入数据的相关方法的调用,提高了程序的性能和读写效率
-
这里指的就是计算机组成缓冲区的知识,简略而说每次读写数据需要cpu进行服务,能不能先把数据写入缓冲区再一次性输出,缓冲区就是buffer
- 字节缓冲流只仅仅提供缓冲区,而真正的读写数据还是得依靠基本的字节流对象进行操作
public static void main(String[] args) throws IOException {
// FileOutputStream f1 = new FileOutputStream("javvva.txt");
// BufferedOutputStream b1 = new BufferedOutputStream(f1);
// 可以合并为以下
BufferedOutputStream b1 = new BufferedOutputStream(new FileOutputStream("javvva.txt"));
b1.write("Hello\n".getBytes());
b1.write("World\n".getBytes());
b1.close();
BufferedInputStream b2 = new BufferedInputStream(new FileInputStream("javvva.txt"));
//一次读取一个字节数据
int by;
while ((by = b2.read()) != -1) {
System.out.print((char) by);
}
//一次读取一个字节数组数据
byte[] bb = new byte[1024];
int len;
while ((len = b2.read(bb)) != -1) {
System.out.println(new String(bb, 0, len));
}
b2.close();
}
字节流复制视频
public static void main(String[] args) throws IOException{
//记录开始时间
long starTime = System.currentTimeMillis();
//视频大小:17.5 MB (18,351,601 字节)
method1();//基本字节流读写一个字节 总共用时:135975毫秒
method2();//基本字节流读写一个字节数组 总共用时:183毫秒
method3();////字节缓冲流读写一个字节 总共用时:451毫秒
method4();////字节缓冲流读写一个字节数组 总共用时:43毫秒
long endTime = System.currentTimeMillis();
System.out.println("总共用时:" + (endTime - starTime) + "毫秒");
}
public static void method4() throws IOException {//字节缓冲流读写一个字节数组
BufferedInputStream bin = new BufferedInputStream(new FileInputStream("D:\\study video\\1122.mp4"));
BufferedOutputStream bou = new BufferedOutputStream(new FileOutputStream("1122.mp4"));
byte[] by = new byte[1024];
int len;
while ((len = bin.read(by))!= -1){
bou.write(by,0,len);
}
bin.close();
bou.close();
}
public static void method3() throws IOException {//字节缓冲流读写一个字节
BufferedInputStream bin = new BufferedInputStream(new FileInputStream("D:\\study video\\1122.mp4"));
BufferedOutputStream bou = new BufferedOutputStream(new FileOutputStream("1122.mp4"));
int by;
while ((by = bin.read())!= -1){
bou.write(by);
}
bin.close();
bou.close();
}
public static void method2() throws IOException {//基本字节流读写一个字节数组
FileInputStream fin = new FileInputStream("D:\\study video\\1122.mp4");
FileOutputStream fou = new FileOutputStream("1122.mp4");
byte[] by = new byte[1024];
int len;
while ((len = fin.read(by))!= -1){
fou.write(by,0,len);
}
fin.close();
fou.close();
}
public static void method1() throws IOException {//基本字节流读写一个字节
FileInputStream fin = new FileInputStream("D:\\study video\\1122.mp4");
FileOutputStream fou = new FileOutputStream("1122.mp4");
int by;
while ((by = fin.read())!= -1){
fou.write(by);
}
fin.close();
fou.close();
}
字符流
为什么会出现字符流
-
一个汉字存储
- 如果是GBK编码,占用2个字节
- 如果是UTF-8编码,占用3个字节
-
String类的对象方法getBytes默认使用UTF-8编码来获取汉字的字节,但无论哪种编码储存第一个字节都为负数,所以在使用字节流读取字节并拼接时系统会根据编码类型来判断负数为汉字,GBK2个字节一拼,UTF-8三个字节一拼
-
但由于字节流操作中文不是特别方便,所以java提供了字符流
- 字符流 = 字节流 + 编码表(底层还是字节流)
编码表
-
计算机中储存的信息都是用二进制表示,看到的汉字英文等字符都是二进制转换后的结果
-
按照某种规则将字符存储到计算机中,称之为编码,反之按照某种规则解析显示为解码
,按照A编码存储就必须按照A编码解析,如果不按指定编码解析,则会出现乱码
-
字符集
- 是一个系统支持的所有字符的集合,包括各个国家文字,标点符号,图形符号,数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码,常见字符集有ASCII字符集,GBXXX字符集,Unicode字符集
-
ASCII字符集
- 美国信息交换标准代码,基于拉丁字母的一套电脑编码系统,主要显示现代英文,用7位表示一个字符,共128字符,扩展字符集用8位表示一个字符,共256字符,方便支持欧洲常用字符,是一个系统支持的所有字符的集合
-
GBXXX字符集
- GB2312:简体中文码表,小于127的字符意义与原来相同,但大于127的字符连在一起就表示一个汉字,包含了7000多个简体汉字,此外数学符号,罗马希腊的字母,日文的假名等都编进去了,连在ASCII里本来就有的数字标点字母等都通通编了两个字节长的编码,这就是常说的“全角”字符,原来在127号以下的叫“半角”字符
- GBK:最常用的中文码表,在GB2312的标准基础上的扩展规范,使用了双字节的编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
- GB18030:最新的中文码表,收录汉字70244个,采用多字节编码,每个字可以由1个,2个或者4个字节组成,支持中国国内少数民族的文字,同时支持繁体汉字和日韩汉字
-
Unicode字符集
- 为表达任意语言的任意字符而涉及,是业界的一种标准,标准万国码,他最多使用4个字节的数字来表达每个字符,符号,或者文字,有三种编码方案,UTF-8,UTF-16和UTF32,最常用的为UTF-8编码
- UTF-8编码:可以用来表示Unicode标准中的任意字符,它是电子邮件,网页以及其他储存或传输文字的应用中,优先采用的编码,互联网工程工作小组(IEIT)要求所有的互联网协议都必须支持UTF-8编码,它使用1-4个字节为每个字符编码
- 128个US-ASCII字符,只需要一个字节编码
- 拉丁文等字符,需要2个字节编码
- 大部分常用字(含中文),使用3个字节编码
- 其他极少使用的Unicode辅助字符,使用4字节编码
-
小结:采用何种规则字符编码,就要采用对应规则解码,否则就会出现乱码
字符串中的解码编码问题
public static void main(String[] args) throws UnsupportedEncodingException {
//定义一个字符串
String s = "中国";
byte[] by = s.getBytes();//[-28, -72, -83, -27, -101, -67]
// byte[] by = s.getBytes("UTF-8");//[-28, -72, -83, -27, -101, -67]
//getBytes默认采用UTF-8的编码
// byte[] by = s.getBytes("GBK");//[-42, -48, -71, -6]
System.out.println(Arrays.toString(by));
//解码
// String ss = new String(by);//默认解码位UTF-8,结果为中国
String ss = new String(by,"GBK");//结果为乱码
System.out.println(ss);
}
字符流中的编码解码问题
- 字符流抽象父类
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
- 字符流中编码解码问题相关的两个类
- InputStreamReader
- 是字节流到字符流的桥梁,它读取字节,并使用指定的charset将其解码为字符,它使用的字符集可以由名称指定
- OutputStreamWriter
- 是字符流到字节流的桥梁,使用默认或指定的charset将写入的字符编码为字节
- InputStreamReader
public static void main(String[] args) throws IOException {
// OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("out.txt"));
// OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("out.txt"),"GBK");
// opw.write("第七史诗");
//
// opw.close();
InputStreamReader ipr = new InputStreamReader(new FileInputStream("安装必读.txt"),"GBK");
int ch;
while ((ch = ipr.read())!= -1){
System.out.print((char)ch);
}
ipr.close();
}
字符流写数据的5种方式
- 补充
- flush() :刷新流,还可以写数据
- close():关闭流,释放资源,但是在关闭前会先刷新流,一旦关闭,就不能再写数据
public static void main(String[] args) throws IOException {
OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("ooo.txt"));
// 写一个字符
opw.write(97);
opw.flush();//不刷新的话运行则不会写入,会在缓冲区,close()也有一样的效果,先刷新再关闭
opw.write(98);
opw.flush();
//写一个字符数组
char[] chs = {'a','b','c','d','e'};
opw.write(chs);
//写入字符数组的一部分
opw.write(chs,0, chs.length);
opw.write(chs,1, 4);
//写入一个字符串
opw.write("abcde");
//写入一个字符串的一部分
opw.write("abced",0,"abcde".length());
opw.close();
}
字符流读数据的两种方法
public static void main(String[] args) throws IOException {
InputStreamReader ipr = new InputStreamReader(new FileInputStream("安装必读.txt"), "GBK");
//int read():一次读一个字符数据
int ch;
while ((ch = ipr.read()) != -1) {
System.out.print((char)ch);
}
//一次读一个字符数组
char[] chs = new char[1024];
int len;
while((len= ipr.read(chs))!= -1){
System.out.println(new String(chs,0,len));
}
ipr.close();
}
字符流复制java文件以及改进版
public static void main(String[] args) throws IOException {
InputStreamReader ipr = new InputStreamReader(new FileInputStream("JavaIoDemo.java"));
OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("copy.java"));
char[] chs = new char[1024];
int len;
while ((len = ipr.read(chs,0, chs.length))!= -1){
opw.write(chs);
}
ipr.close();
opw.close();
}
若是无编码转码的需求则可以使用更简洁的字符流读写类
- FileReader(String fileName)
- FileWriter(String fileName)
数据源:xxx.java----读数据----Reader(由于是抽象类)----InputStreamReader(自带编码解码转换)----FileReader(无编码解码需求则可以使用更加简洁)
目的地:xxxcopy.java----写数据----Writer(由于是抽象类)----OutputStreamWriter(自带编码解码转换)----FileWriter(无编码解码需求则可以使用更加简洁)
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("JavaIoDemo.java");
FileWriter fw = new FileWriter("copy.java");
//一次读写一个字符
int len;
while ((len = fr.read())!= -1){
fw.write(len);
}
//一次读写一个字符组
char[] chs = new char[1024];
int len1;
while ((len1 = fr.read(chs,0, chs.length))!= -1){
fw.write(chs);
}
fr.close();
fw.close();
}
字符缓冲流
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("ooo.txt"));
bw.write("hello\n");
bw.write("world\n");
bw.close();
BufferedReader br = new BufferedReader(new FileReader("ooo.txt"));
char[] chs = new char[1024];
int len;
while((len = br.read(chs))!= -1){
System.out.println(new String(chs,0,len));
}
br.close();
}
字符缓冲流复制文件可将上面的普通字符流复制代码加入缓冲流即可
字符缓冲流特有功能
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("ooo.txt"));
for (int i = 0; i < 10; i++) {
bw.write("hello" + i);
bw.newLine();//不需要输入\n也能实现换行
bw.flush();
}
bw.close();
BufferedReader br = new BufferedReader(new FileReader("ooo.txt"));
//读一行字
String line = br.readLine();
System.out.println(line);
line = br.readLine();
System.out.println(line);
line = br.readLine();
System.out.println(line);
//由于只有两行文字,第三行则会返回null
//通过循环改进
String line1;
while ((line1 = br.readLine()) != null) {
System.out.println(line1);//不读换行等符号所以需要ln换行
}
br.close();
}
复制java文件(使用字符缓冲流特有功能改进)
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("JavaIoDemo.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("copy.java"));
//使用readline读写数据
String line;
while ((line = br.readLine())!= null){
bw.write(line);//因为readline方法不会读分隔换行符,会全部写在一行上,需要手动换行
bw.newLine();//换行
bw.flush();//刷新
}
bw.close();
br.close();
}
IO流小结
- 由于这两个流都是抽象类,我们使用以下各两种的子类
- 接下来是字符流
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术