文章目录
I/O
IO体系:
按照流的流向分:输入流和输出流;
按照操作单元分:字节流和字符流;
Java IO流共涉及40多个类,IO流中的40多个类都是从以下4个抽象类基类中派生出来的:
- InputStream / Reader: 从外部输入到内存,所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream Writer: 从内存输出到外部,所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作对象分类结构图:
图片来源:https://blog.csdn.net/ThinkWon/article/details/104390612
1 Java中描述文件的类——File
在java.io 包
之中,用File 类
来对文件进行操作(创建、删除、取得信息等)。
描述一份文件的两类信息:
1) 元信息:文件名、路径、创建日期、权限等
2 )内容数据
(1)java.io.File 类常用到两个构造方法:
public File(String pathname)
:创建指定路径文件对象
public File(String parent, String child)
:可将路径分为父路径和子路径
(2)File类的常用方法
public boolean exists()
:指定路径中的文件或者目录是否存在
public boolean isDirectory()
:判定一个文件是否是目录
public boolean isFile()
:判定是否是文件
public boolean delete()
:删除文件
public boolean createNewFile() throws IOException
:创建一个新文件
public boolean mkdir()
:创建一个空目录
public boolean mkdirs()
:创建目录(无论有多少级父目录,都会创建)
public String getParent()
:取得父路径
public File getParentFile()
:取得父File对象
public long length()
:取得文件大小(字节)
public long lastModified()
:最后一次修改日期
public File[] listFiles()
:列出一个目录指定的全部组成
(3)File代码演示
1)传入路径,构建文件对象:
String path = "E:\\world\\hello.txt";
File file = new File(path);
System.out.println(file);//E:\world\hello.txt
2)将路径分为父路径和子路径:
String parent = "E:\\hello";
String path = "hello.txt"";
File file = new File(parent, path);
System.out.println(file);//E:\hello\hello.txt
// 获取常见的属性
System.out.println(file.exists());
System.out.println(file.getAbsolutePath());
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.isAbsolute());//是否为绝对路径
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.isHidden());
System.out.println(file.canRead());
System.out.println(file.canWrite());
System.out.println(file.canExecute());
注意:
- 路径可以是相对路径也可以是绝对路径,hello.txt不存在也可以构建出来,构建的是一个抽象的文件对象。
3)文件的创建:
// 演示文件的创建(普通文件)
String parent = "E:\\hello\\directory";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
boolean success = file.createNewFile();
if (success) {
System.out.println(filename + " 创建成功");
} else {
System.out.println(filename + " 已经存在");
}
4)创建文件夹
// 演示文件夹的创建过程
String parent = "E:\\hello\\world";
String filename = "noExits\\newDirectory";
File file = new File(parent, filename);
System.out.println(file.exists());
// mkdir : make directory
//boolean success = file.mkdir();
// mkdirs: 会把需要创建的中间文件夹一并创建出来
boolean success = file.mkdirs();
if (success) {
System.out.println(filename + " 文件夹创建成功");
} else {
System.out.println(filename + " 文件夹原来就存在");
}
注意:
- 创建文件时,如果路径不存在会抛异常
- mkdir创建文件夹时,如果路径不存在会返回false
(file.mkdir()返回false)
; - mkdirs创建文件夹时,如果路径不存在会将中间缺失的路径文件夹补全
5)删除文件
file.delete()
String parent = "E:\\hello";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
// 只能删除一个空的文件夹
boolean delete = file.delete(); // 不是移动到回收站,而是直接删除
if (delete) {
System.out.println("删除了");
} else {
System.out.println("不存在");
}
file.deleteOnExit()
String parent = "E:\\world";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
file.deleteOnExit();// 也是把文件删掉,但不是马上删,而是等 JVM 进程结束后才删除
Scanner scanner = new Scanner(System.in);
System.out.println("只要不按回车,就不会删除");
scanner.nextLine();
注意:
- delete()、deleteOnExit()只能删除空的文件夹
- deleteOnExit():JVM结束运行后后才删除
6)获取文件列表
String path = "E:\\world\\hello";
File dir = new File(path);
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
System.out.println(file.getName());
}
}
注意:
- files==null表示发生了路径错误等IO错误
- files.length == 0表示文件列表为空
- 实际项目部署环境可能与开发环境不同。windows 下使用的路径分隔符是\ ,而Unix/Linux 系统下使用的是/。所以在使用路径分隔符时都会采用File类的一个常量public static final String separator 来描述 System.out.println(File.separator); \ 。
- 文件部分异常,常见为IOException
(4)文件夹的遍历(树)
深度优先的遍历(前中后序遍历): 用栈(手动栈,递归栈)
广度优先的遍历(层序遍历): 用队列
1)深度优先遍历
//使用递归栈实现深度优先队列
private static void travelDepth(File node) {
System.out.println(node.getAbsolutePath());
// 判断 node 是不是目录
if (node.isDirectory()) {
File[] files = node.listFiles();
if (files != null) {
for (File file : files) {
travelDepth(file);
}
}
}
}
是叶子节点直接打印,不是叶子节点继续递归调用。
2)广度优先遍历:
//队列先进先出,实现广度优先遍历
private static void travelBroad(File root) {
Queue<File> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
File front = queue.remove();
System.out.println(front.getAbsolutePath());
if (front.isDirectory()) {
File[] files = front.listFiles();
if (files != null) {
for (File file : files) {
queue.add(file);
}
}
}
}
}
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;
public class HowScanDirectory {
public static void main(String[] args) {
File root = new File("E:\\HelloWorld\\hello");
travelDepth(root);
travelBroad(root);
}
}
(5)相对路径:进程启动时的路径
绝对路径:从根目录出发
相对路径:以当前文件夹出发,相对与某个基准目录的路径。
相对路径其实是相对于工作路径
import java.io.File;
import java.io.IOException;
public class RelativePathDemo {
public static void main(String[] args) throws IOException {
//获取工作路径
String workingDirectory = System.getProperty("user.dir");
System.out.println("工作路径,就是进程启动时所在路径");
System.out.println(workingDirectory);
String filename = "hello.txt";
File file = new File(filename);
System.out.println(file.getAbsolutePath());
file.createNewFile();
}
}
2 流
流: 在 Java中所有数据都是使用流读写的。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
流的作用: 为数据源和目的地建立一个输送通道。
按照流向: 输入流;输出流
输入流InputStream/Reader: 从各种输入设备,以二进制流(字节流)的形式,输入到内存中。
输出流OutputStream/Writer: 从内存中,以二进制流(字节流)的形式,输出到各种外设中。
按照处理数据的单位: 字节流(8位的字节);字符流(16位的字节)
字节流: 数据流中最小的数据单元是字节。InputStream、OutputStream。
字符流: 数据流中最小的数据单元是字符,Java中的字符是Unicode编码,一个字符占用两个字节。Reader、Writer。
(1)字节流
InputStream
——输入字节流
OutputStream
——输出字节流
1)输入字节流
public class FileInputStream extends InputStream {}
FileInputStream
:
- 从文件系统中的某个文件中获得输入字节。
- 用于读取诸如图像数据之类的原始字节流。
FileInputStream(File file)
:通过打开与实际文件的连接创建一个FileInputStream ,该文件由文件系统中的File 对象file 命名
FileInputStream(String name)
:通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name 命名。
read()一次只读一个字节,当全部读完时返回一个-1:
InputStream is = new FileInputStream("用于读的文件.txt");
int b;
while ((b = is.read()) != -1) {
System.out.println(b);//ACSII码
}
is.close();
read()中传入数组进行读取:
try(InputStream is = new FileInputStream("hello.txt")){
byte[] buf = new byte[1024];
int len;
while((len = is.read(buf))!=-1){
for(int i=0;i<len;i++){
System.out.println(buf[i]);//ASCII码
char c =(char)buf[i];
System.out.println(c);
}
}
}
read返回值:
@return the total number of bytes read into the buffer,
or<code>-1</code> if there is no more data because the
end of the stream has been reached.
2)输出字节流
文件输出流是用于将数据写入到输出流File 或一个FileDescriptor
public class FileOutputStream extends OutputStream
FileOutputStream(File file)
:创建文件输出流以写入由指定的File 对象表示的文件。
FileOutputStream(String name)
:创建文件输出流以指定的名称写入文件
一次写入一个字节,及以字节数组的形式写入:
try(OutputStream os = new FileOutputStream(“输出.txt”)){
os.write('h');
os.write('\r');
os.write('\n');
String s ="world\r\n";
byte[] bytes = s.getBytes();
os.write(bytes);
os.flush();
}
注意:
- 在进行写入时,会覆盖文件中原有的内容。
- 输出流为了减少IO的次数可能进行了缓冲,即没有直接写到硬盘中,而是写到内存中。所以用
flush()
保证了所有的缓存数据都写到内存中。 在执行写操作时,必须使用flush()。 - 在调用
os.close()
;时内部也会调用os.flush() - \r回到行首 Carriage Return CR; \n换行 Line Feed LF
- 在Windox上,CRLF合起来才是换行; Linux上,LF就是换行; Java中直接采用%n, JVM会根据不同的操作系统进行调整。
(2)字符和字节转换
字节——字符(解码)
字符——字节(编码)
输入字节流转字符需要解码,通过传入合适的字符集,对字节进行解码。 String s = new String(buf, 0, len, charset);
//这里对文件中的字节进行解码,charset为指定字符集
utf8.txt:
为utf8编码的文档
gb18030.txt:
为gb18030编码的文档
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("utf8.txt")) {
readContent(is, "UTF-8");
}
try (InputStream is = new FileInputStream("gb18030.txt")) {
readContent(is, "GB18030");
}
}
private static void readContent(InputStream is, String charset) throws IOException {
byte[] buf = new byte[8192];
int len;
while ((len = is.read(buf)) != -1) {
for (int i = 0; i < len; i++) {
System.out.println(buf[i]);
}
String s = new String(buf, 0, len, charset); //这里对文件中的字节进行解码,charset为指定字符集
System.out.println(s);
}
}
}
输出字符转字节需要编码,通过传入合适的字符集,对字符进行编码。 byte[] bytes = s.getBytes(charset);//将字符编码
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStream {
public static void main(String[] args) throws IOException {
String s = "hello";
try (OutputStream os = new FileOutputStream("utf8.txt")) {
writeContent(os, s, "UTF-8");
}
try (OutputStream os = new FileOutputStream("gb18030.txt")) {
writeContent(os, s, "GB18030");
}
}
private static void writeContent(OutputStream os, String s, String charset) throws IOException {
byte[] bytes = s.getBytes(charset);//将字符编码
System.out.println(charset);
System.out.println(bytes.length);
os.write(bytes);
os.flush();
}
}
(3)字符流
1)输入字符流
InputStreamReader是Reader的子类,将输入的字节流转换成字符流
public class InputStreamReader extends Reader
InputStreamReader(InputStream in)
:创建一个使用默认字符集的InputStreamReader。
InputStreamReader(InputStream in,Charset cs)
:创建一个使用给定字符集的InputStreamReader
InputStreamReader isReader = new InputStreamReader(is, "UTF-8"))
isReader.read();
char[] bur=new char[1024];
isReader.read(buf);
isReader.read();返回的是字符。
下面给出一种更常用的用法,将流传入Scanner中:
public static void main() throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
try (InputStreamReader isReader = new InputStreamReader(is, "UTF-8")) {
try (Scanner scanner = new Scanner(isReader)) {
// 按行读
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
}
}
}
}
}
2)输出字符流
OutputStreamWriter是Writer的子类,将输出的字符流转换成字节流。
public class OutputStreamWriter extends Writer
OutputStreamWriter(OutputStream out)
创建一个使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out,Charset cs)
创建一个使用给定字符集的OutputStreamWriter。
将流传入到PrintWriter中,需要传入一个OutputStreamWriter对象
// 写的文本
private static void writeTest() throws IOException {
try (OutputStream os = new FileOutputStream("某篇文章.txt")) {
try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
try (PrintWriter printWriter = new PrintWriter(osWriter)) {
printWriter.println("你好世界");
printWriter.printf("哈哈");
printWriter.print("哈");
printWriter.format(" ");
printWriter.flush();
}
}
}
}
}
PrintWriter printWriter = new PrintWriter(osWriter,true)
可以自动刷新,代替printWriter.flush();
字节流对比字符流
1、 字节流操作的基本单元是字节;字符流操作的基本单元为Unicode码元。
2、 字节流在操作的时候本身不会用到缓冲区的,是与文件本身直接操作的;而字符流在操作的时候使用到缓冲区的。
3、 所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节。
4、 在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用close方法的话,不会输出任何内容。
注意:
- 图片等二进制文件,需要用InputStream和OutputStream(传入数组);
- 文本等字符形式,需要用InputStreamReader和OutputStreamWriter,其中读用Scanner的方式,写用PrintWriter的方式。
3 序列化(Serialize)与反序列化
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
什么时候需要序列化?
1、把内存中的对象状态保存到一个文件中或者数据库中时候;
2、用套接字在网络上传送对象的时候;
实现序列化的方式:
1,简单方案,放在一行以逗号分割
CSV Comma Seperate Value以逗号分割
2.JSON——JavaScript ObJect Notation
键值对的形式存储
3.XML eXtensionable Markup Language
<person>
<person name=”hot” age = 16></person>
</person>
4.JAVA中的序列化,二进制格式的序列:占用空间更小,存储更小,传输更快要想序列化,需要实现Serializable接口
ObjectOutputStream
代表对象输出流:它的writeObject(Object obj) 方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream
代表对象输入流:它的readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
import java.io.*;
public class SerializeDemo {
// 声明实现 Serializable 接口
// 声明之后,就拥有了可以序列号和反序列化的功能了
static class Person implements Serializable {
String name;
int age;
}
}
写对象
ObjectOutputStream oos = new ObjectOutputStream(os)
,oos.writeObject(p)
//写对象
public static void main(String[] args) throws IOException {
Person p = new Person();
p.name = "小明"; p.age = 84;
Person q = new Person();
q.name = "小红"; q.age = 8;
try (OutputStream os = new FileOutputStream("找到的卧底的名单.binary")) {
try (ObjectOutputStream oos = new ObjectOutputStream(os)) {
oos.writeObject(p);
oos.writeObject(q);
oos.flush();
}
}
}
读对象
ObjectInputStream ois = new ObjectInputStream(is)
,
Person p = (Person) ois.readObject();
//读对象
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (InputStream is = new FileInputStream("找到的卧底的名单.binary")) {
try (ObjectInputStream ois = new ObjectInputStream(is)) {
Person p = (Person) ois.readObject();
System.out.println(p.name);
System.out.println(p.age);
System.out.println("======================");
Person q = (Person) ois.readObject();
System.out.println(q.name);
System.out.println(q.age);
}
}
}
注意:
- 类中的
静态变量
的值是不会被进行序列化的,transient
修饰的属性,是不会被序列化的。