I/O流
一、常用文件操作
1.创建文件对象相关构造器和方法
new File(String pathName)//根据路劲构建一个File对象
new File(File parent,String child)//根据父目录文件+子路径构建
new File(String parent,String child)//根据路劲构建一个File对象
createNewFile //创建新文件
2.获取文件的相关信息
.getName
.getAbsolutePath
.getParent
.length
.exists
.isFile
.isDirectory
3.目录操作和文件删除
mkdir创建一级目录
mkdirs创建多级目录
delete删除空目录或者文件
二、IO流原理及流的分类
2.1 Java IO流原理
- I/O是 Input/Output 的缩写,I/O技术是非常实用的技术,用于处理数据传输,如读/写文件,网络通讯等。
- Java程序中,对于数据的输入/输出操作以”流(stream)”的方式进行。
- Java. Io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
- 输入input: 读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出output: 将程序(内存)数据输出到磁盘、光盘等存储设备中
2.2 流的分类
1.按照操作数据单位不同分为:字节流(8 bit) 和 字符流(按字符)
2.按照流的流向不同分为:输入流 和 输出流
3.按照流的角色不同分为:节点流 和 处理流/包装流
抽象基类 | 字节流(二进制文件) | 字符流(文本文件) |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
三、字节流
InputStream抽象类是所有类字节输入流的超类
InputStream 常用的子类
- FileInputStream: 文件输入流
- BufferedInputStream: 缓冲字节输入流
- ObjectInputStream :对象字节输入流
3.1 FileInputStream 文件输入字节流
read(byte[] b)
从该输入流读取最多b.length
个字节的数据为字节数组
read()
从该输入流读取一个字节的数据。read(byte[] b, int off, int len)
从该输入流读取最多len
字节的数据为字节数组。skip(long n)
跳过并从输入流中丢弃n
字节的数据。close()
关闭此文件输入流并释放与流相关联的任何系统资源。
@Test
public void readFiles() throws FileNotFoundException {
String filePath = "e:\\create01.txt";
java.io.FileInputStream fileInputStream = new java.io.FileInputStream(filePath);
try {
//创建一个文件输入流对象
//从该输入流读取一个字节的数据,如果没有输入可用,此方法将阻止 中文会乱码
//返回-1 表示读取完毕
// int read = fileInputStream.read();
byte[] bytes = new byte[3];
//如果读取正常,返回时实际读取的字节数
while (fileInputStream.read(bytes) != -1){
System.out.print(new String(bytes,0, bytes.length));
}
//关闭流
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
}
}
3.2 FileOutputStream 文件输出字节流
write(byte[] b)
将b.length
个字节从指定的字节数组写入此文件输出流。
write(byte[] b, int off, int len)
将len
字节从位于偏移量off
的指定字节数组写入此文件输出流。write(int b)
将指定的字节写入此文件输出流close()
关闭此文件输入流并释放与流相关联的任何系统资源。
/**
* 写文件
*/
@Test
public void writeFile() {
String outPath = "e:\\b.txt";
int inWeekYear = Calendar.getInstance().getWeeksInWeekYear();
byte[] bytes = ("本周是今年第"+inWeekYear+"周").getBytes();
try {
// 第二个参数append,如果为true,则字节将被写入文件的末尾而不是开头!!!!追加
FileOutputStream fileOutputStream = new FileOutputStream(outPath,true);
fileOutputStream.write(bytes);
System.out.println("写入成功!!!");
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 拷贝文件
*
* @deprecated :文件拷贝 从A位置拷贝到B位置
*/
@Test
public void fileCopy() {
String fileFromPath = "E:\\GoogleDownload\\壁纸\\01.jpg";
String fileToPath = "E:\\GoogleDownload\\CMLXP.jpg";
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = new FileInputStream(fileFromPath);
outputStream = new FileOutputStream(fileToPath);
//定义字节数组 提高读取效率
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) != -1) {
//读取到就开始写入 边读边写
//一定要用这个构造方法
outputStream.write(bytes, 0, bytes.length);
}
System.out.println("拷贝成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、字符流
FileReader和FileWriter是字符流,即按照字符来操作IO
4.1 FileReader字符输入流
- java.lang.Object
- java.io.InputStreamReader
- java.io.FileReader
构造方法:
FileReader(File file)
创建一个新的 FileReader ,给出 File读取。
FileReader(FileDescriptor fd)
创建一个新的 FileReader ,给定 FileDescriptor读取。
FileReader(String fileName)
创建一个新的 FileReader ,给定要读取的文件的名称。
主要方法:
//读一个字符 该方法将阻塞,直到字符可用,发生I / O错误或达到流的结尾。 旨在支持高效单字符输入的子类应该覆盖此方法。
public int read()throws IOException
//将字符读入数组。 该方法将阻塞,直到某些输入可用,发生I / O错误或达到流的结尾。
public int read(char[] cbuf)throws IOException
//将字符读入数组的一部分。 该方法将阻塞,直到某些输入可用,发生I / O错误或达到流的结尾。
public int read(char[] cbuf,int offset,int length)throws IOException
// 结果 :读取的字符数,如果已经达到流的结尾,则为-1
相关API:
//将char[]转换成String
new String(char[])
//将char[]指定部分转换成String
new String(char[],offset,length)
4.2 FileWriter 字符输出流
- java.lang.Object
- java.io.OutputStreamWriter
- java.io.FileWriter
注意:
FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件。
构造方法:
//给一个File对象,构造一个FileWriter对象 覆盖模式 相当于流的指针在首端
FileWriter(File file)
//给一个File对象,构造一个FileWriter对象。 追加模式 相当于流的指针在尾端
FileWriter(File file, boolean append)
//构造一个给定文件名,构造FileWriter对象。覆盖模式
FileWriter(String fileName)
//构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据 追加模式
FileWriter(String fileName, boolean append)
主要方法:
//写一个字符
public void write(int c)throws IOException
//写入一个字符数组
public void write(char[] cbuf) throws IOException
//写入字符数组的一部分
public abstract void write(char[] cbuf,int off,int len)throws IOException
//写一个字符串
public void write(String str)throws IOExceptio
//写一个字符串的一部分
public void write(String str,int off,int len) throws IOException
相关API:
String类:
//toCharArray:将String转换为char[]
"sdasd".toCharArray()
五、节点流和处理流
节点流:可以从一个特定的数据源读写数据,如FileReader、FileWriter
处理流/包装流:是“连接”在已存在的流(节点流或者处理流)之上,为程序 提供更为强大的读写功能,如BufferedReader、BufferedWriter
5.1节点流和处理流的区别和联系
1.节点流是底层流/低级流,直接跟数据源相接。
2.处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。[源码理解]
3.处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连 as. 模拟修饰器设计模式]
处理流的功能主要体现在以下两个方面:
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
5.2 处理流 BufferedReader 和 BufferedWriter
BufferedReader和 BufferedWriter属于字符流,是按照字符来读取数据的关闭时,关闭处理流时,只需要关闭外层流即可
BufferedWriter:
public void writer() throws IOException {
String writePath ="e:\\cml.txt";
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(writePath));
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
writer.write("写入时间:" + dateFormat.format(Calendar.getInstance().getTime()));
writer.newLine();//插入一个换行
writer.write("写入时间:" + dateFormat.format(Calendar.getInstance().getTime()));
System.out.println("写入成功!!!");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (writer != null){
writer.close();
}
}
}
BufferedReader:
public void readFile(){
String filePath = "e:\\你好.txt";
BufferedReader bufferedReader =null;
try {
//第二个参数 是设置缓冲区的大小
bufferedReader = new BufferedReader(new FileReader(filePath),1024);
//按行读取 readLine 当返回null表示读取完毕
while (bufferedReader.readLine() != null){
System.out.println(bufferedReader.readLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bufferedReader !=null){
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
边读边写:
/**
* 拷贝文件 边读边写
*
* @throws IOException ioexception
*/
@Test
public void fileCopy() throws IOException {
String fromFile = "e:\\cml.txt";
String toFile = "e:\\xp.txt";
BufferedReader reader = null;
BufferedWriter writer = null;
try {
reader = new BufferedReader(new FileReader(fromFile));
writer = new BufferedWriter(new FileWriter(toFile));
//readLine 读取一行内容 但没有换行
while ( reader.readLine() != null){
//读取一行 写入一行
writer.write(reader.readLine());
writer.newLine();
}
System.out.println("copy successful!!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (writer != null){
writer.close();
}
if (reader != null){
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.3 处理流BufferedInputStream和BufferedOutputStream
BufferedInputStream
为另一个输入流添加了功能,即缓冲输入和支持mark
和reset
方法的功能。 当创建BufferedInputStream
时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark
操作会记住输入流中的一点,并且reset
操作会导致从最近的mark
操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
package com.xp.bufferedInput;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* 缓冲输入流演示
* @description 图片拷贝
* @author XiaoPeng
* @date 2022/05/31
*/
public class BufferInputStreamDemo {
@Test
public void binaryCopy() throws IOException {
String fromPath = "C:\\Users\\XiaoPeng\\Pictures\\Camera Roll\\xp.jpg";
String toPath = "E:\\xiaopeng.jpg";
BufferedInputStream inputStream = null;
BufferedOutputStream outputStream = null;
byte[] bytes = new byte[8];
try {
inputStream = new BufferedInputStream(new FileInputStream(fromPath));
outputStream = new BufferedOutputStream(new FileOutputStream(toPath));
while (inputStream.read(bytes) != -1) {
outputStream.write(bytes);
}
// assert 1>2;
System.out.println("写入成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream != null){
outputStream.flush();
outputStream.close();
}
if (inputStream != null){
inputStream.close();
}
}
}
}
5.4 对象处理流 ObjectInputStream和ObejctOutputStream
基本介绍:
1.功能:提供了对基本类型或对象类型的序列化和反序列化的方法。
2.ObjectInputStream提供序列化功能
3.ObejctOutputStream提供反序列化功能
看一个需求
1.将int num = 100这个int 数据保存到文件中,注意不是100数字,而是int 100,并且,能够从文件中直接恢复int 100
2.将Dog dog = new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件恢复.3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作
5.4.1 ObejctOutputStream序列化基本数据和Dog对象
package com.xp.objectInput;
import com.xp.entity.Dog;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* 对象输入流演示
*
* @author XiaoPeng
* @date 2022/05/31
*/
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException {
//序列化后,保存的文件格式,不是存文本 按照他的格式来存储
String filePath = "e://data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据
oos.write(100);//int ->Integer
oos.writeBoolean(true); //boolean ->Boolean
oos.writeChar('a');//char ->Character
oos.writeDouble(3.14159265358984626);//double ->Double
oos.writeUTF("宁夏大学");//String
//序列化对象
oos.writeObject( new Dog("cml", 15, 32241258745L));
oos.close();
System.out.println("序列化完毕!!!!");
}
}
5.4.2 ObjectInputStream反序列化data.dat
package com.xp.objectInput;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 对象输入流演示
*
* @author XiaoPeng
* @date 2022/05/31
*/
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream oos = new ObjectInputStream(new FileInputStream(filePath));
//读取 反序列化的顺序和序列化的顺序保持一致
System.out.println(oos.readInt());
System.out.println(oos.readBoolean());
System.out.println(oos.readChar());
System.out.println(oos.readDouble());
System.out.println(oos.readUTF());
Object o = oos.readObject();
System.out.println(o);
System.out.println("读取类型:"+o.getClass());
//如果需要调用对象方法则需要向下转型
Dog dog = (Dog) o;
System.out.println(dog.getAge());
oos.close();
}
}
5.4.3 注意事项
1.读写顺序要一致
2.要求实现序列化或反序列化对象,需要实现 Serializable
3.序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
4.序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
5.序列化对象时,要求里面属性的类型也需要实现序列化接口
6.序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
SerialVersionUID
1.序列化ID,相当于身份认证,主要用于程序的版本控制,保持不同版本的兼容性,在程序版本升级时避免程序报出版本不一致的错误。
2.如果定义了private static final long serialVersionUID = 1L,那么如果你忘记修改这个信息,而且你对这个类进行修改的话,这个类也能被进行反序列化,而且不会报错。一个简单的概括就是,如果你忘记修改,那么它是会版本向上兼容的。
3.如果没有定义一个名为serialVersionUID,类型为long的变量,Java序列化机制会根据编译的class自动生成一个serialVersionUID,即隐式声明。这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID 。此时如果对某个类进行修改的话,那么版本上面是不兼容的,就会出现反序列化报错的情况。
package com.xp.entity;
import lombok.*;
import java.io.Serializable;
/**
*
* @author XiaoPeng
* @date 2022/05/31
* @deprecated:
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Dog implements Serializable {
//序列化的版本号 可以提高兼容性
private static final Long serialVersionUID = 1L;
private String name;
private Integer age;
private Long id;
//序列化对象时,默认将对象所有属性都进行序列化,但除了static和transient修饰的成员
private transient String hobby;
private static Integer cacheSize;
}
序列化和反序列化
1.序列化就是在保存数据时,保存数据的值和数据类型
2反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,
为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable //这是一个标记接口,一般选择这个
Externalizable //该接口有方法需要实现
5.5 标准输入输出流
com.hspedu.standard InputAndOutput.java
类型 | 默认设备 | |
---|---|---|
System.in 标准输入 | InputStream | 键盘 |
System.out 标准输出 | OutputStream | 显示器 |
5.6 转换流 InputStreamReader 和 OutputStreamWriter
- InputStreamReader : Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
- OutputStreamWriter : Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
package com.xp.transformationStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* 输入流读者演示
*
* @author XiaoPeng
* @date 2022/06/02
*/
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
String filePath = "e:\\b.txt";
//1.把FileInputStream 转换成InputStreamReader,并指定编码 gbk
InputStreamReader streamReader = new InputStreamReader(new FileInputStream(filePath), "GBK");
//2.将InputStreamReader 传入BufferReader
BufferedReader bufferedReader = new BufferedReader(streamReader);
String s = bufferedReader.readLine();
System.out.println("读取内容为: "+s);
bufferedReader.close();
}
}
package com.xp.transformationStream;
import java.io.*;
/**
* 输出流作家演示
*
* @author XiaoPeng
* @date 2022/06/02
*/
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
String filePath = "e:\\b.txt";
BufferedWriter bfw= new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath),"GBK"));
String[] strings ={"我叫小彭","今年24岁","你还好吗"};
for (int i = 0; i < strings.length; i++) {
bfw.write(strings[i]);
}
System.out.println("写入成功!!");
bfw.close();
}
}
5.7 打印流PrintStream 和 PrintWriter
打印流只有输出流,没有输入流
package com.xp.printStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 打印作家演示
*
* @author XiaoPeng
* @date 2022/06/02
*/
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//默认情况下,PrintStream 输出的位置是 标准输出 即显示器
out.println("端午节快乐!!!");
//源码
/* public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}*/
//底层使用的是write方法,所以可以直接使用write进行打印/输出
out.write("有意思".getBytes());
//可以修改打印流输出的位置/设备
System.setOut(new PrintStream("e:\\b.txt"));
//会输出到e:\\b.txt中
System.out.println("篆刻籇眉间心上!!");
out.close();
}
}
package com.xp.printWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 打印作家演示
*
* @author XiaoPeng
* @date 2022/06/02
*/
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\ff.txt"));
printWriter.println("如果这都不算爱,还有什么好期待");
printWriter.close();
}
}
5.8 Properties类
看一个需求:
如下一个配置文件mysql.properties
ip=192.168.0.13
username=root
password=12345
Properties父类是 Hashtable ,底层就是Hashtable核心方法
1.专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值2.注意:键值对不需要有空格,值不需要用引号一起来。默认类型是String
3.Properties的常见方法
load:加载配置文件的键值对到Properties对象list:将数据显示到指定设备
getProperty(key):根据键获取值
setProperty(key,value):设置键值对到Properties对象
store:将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码
package com.xp.properties;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.Properties;
/**
* 属性以及接下来
*
* @author XiaoPeng
* @date 2022/06/03
* Properties父类是 Hashtable ,底层就是Hashtable核心方法
*/
public class PropertiesDemo2 {
public static void main(String[] args) throws IOException {
//1.创建Properties对象
Properties properties = new Properties();
//2.加载指定配置文件
properties.load(new FileReader("src\\main\\resources\\application.properties"));
//3.获取数据并展示到控制台
properties.list(System.out);
System.out.println(properties.getProperty("spring.redis.port"));
}
@Test
public void createConfigureFile() throws IOException {
//1.创建Properties对象
Properties properties = new Properties();
//2.设置属性
//配置文件存在key 就更新,没有就创建
properties.setProperty("user","小彭");
properties.setProperty("id","1852164");
properties.setProperty("pwd","xiaopeng");
properties.store(new FileOutputStream("src\\main\\resources\\application.properties2"),"fist add");
System.out.println("创建并保存成功!");
}
}
六、字符流和字节流的区别
(1)读写单位不同:字节流 以字节(8 Bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
(2)处理对象不同:字节流能处理所有类型的数据(如图片,avi等),而字符流只能处理字符类型的数据。
(3)字节流在操作的时候本身是不会用到缓冲区的,是直接操作文件本身的;而字符流在操作的时候是会用到缓冲区的,通过缓冲区来操作文件。
结论:优先选用字节流,首先,因为硬盘上的所有文件都是以字节的形式进行传输或保存的,包括图片等内容,但字符只是在内存中才会形成的,所以,在开发上午过程中,字节流使用广泛。
十、Java的几种写入方法
10.1 PrintStream(字节打印流)方法写入:
PrintStream写入方法方便,并且可以输出java中任何对象,但写入速度较慢。
String data="hello java";
PrintStream ps=new PrintStream("E:/1.txt"); //1.txt不管存不存在都会被创建/覆盖
start =System.currentTimeMillis();
ps.print(data);
ps.close(); //用完及时关闭流避免资源泄露
10.2 常用的FileWriter:
FileWriter带有缓冲区,能够减少流的使用,所有在多次写入数据的时候会有速度上的优势。
String data="hello java";
FileWriter fw=new FileWriter(new File("E:/2.txt"));
fw.write(data);
fw.flush(); //FileWriter存在缓冲区,在最后要释放缓冲区的内容,避免还有数据停留在缓冲区里
fw.close();
//FileWriter还可以对原文件进行添加,只要在构造时多加个true为入参
FileWriter fw2=new FileWriter(new File("E:/2.txt"),true);
fw2.write(data); //这样就可以在原有的文件里添加内容了
fw2.flush();
fw2.close();
10.3 用BufferedWriter写入:
如果有非常多次的写入可以用BufferedWriter对FileWriter的缓冲区进行优化,BufferedWriter的缓冲区大小还可以进行自定义。
FileWriter fw=new FileWriter(new File("E:/3.txt")); //和之前一样新建FileWriter对象
BufferedWriter bw=new BufferedWriter(fw); //用BufferedWriter能比FileWriter更快的写入
bw.write(data);
bw.flush(); //同样最后要释放缓冲
bw.close();
//设置缓冲区大小
BufferedWriter bw2=new BufferedWriter(fw,1024); //在入参后多加个数字就是设置的大小啦
//缓冲区并不是越大越好,太大了写入速度不一定会变快,反而还占用了资源,一般用默认就够了。