一、IO流
I代表输入,O代表输出
输入:数据从数据源加载到内存。
输出:数据从内存写回到数据源。
Input代表将磁盘数据加载到内存中的过程
Output代表将内存数据写回磁盘的过程
二、io流分类
1. 文件流
2. 字符流和字节流
字符流通常比较适合用于读取一些文本数据
字节流通常适合读取图片,MP4资源等二进制的数据。
在基于字节流和字符流的基础上又扩展出了以下四种类别:
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
| 抽象接口 | InputStream | OutputStream | Reader | Writer |
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
| 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
| 访问字符串 | StringReader | StringWriter |
3. 节点流和处理流
节点流和处理流就是对已有的这些io流做一些优化改善。
节点流:直接对特定的数据源执行读写操作。
处理流:对一个已经存在的流做一些二次封装操作,其功能更加比原先更加强大。
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | 类型 |
|---|---|---|---|---|---|
| 抽象接口 | InputStream | OutputStream | Reader | Writer | 节 点 流 |
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter | 节 点 流 |
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter | 节 点 流 |
| 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter | 节 点 流 |
| 访问字符串 | StringReader | StringWriter | 节 点 流 | ||
| 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter | 处 理 流 |
| 打印流 | PrintStream | PrintWriter | 处 理 流 | ||
| 对象流 | ObjectInputStream | ObjectOutputStream | 处 理 流 | ||
| 转换流 | InputStreamReader | OutputStreamWriter | 处 理 流 |
三、文件流
1. 创建文件对象相关构造器和方法
new File(String pathname) //根据路径构建一个File对象
new File(File parent,String child) //根据父目录文件+子路径构建
new File(String parent,String child) //根据父目录+子路径构建
CreateNewFile 创建新文件
2. 获取文件的相关信息
文件名字:getName()
文件绝对路径:getAbsolutePath()
文件父级目录:getParent()
文件大小(字节):length())
文件是否存在:exists()
是不是一个文件:isFile()
是不是一个目录: isDirectory()
3. 目录的操作和文件删除
创建一级目录 mkdir()
创建多级目录 mkdirs()
删除空目录或文件 delete()
四、常用节点流
1. FileInputStream 和 FileOutputStream
FileInputStream流会用于以字节的方式去读取文件信息(代表从磁盘读取数据)
FileInputStream类
public static void readFile() {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File("e:\\text1.txt"));
byte[] readData = new byte[10];
int bufferSize = 0;
//如果读取正常,会返回实际读取的字节数
while ((bufferSize = fileInputStream.read(readData)) != -1){
System.out.print(new String(readData,0,bufferSize));
}
System.out.println();
}catch (Exception e){
e.printStackTrace();
} finally {
//关闭文件流,释放资源
try {
//这个异常只要保证的确执行了close操作即可
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用注意点:
- 流需要进行关闭操作
- 使用read函数的时候,当文件读取正常时候会返回实际的字节数
FileOutputStream是文件的字节输出流(代表往磁盘中写入数据)
FileOutputStream类
public static void writeFileVersion1() {
String filePath = "e:\\text2.txt";
FileOutputStream fileOutputStream = null;
try {
//如果通过new FileOutputStream(filePath)的方式执行写入,实际上数据是会覆盖原先的内容,所以根据构造函数的第二参数来实现追加的效果
fileOutputStream = new FileOutputStream(filePath,true);
fileOutputStream.write("input sth".getBytes());
fileOutputStream.write("input sth2".getBytes());
}catch (Exception e){
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:
- 在`FileOutputStream`的构造函数中,可以通过定义第二个参数为true,去设置写入的模式(覆盖模式还是追加模式)。
- 使用字符输出流的时候,即使没有走到close函数,数据也会被持久化写入到磁盘。
2. FileReader 和 FileWriter
FileReader
public static void readFile(){
FileReader fileReader = null;
try {
fileReader = new FileReader("e:\\text.txt");
char[] chars = new char[3];
int readLen = 0;
while ((readLen=fileReader.read(chars))!=-1){
System.out.println(new String(chars,0,readLen));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter
/**
* 记得追加模式和close/flush
*/
public static void writeFileVersion1(){
FileWriter fileWriter = null;
try {
//默认是覆盖模式
//fileWriter = new FileWriter("e:\\text_3.txt");
fileWriter = new FileWriter("e:\\text_3.txt",true);
fileWriter.write("this is test\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//底层使用了sun.nio.cs.StreamEncoder.writeBytes,
//底层其实是使用了Nio 的 bytebuffer做数据实际刷盘操作
//所以其实底层还是使用了byte作为基本单位来进行操作
fileWriter.close();
//flush也会写入数据,但是没有执行close操作
// fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意点:
- 使用完毕后如果不执行close操作或者flush操作则并不会将实际的内容持久化输出到磁盘当中。
- 在使用`FileWriter`的构造函数中,第二个参数,用于声明当前的数据写入之后是否会覆盖原先的内容。
- 关闭文件流, 等价 flush() + 关闭
3. PipedInputStream 和 PipedOutputStream
管道流通常会被我们用在不同的线程之间进行消息通讯中使用。
例如下边这段代码,定义了一个子线程,该线程会往PipedOutputStream中写入数据,然后main线程会去PipedInputStream中读取子线程写入的数据。
/**
* out底层是用了一个字节缓冲数组buffer接收数据传输,当这个字节缓冲数组满了之后通过使用notifyAll唤醒in内部读数据的线程
* 所以这里面的底层原理还是离不开sync和notifyall
* @param args
*/
public static void main(String[] args) {
try (PipedOutputStream out = new PipedOutputStream()) {
PipedInputStream in = new PipedInputStream(out);
new Thread(() -> {
try {
Thread.sleep(2000);
String item = new String();
for (int i=0;i<1000;i++){
item = item + "1";
}
out.write(item.getBytes(StandardCharsets.UTF_8));
out.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
int receive = 0;
System.out.println("try");
byte[] temp = new byte[1024];
//等待子线程往pipedOutputStream内部写入数据
while ((receive = in.read(temp)) != -1) {
String outStr = new String(temp,0,receive);
System.out.println(outStr);
}
} catch (Exception e) {
e.printStackTrace();
}
}
五、常用处理流
1. 缓冲流:BufferedInputStream 和 BufferedOutputStream
缓冲字节流的出现就是通过引入一个缓冲Buffer的来优化这种多次读写操作导致的io性能不足的设计。
缓冲字节处理流的内部存在一个叫做buffer的缓冲区,每次写入数据的时候都会往缓冲区中写入数据,
当缓冲区积攒了足够多的数据后再一次性写回到数据源中。这种设计思路相比原先的一次写一次io要高效更多。
BufferedInputStream和BufferedOutputStream如何使用?
//基于字节为单位实现文件拷贝效果
public static void fileCopyByByte(String sourceFile,String destPath) throws IOException {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath));
byte[] tempByte = new byte[1024];
int tempByteLen = 0;
while ((tempByteLen = bufferedInputStream.read(tempByte)) != -1) {
bufferedOutputStream.write(tempByte,0,tempByteLen);
}
bufferedOutputStream.close();
bufferedInputStream.close();
}
注意点:
- `BufferedInputStream`和`BufferedOutputStream`内部采用了修饰器模式,
通过构造函数注入一个`java.io.InputStream/java.io.OutputStream`对象,从而达到可以注入多种字节输入输出流。
- 当关闭流的时候只需要关闭外界包装的字节缓冲流即可,它的close操作会触发对应的字节流内部的close函数。
- 使用缓冲处理流的时候要记得调用close或者flush操作才能将实际数据真正写入到磁盘当中。
BufferedReader和BufferedWriter的使用
- BufferedReader
字符缓冲输入流:BufferedReader。
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。
方法:public String readLine() 读取一行数据返回,如果读取没有完毕,无行可读返回null
BufferedReader类的使用
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "e:\a.java";
//创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流
//FileReader。
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了.
} finally {
in = null;
cb = null;
}
}
}
*/
bufferedReader.close();
}
}
- BufferedWriter
字符缓冲输出流:BufferedWriter。
作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器:public BufferedWriter(Writer w) 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能
方法: public void newLine() 换行操作
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello2!");
bufferedWriter.newLine();
bufferedWriter.write("hello3!");
bufferedWriter.newLine();
//说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭
bufferedWriter.close();
}
}
2. 打印流:PrintStream 和 PrintWriter
作用:打印流可以实现方便、高效的打印数据到文件中去。
打印流一般是指:PrintStream,PrintWriter两个类。
可以实现打印什么数据就是什么数据,
例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
- PrintStream
public class PrintStream_ {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
*/
out.print("john, hello");
//因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出
out.write("你好".getBytes());
out.close();
//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\f1.txt"
//2. "hello,~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了out
// }
System.setOut(new PrintStream("e:\f1.txt"));
System.out.println("hello, ~");
}
}
- PrintWriter
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 北京你好~~~~");
printWriter.close(); //flush + 关闭流, 才会将数据写入到文件..
}
}
- PrintStream和PrintWriter的区别
打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
PrintWriter继承自字符输出流Writer,支持写字符数据出去。
3. 转换流:InputStreamReader 和 OutputStreamWriter
1. InputStreamReader
在读取文件时,当代码编码和文件编码不一致时,就会导致乱码,字符输入转换流就是解决乱码而生的。
字符输入转换流的作用:
使用字符输入转换流可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码
字符输入转换流:InputStreamReader,可以把原始的字节流按照指定编码转换成字符输入流。
构造器
public InputStreamReader(InputStream is) 可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。
public InputStreamReader(InputStream is ,String charset) 可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点)
/**
* @author Maple
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);
//将2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));
//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();
}
}
2. OutputStreamWriter
字符输入转换流:OutputStreamWriter
可以把字节输出流按照指定编码转换成字符输出流。
构造器
1. public OutputStreamWriter(OutputStream os) 可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。
2. public OutputStreamWriter(OutputStream os,String charset) 可以把原始的字节输出流按照指定编码转换成字符输出流(重点)
/**
* @author Maple
* @version 1.0
* 演示 OutputStreamWriter 使用
* 把FileOutputStream 字节流,转成字符流 OutputStreamWriter
* 指定处理的编码 gbk/utf-8/utf8
*/
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\maple.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("hi");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");
}
}
4. 对象流:ObjectInputStream 和 ObjectOutputStream
1. ObjectOutputStream
对象序列化(对象字节输出流,写对象数据到磁盘文件):
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
使用到的流是对象字节输出流:ObjectOutputStream
ObjectOutputStream序列化方法
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("Maple");//String
//保存一个dog对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
总结:
1. 对象序列化的含义是什么?
把对象数据存入到文件中去。
2. 对象序列化用到了哪个流?
对象字节输出流ObjectOutputStram
public void writeObject(Object obj)
3. 序列化对象的要求是怎么样的?
对象必须实现序列化接口
2. ObjectInputStream
对象反序列化(将磁盘文件的数据恢复到内存中):
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
ObjectInputStream序列化方法
public class ObjectInputStream_ {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取
//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
//2. 否则会出现异常
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog
//这里是特别重要的细节:
//1. 如果我们希望调用Dog的方法, 需要向下转型
//2. 需要我们将Dog类的定义,放在到可以引用的位置
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //旺财..
//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();
}
}
总结:
1. 对象反序列化的含义是什么?
把磁盘中的对象数据恢复到内存的Java对象中。
2. 对象反序列化用到了哪个流?
对象字节输入流ObjectInputStram
public Object readObject()
六、Properties
-
Properties属性集对象
其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用。 -
Properties核心作用:
1. Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。 2. 属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。 比如: ip=127.0.0.1 user=root password=admin -
Properties的API:
Properties和IO流结合的方法:
案例
- 使用properties类完成对mysql.properties的读取
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties 文件,并得到ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("src\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
String[] split = line.split("=");
//如果我们要求指定的ip值
if("ip".equals(split[0])) {
System.out.println(split[0] + "值是: " + split[1]);
}
}
br.close();
}
}
- 使用properties类添加key-value到新文件 mysql2.properties中
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件
//1. 创建Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\mysql.properties"));
//3. 把k-v显示控制台
properties.list(System.out);
//4. 根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}
- 使用properties类完成对mysql2.properties的读取,并修改某个key-value
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有key 就是创建
//2.如果该文件有key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果key 存在,就替换
return old;
}
}
addEntry(hash, key, value, index);//如果是新k, 就addEntry
return null;
}
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值
properties.setProperty("pwd", "888888");
//将k-v 存储文件中即可
properties.store(new FileOutputStream("src\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}
总结:
1. 可以存储Properties属性集的键值对数据到属性文件中去:
void store(Writer writer, String comments)
2. 可以加载属性文件中的数据到Properties对象中来:
void load(Reader reader)