目录
🥅缓冲流
(1)带有缓冲区的字符输入流BufferedReader;使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组;自带缓冲!如果使用字节流进行读写数据时同样是借助byte数组[]进行文件复制,但是缓冲流BufferedInputStream的读写速度远大于文件流FileInputStream。
(2)读取数据时,可以一次性读取一行,调用readLine()方法。
(3)这里只讲解字符缓冲流: java.io.BufferedReader 和 java.io.BufferedWriter;字节缓冲流使用方法是一样的。
(4)步骤:
第一步:先创建一个字符输入流FileReader;
第二步:创建一个BufferedReader包装流,调用有参构造方法把上面的字符输入流传进去;
第三步:调用包装流的readLine()方法,一次读取一行;返回的是一个字符串;
第四步:关闭流,只需要关闭最外层的流即可;里面的节点流会自动关闭。
(5)BufferedReader和BufferedWriter都是包装流!
注:字符包装流有一个readLine方法可以读取一行,但是不带换行;可以“\n”进行换行,或者调用newLine()方法进行换行!
1.字符输入流BufferedReader
package com.bjpowernode.java.io;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
public class BufferedReaderTest01 {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("file");
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
// 外部负责包装的这个流,叫做:包装流或者叫做:处理流。
// FileReader就是一个节点流。BufferedReader就是包装流/处理流。
BufferedReader br = new BufferedReader(reader); // 只能传字符流
// 优点:一次能读一行;但不带换行符!
// br.readLine()返回的是一个字符串
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
// 关闭流,只需要关闭最外层流就行,里面的节点流会自动关闭。
br.close();
}
}
2.字符输出流BufferedWriter
(1)缓冲流本质上和文件专属流的用法是很类似的,就是文件流借助于包装流,进行包装,这样读取和写入数据就不需要借助char数组或者byte数组了,当然也可以使用数组;直接调用readLine()方法就可以完成对应的写入一行或者读出一行到操作!
(2)包装流BufferedWriter写入数据有一个弊端,没有换行操作,需要我们手动添加换行。
package com.bjpowernode.java.io;
import java.io.*;
public class BufferedWriterTest01 {
public static void main(String[] args) throws IOException {
// 创建带有缓冲区的字符输出流
BufferedWriter writer = new BufferedWriter(new FileWriter("file"));
// 开始写
writer.write("hello world!");
writer.write("\n");
writer.write("hello kitty!");
// 刷新
writer.flush();
// 关闭最外层
writer.close();
}
}
总结:建议文件比较大时使用缓冲流,相对于文件流,明显增加读取的时间!
🥅转换流
转换流OutputStreamWriter和InputStreamReader都是包装流。
引入情况1:
使用
FileReader
读取项目中的文本文件。由于IDEA设置中针对项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时,如果Windows系统默认的是GBK编码,则读入内存中会出现乱码。
引入情况2:
针对文本文件,现在使用一个字节流进行数据的读入,希望将数据显示在控制台上。此时针对包含中文的文本数据,可能会出现乱码。
作用:转换流是字节与字符间的桥梁!
具体来说:
构造器
InputStreamReader(InputStream in): 创建一个使用默认字符集UTF-8的字符流。
InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流UTF-8或者gbk。
API
InputStreamReader:将一个输入型的字节流转换为输入型的字符流。这里的编码(存储到内存)和解码(读取到控制台)要保持一致,比如:存储时使用UTF-8,那么解码使也要使用UTF-8。
OutputStreamWriter:将一个输出型的字符流转换为输出型的字节流。 先从文件从内存中读取出来,两者要保持相同的编码方式(如都是gbk),然后将一个gbk编码的文件转换为UTF-8编码的文件。
关于字符集的理解
在存储的文件中的字符:
①ascii:主要用来存储a、b、c等英文字符和1、2、3、常用的标点符号。每个字符占用1个字节。
②iso-8859-1:了解,每个字符占用1个字节。向下兼容ascii。
③gbk:用来存储中文简体繁体、a、b、c等英文字符和1、2、3、常用的标点符号等字符。
中文字符使用2个字节存储的。向下兼容ascii,意味着英文字符、1、2、3、标点符号仍使用1个字节。④utf-8:可以用来存储世界范围内主要的语言的所有的字符。使用1-4个不等的字节表示一个字符。中文字符使用3个字节存储的。向下兼容ascii,意味着英文字符、1、2、3、标点符号仍使用1个字节。
在内存中的字符:
一个字符(char)占用2个字节。在内存中使用的字符集称为Unicode字符集。
1.OutputStreamWriter
(1)OutputStreamWriter:转换流;把一个字节输出流转换成字符输出流!
(2)我们可能遇到以下场景BufferedWriter流里面传的就是对应的字符输出流FileWriter流;但是我们如果只有对应的字节输出流FileOutputStream流怎么办?此时就需要使用转换流,先把字节输出流FileOutputStream转换成对应的字符输出流FileWriter。
package com.bjpowernode.java.io;
import java.io.*;
public class BufferedWriterTest01 {
public static void main(String[] args) throws IOException {
// 创建带有缓冲区的字符输出流
BufferedWriter writer = new BufferedWriter(new FileWriter("file"));
// 如果想要使用字节输出流,还是需要OutputStreamWriter进行流的转换
// 这样才能创建带有缓冲区的字符输出流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file"));
BufferedWriter writer1 = new BufferedWriter(osw);
// 合并在一起
BufferedWriter writer1 = new BufferedWriter(new OutputStreamWriter
(new FileOutputStream("file")));
// 开始写
writer.write("hello world!");
writer.write("\n");
writer.write("hello kitty!");
// 刷新
writer.flush();
// 关闭最外层(关闭的时候实际上也会进行刷新)
writer.close();
}
}
2.InputStreamReader
package com.bjpowernode.java.io;
import java.io.*;
public class BufferedReaderTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 字节流
fis = new FileInputStream("file");
// 通过转换流转换(InputStreamReader将字节流转换成字符流)
// fis是节点流。reader是包装流。
InputStreamReader reader = new InputStreamReader(fis);
// 把转换成字符流的字节流,传过来
// reader是节点流。br是包装流。
BufferedReader br = new BufferedReader(reader);
// 上面进行合并
BufferedReader br1 = new BufferedReader(new InputStreamReader(new FileInputStream("file")));
// 进行打印
String s = null;
while((s = br1.readLine()) != null){
System.out.println(s);
}
// 关闭
br.close(); // 关闭最外层
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
🥅数据流
数据流DataOutputStream和DataStream都是包装流。
DataOutputStream:允许应用程序将基本数据类型、String类型的变量写入输出流中!
DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量!
注:引用数据无法写入,但是可以写入基本数据类型和String类型!
1.DataOutputStream
(1)java.io.DataOutputStream:数据专属的流;也是一个包装流!
(2)这个流可以将数据连同数据的类型一并写入文件。
(3)注意:这个文件不是普通文本文档(这个文件使用记事本打不开)(4)因为不是普通文档,所以一般是数据流是DataOutputStream和DataInputStream联合使用,先写入才能读!
package com.bjpowernode.java.io;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class DataOutputStreamTest01 {
public static void main(String[] args) throws Exception{
// 创建数据专属的字节输出流(写)
DataOutputStream dos = new DataOutputStream(new FileOutputStream("file")); //生成的这个文件,记事本打不开
// 写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = false;
char c = 'a';
// 写;把数据以及数据的类型一并写入到文件当中
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
// 刷新
dos.flush();
// 关闭最外层
dos.close();
}
}
2.DataInputStream
(1)DataInputStream:数据字节输入流。
(2)DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序;读的顺序需要和写的顺序一致。才可以正常取出数据。(3)对象流DataInputStream中的方法:
byte readByte()
short readShort()
int readInt()
long readLong()
float readFloat()
double readDouble()
char readChar()
boolean readBoolean()
String readUTF()
void readFully(byte[] b)
package com.bjpowernode.java.io;
import java.io.DataInputStream;
import java.io.FileInputStream;
public class DataInputStreamTest01 {
public static void main(String[] args) throws Exception {
// 创建数据专属的字节输入流(读)
DataInputStream dis = new DataInputStream(new FileInputStream("file"));
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();
// 打印
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(sex);
System.out.println(c);
// 关闭流
dis.close();
}
}
数据流的弊端:
只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以重点介绍对象流ObjectOutputStream和ObjectInputStream,数据流了解即可!
🥅标准输出流
PrintStream也是一个包装流。
标准输入、输出流:
System.in和System.out分别代表了系统标准的输入和输出设备。
默认输入设备是:键盘,输出设备是:显示器。
System.in的类型是InputStream。
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类。
重定向:通过System类的setIn,setOut方法对默认设备进行改变!
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
扩展:
System类中有三个常量对象:System.out、System.in、System.err;查看System类中这三个常量对象的声明发现都是null!并且是被final声明,final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗? 这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗? 这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
答:final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
1.PrintStream
(1)java.io.PrintStream:标准的字节输出流,默认输出到控制台。
(2)标准输出流不需要手动close()关闭!(3)虽然默认是输出到控制台,但是可以通过System.setOut()方法改变输出流的方向,俩面的参数传一个包装流对象!
(4)实际上前面学的System.out.println前面的System.out返回的就是PrintStream流对象。
package com.bjpowernode.java.io;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamTest {
public static void main(String[] args) throws Exception {
System.out.println("hello world");
// 实际上上述代码可以拆分成
PrintStream ps = System.out;
ps.println("hello world");
// 可以改变标准输出流的输出方向;
// 例如:标准输出流不在指向控制台,指向log文件
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
// 修改输出方向
System.setOut(printStream);
// 输出
System.out.println("Hello World"); //不会再输出到控制台,而是到log文件
}
}
System小总结:
①System.gc 建议开启垃圾回收机制,默认会调用finalize方法。
②System.arraycopy:数组拷贝,五个参数。
③System.currentTimeMillis() 获取自1970年1月1日到系统当前时间的总毫秒数。
④System.setOut:是修改输出流的方向,参数是一个PrintStream流对象。
2.日志工具类
那么标准输出流PrintStream有什么用呢?我们不妨写一个简单的日志工具类,既然是工具类就写成静态的,使用“类名.”的方式就可以调用!
日志框架
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
// 日志工具
public class Logger {
// 记录日志的方法
public static void log(String msg) {
try {
PrintStream out = new PrintStream(new FileOutputStream("log.txt",true));
// 改变流的方向
System.setOut(out);
// 获取系统当前时间
Date nowDate = new Date();
// 格式类型转换
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String myNowdate = sdf.format(nowDate);
// 打印
System.out.println(myNowdate+":"+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
日志测试
package com.bjpowernode.java.io;
public class LoggerTest {
public static void main(String[] args) {
// 测试日志工具类
Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
Logger.log("调用了UserService的doSome()方法");
Logger.log("用户尝试进行登录,验证失败");
// 在log.txt打印的结果
/* 2022-07-22 08:47:49 667:调用了System类的gc()方法,建议启动垃圾回收
2022-07-22 08:47:49 703:调用了UserService的doSome()方法
2022-07-22 08:47:49 714:用户尝试进行登录,验证失败*/
}
}
🥅File类的理解
java.io.File
(1)File类和四大家族没有关系,所以File类不能完成文件的读和写。
(2)File对象代表什么?文件和目录路径名的抽象表示形式。
C:\Drivers 这是一个File对象
C:\Drivers\Lan\Realtek\Readme.txt 也是File对象。
一个File对象有可能对应的是目录,也可能是文件;File只是一个路径名的抽象表示形式;不能通过File完成文件的读和写!
构造器:
public File(String pathname) ` :以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
public File(String parent, String child) ` :以parent为父路径,child为子路径创建File对象。
public File(File parent, String child)` :根据一个父File对象和子文件路径创建File对象
关于路径:
①IDEA中,main中的文件的相对路径,是相对于"
当前工程
";②IDEA中,单元测试方法中的文件的相对路径,是相对于"
当前module
"。
1.常用方法1
(1)exists()方法:判断当前文件是否存在。
(2)creatNewFile()方法:以文件的形式创建出来。
(3)mkdir()方法:以目录的形式创建出来;创建多重目录mkdirs()方法
(4)获取当前文件的父路径:
方法1:使用getParent(),返回的是一个String。
方法2:使用getParentFile(),返回的是一个File;和上面效果是一样的。(5)getAbsolutePath()方法:获取当前文件的绝对路径!
(6)public boolean delete():删除文件或者文件夹;删除注意事项:
① Java中的删除不走回收站。
② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
package com.bjpowernode.java.io;
import java.io.File;
public class FileTest01 {
public static void main(String[] args) throws Exception {
// 创建File对象
File f1 = new File("D:\\file");
// 1、判断当前文件是否存在;exists()方法
System.out.println(f1.exists()); // false
// 2、如果D:\file不存在,则以文件的形式创建出来;createNewFile()方法
if (!f1.exists()) {
f1.createNewFile();
}
// 3、如果D:\file不存在,则以目录的形式创建出来;mkdir()方法
if (!f1.exists()) {
f1.mkdir();
}
// 4、创建多重目录;mkdirs()方法
File f2 = new File("D:/a/b/c/d");
if (!f2.exists()) {
f2.mkdirs();
}
// 5、获取当前文件的父路径
File f3 = new File("D:\\python学习\\PyCharm Community Edition 2021.2.2");
// 第一种:使用getParent(),返回的是一个String
String parentPath = f3.getParent();
System.out.println(parentPath); // D:\python学习
// 第二种:使用getParentFile(),返回的是一个File;效果是一样的
File parentFile = f3.getParentFile();
System.out.println(parentFile); // D:\python学习
// 6、获取当前文件的绝对路径
File f4 = new File("file");
System.out.println(f4.getAbsolutePath()); // C:\Users\86177\IdeaProjects\JavaSe1\file
}
}
2.常用方法2
(1)getName():获取当前文件的名字
(2)isDirectory():判断是否是一个目录
(3)isFile():判断是否是一个文件
(4)lastModified():获取文件最后一次修改时间;单位是毫秒数,返回long型
(5)length():获取文件大小,单位是字节
package com.bjpowernode.java.io;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileTest02 {
public static void main(String[] args) {
File f1 = new File("C:\\Java学习\\javaSE学习\\2.1JavaSE进阶笔记\\javase\\Copy02.java");
// 1、获取文件名,getName()方法
System.out.println("文件名:"+f1.getName()); // 文件名:Copy02.java
// 2、判断是否是一个目录,isDirectory()方法
System.out.println(f1.isDirectory()); // false
// 3、判断是否是一个文件,isFile()方法
System.out.println(f1.isFile()); // true
// 4、获取文件最后一次修改时间,lastModified()方法
// 得到的是毫秒数
long modifytTime = f1.lastModified();
// 将总毫秒数转换成日期
Date date = new Date(modifytTime);
// 格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(date)); //2020-03-11 14:35:46 154
// 5、获取文件大小,length()方法
System.out.println(f1.length()); //1490 字节
}
}
3.listFiles方法
public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
package com.bjpowernode.java.io;
import java.io.File;
public class FileTest03 {
public static void main(String[] args) {
File f = new File("C:\\Java学习\\javaSE学习\\2.1JavaSE进阶笔记\\javase\\chapter15\\src");
// 调用listFiles()方法,返回的是一个File[]数组
File[] files = f.listFiles();
// 用增强for循环打印
for(File file :files){
System.out.println(file.getAbsolutePath()); // 获取所有子目录的绝对路径
System.out.println(file.getName()); //获取所有子目录的文件名
}
}
}
4. File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径,相当于剪切
file1.renameTo(file2)要想此方法执行完返回true,要满足以下要求:
①首先file1必须存在;即D:\\spring.xml这个文件要存在;
②其次file2必须不存在,但是file2所在的文件目录需要存在;即D:\\spring要存在;spring.xml不存在;
package com.zl.config;
import java.io.File;
public class Test {
public static void main(String[] args) {
File file1 = new File("D:\\spring.xml");
File file2 = new File("D:\\spring\\spring.xml");
boolean b = file1.renameTo(file2);
System.out.println(b?"重命名成功":"重命名失败");
}
}
5. 判断功能的方法
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
例1:创建一个与hello.txt文件在相同文件目录下的另一个名为abc.txt文件
思路:首先我们需要调用getAbsoluteFile()方法,获得abc.txt的绝对路径;有了绝对路径就可以调用getParent()方法得到文件的父路径;有了父路径就可以通过调用public File(String parent, String child)这个构造方法来创建abc.txt文件。
package com.zl.config;
import java.io.File;
public class Test {
public static void main(String[] args) {
File file1 = new File("hello.txt");
System.out.println(file1.getAbsoluteFile());
// 获取绝对路径
File absoluteFile = file1.getAbsoluteFile();
// 获取父路径
String parent = absoluteFile.getParent();
// 创建abc.txt
File file2 = new File(parent, "abc.txt");
System.out.println(file2.getAbsoluteFile());
// 一步到位
File file = new File(file1.getAbsoluteFile().getParent(), "abc.txt");
System.out.println(file.getAbsoluteFile());
}
}
例题2:判断指定目录下是否有后缀名为.jpg的文件。如果有,就输出该文件名称
方式一:直接调用list()方法,返回的是一个有子文件或目录名字组成的String的数组,然后就可以调用endwith方法,看是否以“.jpg”结尾。
方式二:也调用list(filter)方法,里面可以传一个过滤器;返回一个布尔类型,是true就保留。
方式三:调用listFiles()方法,返回的是一个File[]数组,然后进行遍历调用getName()方法返回String类型,得到每一个的名字,在调用endwith方法即可。
package com.zl.config;
import java.io.File;
import java.io.FilenameFilter;
public class Test {
public static void main(String[] args) {
File file = new File("C:\\Users\\86177\\Pictures\\nkw");
// 方法1:
String[] list = file.list();
for (String s : list) {
if(s.endsWith(".jpg")){
System.out.println(s);
}
}
// 方法2:
String[] list1 = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jpg");
}
});
for (String s : list1) {
System.out.println(s);
}
// 方法3:
File[] files = file.listFiles();
for (File file1 : files) {
if (file1.getName().endsWith(".jpg")){
System.out.println(file1.getName());
}
}
}
}
例题3:遍历指定目录所有文件名称,包括子文件目录中的文件(递归调用)。
package com.zl.config;
import java.io.File;
public class Test {
public static void main(String[] args) {
File file = new File("C:\\Java学习\\1.javaSE学习\\13.JDK8新特性");
printFileName(file);
}
// 封装方法
public static void printFileName(File file){
if (file.isFile()){ // 如果此时是文件
System.out.println(file.getName());
}else if (file.isDirectory()){ // 如果此时是目录
File[] files = file.listFiles();
for (File file1 : files) { // 遍历,需要再次判断是文件还是目录
printFileName(file1);
}
}
}
}
总结:File类的对象,通常是作为IO流操作的文件的端点出现的;代码层面,将File类的对象作为参数传递到IO流相关的类的构造器当中!