day20 File类与IO流
1.java.io.File类
1.1 概述
File类是java.io包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。
在API中File的解释是文件和目录路径名的抽象表示形式,即File类是文件或目录的路径,而不是文件本身,因此File类不能直接访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
File类代表磁盘或网络中某个文件或目录的路径名称,如:/atguigu/javase/io/佟刚.jpg
但不能直接通过File对象读取和写入数据,如果要操作数据,需要IO流。File对象好比是到水库的“路线地址”,要“存取”里面的水到你“家里”,需要“管道”。
1.2 构造方法
-
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。 -
public File(String parent, String child)
:从父路径名字符串和子路径名字符串创建新的 File实例。 -
public File(File parent, String child)
:从父抽象路径名和子路径名字符串创建新的 File实例。 -
构造举例,代码如下:
package com.atguigu.io.file;
import java.io.File;
public class FileTest {
public static void main(String[] args) {
// 创建File对象
File f1 = new File("D\\a.txt");
System.out.println("f1 = " + f1); // f1 = D\a.txt
File f2 = new File("D:/a.txt");
System.out.println("f2 = " + f2); // f2 = D:\a.txt
// 通过父路径和子路径字符串
File f3 = new File("D:\\a", "c.txt");
File f4 = new File("D\\a\\c");
System.out.println(f4); // D\a\c
}
}
- 一个File对象代表硬盘种实际存在的一个文件或者目录
- 无论该路径下是否存在文件或者目录,都不影响File对象的创建
1.3常用方法
1.3.1 获取文件和目录基本信息的方法
-
public String getName()
:返回由此File表示的文件或目录的名称。 -
public long length()
:返回由此File表示的文件的长度。 -
public String getPath()
:将此File转换为路径名字符串。 -
public long lastModified()
:返回File对象对应的文件或目录的最后修改时间(毫秒值)方法演示,代码如下:
@Test public void method01(){ File file = new File("D:\\zw.txt"); System.out.println("file = " + file); // file = D:\zw.txt // 获取文件名 String fileName = file.getName(); System.out.println("fileName = " + fileName); // fileName = zw.txt // 获取最后修改时间 long longDate = file.lastModified(); Date date = new Date(longDate); // 转换成Date类型 System.out.println("date = " + date); // date = Tue Sep 27 15:55:32 CST 2022 // 转换成字符类型显示 String local = date.toLocaleString(); System.out.println("local = " + local); // local = 2022-9-27 15:55:32 // 获取文件的容量 字节 long length = file.length(); System.out.println("length = " + length); // length = 9 // 注意:length() 不能表示文件夹大小,和实际大小不对应 File f2 = new File("D:\\有道云笔记"); System.out.println("f2.length() = " + f2.length()); } }
1.3.2 各种路径问题
public String getPath()
:将此File转换为路径名字符串。public String getAbsolutePath()
:返回此File的绝对路径名字符串。String getCanonicalPath()
:返回此File对象所对应的规范路径名。
File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。
默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。
- 绝对路径:从盘符开始的路径,这是一个完整的路径。
- 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
- 规范路径:所谓规范路径名,即对路径中的“…”等进行解析后的路径名
package com.atguigu.io.file;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class FileMethod02 {
@Test
public void test01() throws IOException {
// 在相对路径操作文件
File f1 = new File("abc.txt");
f1.createNewFile();
}
@Test
public void test02() throws IOException {
// 文件的相对路径
File f1 = new File("abc.txt");
// 获取文件的绝对路径 f1.getAbsolutePath() = D:\java20220828\成长之路\code\day20\abc.txt
System.out.println("f1.getAbsolutePath() = " + f1.getAbsolutePath());
// 获取文件的相对路径 f1.getPath() = abc.txt
System.out.println("f1.getPath() = " + f1.getPath());
// 获取文件的规范路径 f1.getCanonicalPath() = D:\java20220828\成长之路\code\day20\abc.txt
System.out.println("f1.getCanonicalPath() = " + f1.getCanonicalPath());
}
@Test
public void test03() throws IOException {
// 文件的绝对路径
File f1 = new File("D:\\a.txt");
// 获取文件的绝对路径 f1.getAbsolutePath() = D:\a.txt
System.out.println("f1.getAbsolutePath() = " + f1.getAbsolutePath());
// 获取文件的相对路径 f1.getPath() = D:\a.txt
System.out.println("f1.getPath() = " + f1.getPath());
// 获取文件的规范路径 f1.getCanonicalPath() = D:\a.txt
System.out.println("f1.getCanonicalPath() = " + f1.getCanonicalPath());
}
@Test
public void test04() throws IOException {
// 文件的相对路径 使用../ 规范路径能够智能识别
File f1 = new File("../../abc.txt");
// 获取文件的绝对路径 f1.getAbsolutePath() = D:\java20220828\成长之路\code\day20\..\..\abc.txt
System.out.println("f1.getAbsolutePath() = " + f1.getAbsolutePath());
// 获取文件的相对路径 f1.getPath() = ..\..\abc.txt
System.out.println("f1.getPath() = " + f1.getPath());
// 获取文件的规范路径 f1.getCanonicalPath() = D:\java20220828\成长之路\abc.txt
System.out.println("f1.getCanonicalPath() = " + f1.getCanonicalPath());
}
}
1.3.3 判断功能的方法
-
public boolean exists()
:此File表示的文件或目录是否实际存在。 -
public boolean isDirectory()
:此File表示的是否为目录。 -
public boolean isFile()
:此File表示的是否为文件。方法演示,代码如下:
@Test
public void method02(){
File file = new File("D:\\w.txt");
// 判断文件或文件夹是否存在,不存在返回false,否则返回true
boolean exists = file.exists();
System.out.println("exists = " + exists); // exists = true
// 判断file是不是文件
boolean isFile = file.isFile();
System.out.println("isFile = " + isFile); // isFile = true
// 判断file是不是文件夹
boolean isDirectory = file.isDirectory();
System.out.println("isDirectory = " + isDirectory); // isDirectory = false
}
如果文件或目录存在,那么exists()、isFile()和isDirectory()都是返回true
1.3.4 创建删除功能的方法
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。public boolean delete()
:删除由此File表示的文件或目录。 只能删除空目录。public boolean mkdir()
:创建由此File表示的目录。public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。
方法演示,代码如下:
@Test
public void test03() throws IOException {
// 创建删除
File f1 = new File("D://abc.txt");
if (f1.exists()){
System.out.println("存在 删除");
f1.delete();
}else{
System.out.println("不存在 创建");
f1.createNewFile();
}
File f2 = new File("D://mm/nn");
// 创建单级目录
// f2.mkdir();
// 创建多级目录
f2.mkdirs();
// 不能删除非空文件夹
f2.delete();
}
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
1.3.5 目录操作
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。public File[] listFiles(FileFilter filter)
:返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(java.io.File) 方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。
以上方法常用与递归操作:
```java
@Test
public void method04(){
// 目录遍历
File file = new File("D:\\Luffy");
System.out.println("------------list-------------");
// 返回一个String数组,表示该File目录中的所有子文件或目录。
String[] list = file.list();
System.out.println("list = " + list); // list = [Ljava.lang.String;@22927a81
for (String s : list){
System.out.println("s = " + s);
}
System.out.println("------------listFiles --------------");
// 返回一个File数组,表示该File目录中的所有的子文件或目录。
File[] listFiles = file.listFiles();
for (File listFile : listFiles){
System.out.println("listFile = " + listFile);
}
}
```
1.3.6 递归练习
package com.atguigu.io.file;
import org.junit.Test;
import java.io.File;
import java.util.Date;
public class FileExer {
@Test
public void test01(){
/**
* 列出一个文件夹内所有的文件和文件夹 如果是文件夹 再继续列出所有内容
* 参考:递归
* */
File file = new File("D:\\Luffy");
showFile(file);
}
private void showFile(File file) {
File[] files = file.listFiles();
for (File f : files){
if (f.isDirectory()){
System.out.println("---文件夹名字是【" + f.getName() + "】");
showFile(f);
}else{
System.out.println(f + "--->" + new Date(f.lastModified()).toLocaleString());
}
}
}
}
2.IO概述
2.1 什么是IO
生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s
,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input
和输出output
,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
2.2 IO的分类
根据数据的流向分为:输入流和输出流。
- 输入流 :把数据从
其他设备
上读取到内存
中的流。- 以InputStream,Reader结尾
- 输出流 :把数据从
内存
中写出到其他设备
上的流。- 以OutputStream、Writer结尾
根据数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 以InputStream和OutputStream结尾
- 字符流 :以字符为单位,读写数据的流。
- 以Reader和Writer结尾
根据IO流的角色不同分为:节点流和处理流。
-
节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.
-
处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
这种设计是装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),其使用一种对客户端透明的方式来动态地扩展对象的功能,它是通过继承扩展功能的替代方案之一。在现实生活中你也有很多装饰者的例子,例如:人需要各种各样的衣着,不管你穿着怎样,但是,对于你个人本质来说是不变的,充其量只是在外面加上了一些装饰,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”…
常用的节点流:
- 文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流。
- 字符串 StringReader StringWriter 对字符串进行处理的节点流。
- 数 组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
- 管 道 PipedInputStream、PipedOutputStream、PipedReader、PipedWriter对管道进行处理的节点流。
常用处理流:
-
缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter—增加缓冲功能,避免频繁读写硬盘。
-
转换流:InputStreamReader、OutputStreamReader—实现字节流和字符流之间的转换。
-
数据流:DataInputStream、DataOutputStream -提供读写Java基础数据类型功能
-
对象流:ObjectInputStream、ObjectOutputStream–提供直接读写Java对象功能
2.3 IO中的基类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
3 字节流
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
3.1 字节输出流【OutputStream】
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
3.2 FileOutputStream类
OutputStream
有很多子类,我们从最简单的一个子类开始。
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
-
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
- 构造举例,代码如下:
package com.atguigu.outputandinput; import org.junit.Test; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; /** * 4大基流 抽象类 * OutputStream * InputStream * Reader * Writer * * OutputStream: * new FileOutputStream("D:/out.txt"); * new FileOutputStream("D:/out.txt", true); 可以进行数据追加 * os.write(97); // 写出时将数据转为字符 * byte[] bytes = s.getBytes(); * os.write(bytes); * */ public class OutputStreamTest { @Test public void test01() throws IOException { // 1.创建字节输出流对象 没有文件则会创建文件 OutputStream os = new FileOutputStream("D:/out.txt"); // 2.写入单个数据 os.write(97); byte[] bs = {65, 66, 67}; // 写入多个数据 数组 os.write(bs); // aABC // 3.关闭资源 os.close(); System.out.println("写入完毕"); } @Test public void test02() throws IOException { // 1.创建流对象 // OutputStream os = new FileOutputStream("D:/out.txt"); // 会覆盖原文件的内容 OutputStream os = new FileOutputStream("D:/out.txt", true); // 会在原内容后追加 // 2.写入数据 String s = "两个黄鹂鸣翠柳\n"; // 不支持直接写入字符串类型 需要将字符串转化byte数组 byte[] bytes = s.getBytes(); os.write(bytes); // 3.关闭资源 os.close(); System.out.println("写入完毕"); } }
3.3 字节输入流【InputStream】
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read()
: 从输入流读取数据的下一个字节。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
3.4 FileInputStream类
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
-
构造方法
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出
FileNotFoundException
。- 构造举例,代码如下:
package com.atguigu.outputandinput; import org.junit.Test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; /** * FileInputStream: * int length = fis.read(); 一次读取一个字节 * length:读取元素的编码值 A 65 * 如果没有元素可以读 返回-1 * * int length = fis.read(byte[]); * length:读取到数组内有价值元素数量 * 如果没有元素可以读 返回 -1 * */ public class InputStreamTest { @Test public void test06() throws IOException { // 1.创建流对象 FileInputStream fis = new FileInputStream("D:/is1.txt"); // 2.读取数据 byte[] bs = new byte[5]; int length; while ((length = fis.read(bs)) != -1){ // 3. 展示数据 String s = new String(bs, 0, length); System.out.println("s = " + s); } // 4.关闭资源 fis.close(); } @Test public void test05() throws IOException { // 1.创建流对象 FileInputStream fis = new FileInputStream("D://is1.txt"); // 2.读取数据 循环读取数据 byte[] bs = new byte[54]; int length = fis.read(bs); while (length != -1){ // 3.展示数据 String s = new String(bs, 0, length); System.out.println("s = " + s); // 需要再读一次赋值给length,不然会死循环 length = fis.read(bs); } // 4.关闭资源 fis.close(); } @Test public void test04() throws IOException { // 1.创建字节输入流对象 FileInputStream fis = new FileInputStream("D:/is1.txt"); // 2.读取数据 一次读取一个数组 byte[] bs = new byte[4]; // 默认值为0 System.out.println("读取前 = " + Arrays.toString(bs)); // 读取前 = [0, 0, 0, 0] int length = fis.read(bs); // 3.展示数据 length = 4, 第一次读取 = [65, 66, 67, 68],ABCD System.out.println("length = " + length + ", 第一次读取 = " + Arrays.toString(bs) + "," + new String(bs)); // 继续读取下一次 length = fis.read(bs); // length = 4, 第一次读取 = [69, 70, 71, 72],EFGH System.out.println("length = " + length + ", 第一次读取 = " + Arrays.toString(bs) + "," + new String(bs)); // 继续读取下一次 此时length只有3 表示只有3个有价值的元素 最后一个元素是上一批读取缓存时的元素 length = fis.read(bs); // System.out.println("length = " + length + ", 第一次读取 = " + Arrays.toString(bs) + "," + new String(bs)); // // length = 3, 第一次读取 = [73, 74, 75, 72],IJKH System.out.println("length = " + length + ", 第一次读取 = " + Arrays.toString(bs) + "," + new String(bs, 0, length)); // 已经没有内容读取了 返回-1 length = fis.read(bs); System.out.println("length = " + length); // -1 // 4.关闭资源 fis.close(); } @Test public void test03() throws IOException { // 1.创建字节输入流对象 FileInputStream fis = new FileInputStream("D:/is.txt"); // 2.读取数据 int length = 10; // 先读取一次且赋值 再判断是否读取完成 while ((length = fis.read()) != -1){ // 3.展示数据 System.out.println(length + "--->" + (char) length); } } @Test public void test02() throws IOException { // 1.创建字节输入流对象 FileInputStream fis = new FileInputStream("D:/is.txt"); // 2.开始读取数据 int length = fis.read(); // 如果length等于-1表示文件读取完毕 while (length != -1){ // 3.展示数据 System.out.println(length + "--->" + (char)length); // 需再读一次,重新赋值给length 不然会死循环 length = fis.read(); } // 4.关闭资源 fis.close(); } @Test public void test01() throws IOException { // 1.创建字节输入流对象 InputStream is = new FileInputStream("D://is.txt"); // 2.读取数据 一次读取一个字节 int length = is.read(); // 3.展示数据 System.out.println("length = " + length + "," + (char)length); // length = 65,A // 继续读下一个字节 length = is.read(); System.out.println("length = " + length + "," + (char)length); // length = 66,B // 继续都下一个字节 若没有字节则返回-1 length = is.read(); System.out.println("length = " + length); // length = -1 // 4.关闭资源 is.close(); } }
3.5 字节流练习:图片复制
package com.atguigu.copy;
import org.junit.Test;
import java.io.*;
/*
完成图片复制
try with resources
try(流对象){
业务代码
}catch(异常类型){
}
*/
public class Copy1 {
@Test
public void test01() throws IOException {
// 创建字节输入流
InputStream is = new FileInputStream("D:\\g5.png");
// 创建字节输出流
OutputStream os = new FileOutputStream("D:\\g11.png");
// 读取数据
byte[] bs = new byte[1024];
int length;
while ((length = is.read(bs)) != -1){
// 写入数据
os.write(bs, 0, length);
}
System.out.println("复制完成");
is.close();
os.close();
}
}
4 字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
4.1 字符输入流【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
4.2 FileReader类
java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
小贴士:
- 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
eclipse中默认GBK,idea中默认UTF-8
- 字节缓冲区:一个字节数组,用来临时存储字节数据。
-
构造方法
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
- 构造举例,代码如下:
package com.atguigu.readerandwriter; import org.junit.Test; import java.io.*; import java.util.Arrays; /** * 字节输入流读取数据 * 一次读取一个字符 * 一次读取一个字符组 * * 没有数据 返回-1 * */ public class ReaderTest { @Test public void test04() throws IOException { // 1.创建字符输入流对象 FileReader reader = new FileReader("D:/zuowen.txt"); // 2.定义一个字符数组 char[] cs = new char[100]; // 3.读取数据 int length; // 4.显示数据 while ((length = reader.read(cs)) != -1){ String s = new String(cs, 0, length); System.out.println(s); } // 5.关闭资源 } @Test public void test03() throws IOException { // 1.创建字符输入流对象 FileReader reader = new FileReader("D://reader.txt"); // 2.创建数组 char[] cArr = new char[5]; System.out.println("读取前:" + Arrays.toString(cArr)); // 3.读取数组 int length = reader.read(cArr); System.out.println("length1 = " + length + "," + Arrays.toString(cArr) + "," + new String(cArr)); // length1 = 5,[全, 额, 全, 欧, 杰],全额全欧杰 length = reader.read(cArr); System.out.println("length2 = " + length + "," + Arrays.toString(cArr) + "," + new String(cArr)); // length2 = 5,[佛, 吴, 佩, 孚, 就],佛吴佩孚就 length = reader.read(cArr); // length为4 有价值的元素只有4个 // System.out.println("length3 = " + length + "," + Arrays.toString(cArr) + "," + new String(cArr)); // length3 = 4,[和, 我, 发, 货, 就],和我发货就 // 使用此构造方法即可 System.out.println("length3 = " + length + "," + Arrays.toString(cArr) + "," + new String(cArr, 0, length)); // length3 = 4,[和, 我, 发, 货, 就],和我发货 // 4.关闭资源 reader.close(); } @Test public void test02() throws IOException { // 1.创建字符输入流对象 Reader reader = new FileReader("D:/zw.txt"); // 2.读取数据 读取一个字符 int read = reader.read(); // 3.展示数据 System.out.println("read = " + read + "," + (char)read); // read = 23385,孙 // 继续往下读取一个字符 read = reader.read(); System.out.println("read = " + read + "," + (char)read); // read = 23578,尚 read = reader.read(); System.out.println("read = " + read + "," + (char)read); // read = 39321,香 // 没数据可读了,返回-1 read = reader.read(); System.out.println("read = " + read); // read = -1 // 4.关闭资源 reader.close(); } @Test public void test01() throws IOException { // 1.创建字节输入流对象 InputStream is = new FileInputStream("D:/zw.txt"); // 2.读取数据 读取一个字节 int read = is.read(); // 3.展示数据 读取的是中文文本 1个中文对应3个字节 所以显示乱码 System.out.println("read = " + read + "," + (char)read); // read = 229,å // 4.关闭资源 is.close(); } }
4.3 字符输出流【Writer】
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。void write(String str)
写入字符串。void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。
4.4 FileWriter类
java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
-
构造方法
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
示例:
package com.atguigu.readerandwriter; import org.junit.Test; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class WriterTest { @Test public void test01() throws IOException { // 1.创建字符输出流对象 Writer writer = new FileWriter("D:/w.txt", true); // 2.写入数据 // 写入int类型 会自动转换为字符 writer.write(61231235); // 写入String类型 writer.write("你好世界/n"); writer.write("HelloWorld"); char[] cs = {'安', '其', '啦', '吕', '布'}; // 写入char[]类型 writer.write(cs); writer.write("\n"); writer.write(cs, 0 , 3); // 写入从cs数组中下标为0~2的元素 // 3.关闭资源 writer.close(); } }
5 缓冲流
缓冲流,也叫高效流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小为8k的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
5.1 字节缓冲输出流
-
构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
package com.atguigu.buffer;
import org.junit.Test;
import java.io.*;
/**
* BufferedInputStream:字节缓冲输入流
* BufferedInputStream 底层有一个缓冲区 byte[] 8192
* 缓冲区大小可以自己定义
*
* BufferedOutputStream
* 底层有一个缓冲区 byte[] 8192
* 缓冲区大小可以自己定义
* */
public class BufferedInputStreamTest {
@Test
public void test01() throws FileNotFoundException {
// 1.创建字节缓冲输入流
InputStream is = new FileInputStream("D:/w.txt");
// 创建字节缓冲输入流对象 默认缓存为 byte[] 8192
BufferedInputStream bis = new BufferedInputStream(is);
// 创建字节缓冲输入流对象 自定义缓冲区大小 byte[] 81920
new BufferedInputStream(is, 81920);
}
@Test
public void test02() throws IOException {
OutputStream os = new FileOutputStream("D:/os.txt");
BufferedOutputStream bos = new BufferedOutputStream(os);
// 写入在缓冲区 还未保存到磁盘中 无法在文件中查看到写入的内容
bos.write(65);
bos.write(65);
// 使用flush(),将缓存区的内容刷到硬盘中 即可在文件中查看到写入的内容
// bos.flush();
// 或使用close() 关闭资源 也可以将缓存区的内容保存到硬盘中 即可在文件中查看到写入的内容
bos.close();
}
}
5.2 字节缓冲输入流
package com.atguigu.buffer;
import org.junit.Test;
import java.io.*;
/**
* BufferedReader 字符缓冲输入流: char[] 8192
* readLine() 一次读取一行数据 没有数据返回 null
*
* BufferedWriter 字符缓冲输出流: char[] 8192
* bw.newLine() 换行
* */
public class ReaderAndWriterBuffer {
@Test
public void test03() throws IOException {
FileWriter fileWriter = new FileWriter("D:/bw.txt");
BufferedWriter bw = new BufferedWriter(fileWriter);
bw.write("你好");
// 特有方法 换行写入
bw.newLine();
bw.write("世界");
// 关闭资源
bw.close();
}
@Test
public void test02() throws IOException {
FileReader reader = new FileReader("D:/zuowen.txt");
BufferedReader br = new BufferedReader(reader);
String line;
// 循环读取 读取结束返回null
while ((line = br.readLine()) != null){
System.out.println(line);
}
// 关闭资源
br.close();
}
@Test
public void test01() throws IOException {
FileReader reader = new FileReader("D:/zuowen.txt");
BufferedReader br = new BufferedReader(reader);
// 特有方法 读取一行 读完返回null
String s = br.readLine();
System.out.println("s = " + s);
br.close();
}
}
6.数据流
package com.atguigu.data;
import org.junit.Test;
import java.io.*;
/**
* 数据流:在程序中直接处理Java的基础数据类型
*
* */
public class DataStreamTest {
@Test
public void test01() throws IOException {
// 创建流对象
OutputStream os = new FileOutputStream("D:/dos.txt");
DataOutputStream dos = new DataOutputStream(os);
// 写入数据
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("邮政");
// 关闭资源
dos.close();
}
@Test
public void test02() throws IOException {
// 创建流对象
InputStream is = new FileInputStream("D:/dos.txt");
DataInputStream dis = new DataInputStream(is);
// 读取数据
double readDouble = dis.readDouble();
System.out.println("readDouble = " + readDouble); // readDouble = 3.14
boolean readBoolean = dis.readBoolean();
System.out.println("readBoolean = " + readBoolean); // readBoolean = true
String readUTF = dis.readUTF();
System.out.println("readUTF = " + readUTF); // readUTF = 邮政
// 关闭资源
dis.close();
}
}
7.转换流
7.1 字符编码和字符集
字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(能看懂的)–字节(看不懂的)
解码:字节(看不懂的)–>字符(能看懂的)
-
字符编码
Character Encoding
: 就是一套自然语言的字符与二进制数之间的对应规则。编码表:生活中文字和计算机中二进制的对应规则
字符集
- 字符集
Charset
:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
- ASCII字符集 :
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
- 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。
- ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
- ISO-8859-1字符集:
- 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
- ISO-8859-1使用单字节编码,兼容ASCII编码。
- GBxxx字符集:
- GB就是国标的意思,是为了显示中文而设计的一套字符集。
- GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
- GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
- Unicode字符集 :
- Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
- 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
- UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
- 128个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
7.2 编码引出的问题
在Eclipse中,使用FileReader
读取项目中的文本文件。由于Eclipse的设置UTF-8编码但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
public class ReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("E:\\File_GBK.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
fileReader.close();
}
}
输出结果:
���
那么如何读取GBK编码的文件呢?
7.3 InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
1.构造方法
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
// 1.创建字节输入流
InputStream is = new FileInputStream("D:/reader.txt");
// 2.创建转换流 字节输入流转化为gbk格式的字符输入流
InputStreamReader isr = new InputStreamReader(is, "gbk");
2.指定编码读取
package com.atguigu.io.convert;
import org.junit.Test;
import java.io.*;
/**
* InputStreamReader:
* 将字节输入流---> 字符输入流
* 编码不一致会导致中文乱码
* */
public class InputStreamReaderTest {
@Test
public void test02() throws IOException {
// 1.创建字节输入流
InputStream is = new FileInputStream("D:/reader.txt");
// 2.创建转换流 字节输入流转化为gbk格式的字符输入流
InputStreamReader isr = new InputStreamReader(is, "gbk");
// 3.读取数据 这样就不会乱码
int read = isr.read();
System.out.println("read = " + read + "," + (char)read); // read = 20840,全
// 4.关闭资源
isr.close();
}
@Test
public void test01() throws IOException {
// 1.创建字符输入流 文件编码格式为GBK
Reader reader = new FileReader("D:/reader.txt");
// 2.读取数据
int read = reader.read();
// 读取显示乱码 原因:idea使用的是utf-8格式 和 文件的gbk格式不一致
System.out.println("read = " + read + (char)read); // read = 555ȫ
// 3.关闭资源
reader.close();
}
}
7.4 OutputStreamWriter类
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
1.构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
// 1.创建字节输出流
OutputStream os = new FileOutputStream("D:/os.txt");
// 2.创建转换流 写入的是gbk格式
OutputStreamWriter osw = new OutputStreamWriter(os, "gbk");
2.指定编码写出
package com.atguigu.io.convert;
import org.junit.Test;
import java.io.*;
/**
* InputStreamReader:
* 将字节输入流 ---> 字符输入流
* OutputStreamWriter
* 将字节输出流---> 字符输出流
* */
public class OutputStreamWriterTest {
@Test
public void test02() throws IOException {
// 1.创建字节输出流
OutputStream os = new FileOutputStream("D:/os.txt");
// 2.创建转换流 写入的是gbk格式
OutputStreamWriter osw = new OutputStreamWriter(os, "gbk");
// 3.写入数据
osw.write("床前明月光");
// 4.关闭资源
osw.close();
}
@Test
public void test01() throws IOException {
// 1.创建字符输出流
Writer writer = new FileWriter("D:/w.txt");
// 2.写入数据 默认写入的是utf-8格式
writer.write("你好 世界");
// 3.关闭资源
writer.close();
}
}
7.5 转换流理解图解
转换流是字节与字符间的桥梁!
7.6 练习
package com.atguigu.io.convert;
/**
* 单元测试默认不支持 键盘输入
* help ---> 倒数第5个 edit customer vm options ---> -Deditable.java.test.console=true
* 重启idea
* */
import org.junit.Test;
import java.io.*;
import java.util.Scanner;
public class Test1 {
@Test
public void test03() throws IOException {
// System.out.println("你好世界");
PrintStream out = System.out;
// 自定义输出到控制台
OutputStreamWriter writer = new OutputStreamWriter(out);
writer.write("你好帅啊!!|");
writer.close();
}
@Test
public void test02() throws IOException {
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(isr);
System.out.println("请你输入一句话:");
String line = reader.readLine();
System.out.println("line = " + line);
}
@Test
public void test01(){
Scanner in = new Scanner(System.in);
System.out.println("请你输入一个数字");
int num = in.nextInt();
System.out.println("num = " + num);
}
}
8 对象流与序列化
12.8.1 概述
Java 提供了一种对象序列化的机制。用字节序列可以表示一个对象,该字节序列包含该对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
12.8.2 ObjectStream类
- 该类必须实现
java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。- 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现
Serializable
接口
- 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用**
transient
** 关键字修饰。(比如网络中传输时,考虑安全因素银行卡字段可以使用transient不进行序列化) - 静态变量的值不会序列化(静态变量的值不属于某个对象的数据,而是属于类的数据)
package com.atguigu.io.objectstream;
import org.junit.Test;
import java.io.*;
/**
* 对象的序列化:
* 将对象存储到磁盘中
* 对象的反序列化:
* 将存储在磁盘中的对象读取到程序
* 注意:
* 1.想要进行序列化 对象所在的类 必须实现Serializable接口
* 2.如果某些属性不想被序列化 可以在属性前
* static
* transient修饰
* 3.序列化版本号写出和读入时要一致
*
* */
public class ObjectStreamTest {
@Test
public void outObj() throws IOException {
// 0.创建对象 所在类必须实现serializer接口才可以 不然会报错
Person person = new Person("李白", 20000, 18, "男");
System.out.println("写出前 = " + person);
// 1.创建对象输出流
OutputStream os = new FileOutputStream("D:/os.txt");
ObjectOutputStream oos = new ObjectOutputStream(os);
// 2.写入对象
oos.writeObject(person);
// 3.关闭资源
oos.close();
}
@Test
public void readObj() throws IOException, ClassNotFoundException {
// 1.创建对象输入流
InputStream is = new FileInputStream("D:/os.txt");
ObjectInputStream ois = new ObjectInputStream(is);
// 2.读取对象
Object object = ois.readObject();
// object转成person
Person p1 = (Person)object;
// 3.展示对象
System.out.println("读取后 = " + p1); // 读取后 = Person{name='李白', salary=20000.0, age=18, gender='男'}
// 4.关闭资源
ois.close();
}
}
package com.atguigu.io.objectstream;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 81586813266542873L;
private String name;
private static double salary;
private transient int age; // 瞬时的
private String gender;
public Person() {
}
public Person(String name, double salary, int age, String gender) {
this.name = name;
this.salary = salary;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", salary=" + salary +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
9 打印流与标准输入输出流
9.1打印流PrintStream与PrintWriter
打印流只有输出没有输入
-
构造方法
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的字节打印流。public PrintWriter(String fileName)
:使用指定的文件名创建一个新的字符打印流。PrintWriter(OutputStream out, boolean autoFlush)
:基于字符输出流创建一个自动刷新的字符打印流- …
示例如下:
PrintStream ps = new PrintStream("ps.txt"); PrintWriter pw = new PrintWriter("pw.txt"); PrintWriter pw2 = new PrintWriter(new FileOutputStream("pw2.txt"),true);
-
打印流输出
package com.atguigu.io.other; import org.junit.Test; import java.io.FileNotFoundException; import java.io.PrintStream; import java.io.PrintWriter; public class PrintTest { @Test public void test01() throws FileNotFoundException { // 创建字节打印流 PrintStream ps = new PrintStream("D:/ps.txt"); ps.println(65); ps.println("A"); ps.println("B"); ps.close(); } @Test public void test02() throws FileNotFoundException { // 创建字符打印流 PrintWriter pw = new PrintWriter("D:/pww.txt"); pw.println(66); pw.println("B"); pw.println("B"); pw.close(); } }
9.2 标准输入/输出流
System类中有三个属性字段:
Modifier and Type | Field and Description |
---|---|
static PrintStream | err The “standard” error output stream. |
static InputStream | in The “standard” input stream. |
static PrintStream | out The “standard” output stream. |
System.in 标准输入流,本质是一个字节输入流,默认接受键盘录入的数据(不要用Junit单元测试,键盘录入)。
System.out 标准输出流,本质是一个字节输出流,默认输出数据到控制台。
既然是流对象,我们就可以玩一个"小把戏",改变标准输出流的流向。
package com.atguigu.io.other;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class SystemTest {
@Test
public void test01(){
System.out.println("111");
System.out.println("111");
System.out.println("111");
System.err.println(666);
}
@Test
public void test02() throws FileNotFoundException {
PrintStream ps = new PrintStream("D:/out.txt");
// 改变输出流流向 改成输出到文件中
System.setOut(ps);
System.out.println(999);
}
}
10.JDK1.7之后引入新try…catch
语法格式:
try(需要关闭的资源对象的声明){
业务逻辑代码,可能发生异常的代码
}catch(异常类型 e){
处理异常代码
}catch(异常类型 e){
处理异常代码
}
....
它没有finally,也不需要程序员去关闭资源对象,无论是否发生异常,都会关闭资源对象
示例代码:
@Test
public void test03() {
//从d:/1.txt(GBK)文件中,读取内容,写到项目根目录下1.txt(UTF-8)文件中
try(
FileInputStream fis = new FileInputStream("d:/1.txt");
InputStreamReader isr = new InputStreamReader(fis,"GBK");
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
){
String str;
while((str = br.readLine()) != null){
bw.write(str);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
11.IOUtils工具类
IOUtils是Apache出品的一个方便IO操作的工具类,简化了IO流的读、写、复制及关闭流等操作(使用前需要导包)
package com.atguigu.io.other;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import java.io.*;
public class IOUtilsTest {
@Test
public void test01() throws IOException {
// 使用工具类copy文件
IOUtils.copy(new FileInputStream("D:/ps.txt"), new FileOutputStream("D:/ps1.txt"));
}
}