15 File类与IO流
约 15980 字大约 53 分钟
2025-08-22
java.io.File
类的使用
概述
File
类及各种流,都定义在java.io
包下File
File
File
File
对象可以作为参数传递给流的构造器- 想要在
Java
程序中表示一个真实存在的文件或目录,那么必须有一个File
对象,但是Java
程序中的一个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
"
- 测试:
package io.github.weew12;
public class TestPath {
public static void main(String[] args) {
System.out.println(System.getProperty("user.dir"));
}
}
import org.junit.Test;
public class TestPath {
@Test
public void test1() {
System.out.println(System.getProperty("user.dir"));
}
}
File
使用举例
import java.io.File;
public class FileObjectTest {
public static void main(String[] args) {
// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过父级File对象和子路径字符串
File parentDir = new File("d:\\aaa");
String childFile = "bbb.txt";
File file4 = new File(parentDir, childFile);
}
@Test
public void test01() throws IOException{
// 绝对路径
File f1 = new File("d:\\atguigu\\javase\\HelloIO.java");
System.out.println("文件/目录的名称:" + f1.getName());
System.out.println("文件/目录的构造路径名:" + f1.getPath());
System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f1.getParent());
}
@Test
public void test02()throws IOException{
// 绝对路径,从根路径开始
File f2 = new File("/HelloIO.java");
System.out.println("文件/目录的名称:" + f2.getName());
System.out.println("文件/目录的构造路径名:" + f2.getPath());
System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f2.getParent());
}
@Test
public void test03() throws IOException {
// 相对路径
File f3 = new File("HelloIO.java");
System.out.println("user.dir =" + System.getProperty("user.dir"));
System.out.println("文件/目录的名称:" + f3.getName());
System.out.println("文件/目录的构造路径名:" + f3.getPath());
System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f3.getParent());
}
@Test
public void test04() throws IOException{
// 相对路径
File f5 = new File("HelloIO.java");
System.out.println("user.dir =" + System.getProperty("user.dir"));
System.out.println("文件/目录的名称:" + f5.getName());
System.out.println("文件/目录的构造路径名:" + f5.getPath());
System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f5.getParent());
}
}
- 无论该路径下是否存在文件或者目录,都不影响
File
对象的创建 windows
路径分隔符使用\
,而Java
程序中的\
表示转义字符。在Windows
中表示路径,需要用\
或直接使用/
,Java
程序支持将/
当成平台无关的路径分隔符。也可直接使用File.separator
常量值表示
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
- 当构造路径是绝对路径时,那么
getPath
和getAbsolutePath
结果一样 ; 当构造路径是相对路径时,getAbsolutePath的路径
=user.dir的路径
+构造路径
获取
public String getName()
:获取名称public String getPath()
:获取路径public String getAbsolutePath()
:获取绝对路径public File getAbsoluteFile()
:获取绝对路径表示的文件public String getParent()
:获取上层文件目录路径,若无返回null
public long length()
:获取文件长度(字节数),public long lastModified()
:获取最后一次的修改时间()
如果 File
对象代表的文件或目录存在,则File
对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(时间、类型等)为File
对象的属性赋值;否则,除了路径和名称,File
对象的其他属性将会保留默认值。
测试代码:
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class FileInfoMethods {
public static void main(String[] args) {
File file = new File("F:/aaa/FileTest.java");
System.out.println("文件构造路径 " + file.getPath());
System.out.println("文件名称 " + file.getName());
System.out.println("文件长度" + file.length());
System.out.println("文件最后修改时间" + LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), ZoneId.of("Asia/Shanghai")));
System.out.println();
File file2 = new File("F:/aaa");
System.out.println("目录构造路径 " + file2.getPath());
System.out.println("目录名称 " + file2.getName());
System.out.println("目录长度" + file2.length());
System.out.println("目录最后修改时间" + LocalDateTime.ofInstant(Instant.ofEpochMilli(file2.lastModified()), ZoneId.of("Asia/Shanghai")));
}
}
输出结果:
文件构造路径 F:\aaa\FileTest.java
文件名称 FileTest.java
文件长度465
文件最后修改时间2024-12-04T11:04:02.147
目录构造路径 F:\aaa
目录名称 aaa
目录长度0
目录最后修改时间2024-12-04T11:04:38.326
列出
public String[] list()
:返回一个String
数组,表示该File
目录中的所有子文件或目录public File[] listFiles()
:返回一个File
数组,表示该File目录中的所有的子文件或目录
测试代码:
import java.io.File;
import java.util.Arrays;
public class DirListFiles {
public static void main(String[] args) {
File file = new File("F:/aaa");
// list()
String[] list = file.list();
System.out.println(Arrays.toString(list));
// listFiles()
File[] files = file.listFiles();
System.out.println(files);
}
}
输出结果:
[FileTest.java]
[Ljava.io.File;@7291c18f
File
类的
public boolean renameTo(File dest)
:把文件重命名为指定的文件路径
测试代码:
import java.io.File;
public class FileRename {
public static void main(String[] args) {
File renameFile = new File("F:/aaa/FileTest.java");
File toRenameFile = new File("F:/aaa/FileTest1.java");
boolean success = renameFile.renameTo(toRenameFile);
System.out.println(success ? "重命名成功" : "重命名失败");
}
}
的方法
public boolean exists()
:此File
表示的文件或目录是否实际存在public boolean isDirectory()
:此File
表示的是否为目录public boolean isFile()
:此File
表示的是否为文件public boolean canRead()
:判断是否可读public boolean canWrite()
:判断是否可写public boolean isHidden()
:判断是否隐藏
测试代码:
import java.io.File;
public class FileIs {
public static void main(String[] args) {
// 文件
File file1 = new File("F:/aaa/FileTest1.java");
// 目录
File dir1 = new File("F:/aaa");
// 文件或目录是否存在
System.out.println(file1.getName() + (file1.exists() ? "存在" : "不存在"));
System.out.println(dir1.getName() + (dir1.exists() ? "存在" : "不存在"));
// 是否为目录
System.out.println(file1.getName() + (file1.isDirectory() ? "是目录" : "不是目录"));
System.out.println(dir1.getName() + (dir1.isDirectory() ? "是目录" : "不是目录"));
// 是否为文件
System.out.println(file1.getName() + (file1.isFile() ? "是文件" : "不是文件"));
System.out.println(dir1.getName() + (dir1.isFile() ? "是文件" : "不是文件"));
// 是否可读
System.out.println(file1.getName() + (file1.canRead() ? "可读" : "不可读"));
System.out.println(dir1.getName() + (dir1.canRead() ? "可读" : "不可读"));
// 是否可写
System.out.println(file1.getName() + (file1.canWrite() ? "可写" : "不可写"));
System.out.println(dir1.getName() + (dir1.canWrite() ? "可写" : "不可写"));
// 是否隐藏
System.out.println(file1.getName() + (file1.isHidden() ? "是隐藏文件" : "不是隐藏文件"));
System.out.println(dir1.getName() + (dir1.isHidden() ? "是隐藏文件" : "不是隐藏文件"));
System.out.println("==========================");
// 不存在文件
File file2 = new File("F:/aaa/FileTest2.java");
System.out.println("file2.exists(): " + file2.exists());
System.out.println("file2.isFile(): " + file2.isFile());
System.out.println("file2.isDirectory(): " + file2.isDirectory());
// 不存在目录
File dir2 = new File("F:/bbb");
System.out.println("dir2.exists(): " + dir2.exists());
System.out.println("dir2.isFile(): " + dir2.isFile());
System.out.println("dir2.isDirectory(): " + dir2.isDirectory());
}
}
输出结果:
FileTest1.java存在
aaa存在
FileTest1.java不是目录
aaa是目录
FileTest1.java是文件
aaa不是文件
FileTest1.java可读
aaa可读
FileTest1.java可写
aaa可写
FileTest1.java不是隐藏文件
aaa不是隐藏文件
==========================
file2.exists(): false
file2.isFile(): false
file2.isDirectory(): false
dir2.exists(): false
dir2.isFile(): false
dir2.isDirectory(): false
功能
public boolean createNewFile()
:创建文件,若文件存在则不创建并返回false
public boolean mkdir()
:public boolean mkdirs()
:public boolean delete()
:删除文件或文件夹Java
测试代码:
import java.io.File;
import java.io.IOException;
public class FileCreateAndDelete {
public static void main(String[] args) throws IOException {
// 文件创建 createNewFile
File createFile1 = new File("F:/aaa/FileTest1.java");
File createFile2 = new File("F:/aaa/FileTest2.java");
System.out.println(createFile1.getName() + (createFile1.exists() ? "文件存在" : "文件不存在"));
System.out.println(createFile1.getName() + (createFile1.createNewFile() ? "文件创建成功" : "文件创建失败"));
System.out.println(createFile2.getName() + (createFile2.exists() ? "文件存在" : "文件不存在"));
System.out.println(createFile2.getName() + (createFile2.createNewFile() ? "文件创建成功" : "文件创建失败"));
System.out.println();
// 目录创建 mkdir
File dir1 = new File("F:/aaa");
File dir2 = new File("F:/bbb");
System.out.println(dir1.getName() + (dir1.exists() ? "目录存在" : "目录不存在"));
System.out.println(dir1.getName() + (dir1.mkdir() ? "目录创建成功" : "目录创建失败"));
System.out.println(dir2.getName() + (dir2.exists() ? "目录存在" : "目录不存在"));
System.out.println(dir2.getName() + (dir2.mkdir() ? "目录创建成功" : "目录创建失败"));
System.out.println();
// 创建多级目录 mkdirs
File dir3 = new File("F:/aaa/bbb");
System.out.println(dir3.getAbsolutePath() + (dir3.exists() ? "目录存在" : "目录不存在"));
System.out.println(dir3.getAbsolutePath() + (dir3.mkdirs() ? "多级目录创建成功" : "多级目录创建失败"));
System.out.println();
// 文件删除
System.out.println(createFile2.getName() + (createFile2.exists() ? "文件存在" : "文件不存在"));
boolean delete = createFile2.delete();
System.out.println(delete ? (createFile2.getName() + "删除成功") : (createFile2.getName() + "删除失败"));
System.out.println(createFile2.getName() + (createFile2.exists() ? "文件存在" : "文件不存在"));
System.out.println();
// 目录删除
System.out.println(dir2.getAbsolutePath() + (dir2.exists() ? "目录存在" : "目录不存在"));
System.out.println((dir2.list().length > 0) ? "目录不为空" : "目录为空");
boolean delete1 = dir2.delete();
System.out.println((delete1 ? (dir2.getAbsolutePath() + "删除成功") : (dir2.getAbsolutePath() + "删除失败")));
}
}
输出结果:
FileTest1.java文件存在
FileTest1.java文件创建失败
FileTest2.java文件不存在
FileTest2.java文件创建成功
aaa目录存在
aaa目录创建失败
bbb目录不存在
bbb目录创建成功
F:\aaa\bbb目录存在
F:\aaa\bbb多级目录创建失败
FileTest2.java文件存在
FileTest2.java删除成功
FileTest2.java文件不存在
F:\bbb目录存在
目录为空
F:\bbb删除成功
练习
判断指定目录下是否有后缀名为.jpg
的文件,如果就输出该文件名称
public class FindFileTest {
/*
* File类提供了两个文件过滤器方法
* public String[] list(FilenameFilter filter)
* public File[] listFiles(FileFilter filter)
*/
@Test
public void test3(){
File srcFile = new File("d:\\code");
File[] subFiles = srcFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jpg");
}
});
for(File file : subFiles){
System.out.println(file.getAbsolutePath());
}
}
}
IO流原理及流的分类
Java IO
原理
Java
stream
I/O
流中的I/O
是Input/Output
的缩写,I/O
- 如:读/写文件、网络通讯等
- 输入
input
: - 输出
output
:
流的分类
java.io
- 不同分为:输入流和输出流
- 输入流:把数据的流
- 以
InputStream
、Reader
结尾
- 以
- 输出流 :把的流
- 以
OutputStream
、Writer
结尾
- 以
- 输入流:把数据的流
- 的不同分为:字节流(
8bit
)和字符流(16bit
)- 字节流 :以字节为单位,读写数据的流
- 以
InputStream
、OutputStream
结尾
- 以
- 字符流 :以字符为单位,读写数据的流
- 以
Reader
、Writer
结尾
- 以
- 字节流 :以字节为单位,读写数据的流
- 不同分为:节点流和处理流
节点流:
- 处理流:,
小结:图解
流的API
Java
的IO
流共涉及40
多个类,实际上非常规则,都是从如下4
个抽象基类派生的
(抽象基类) | 输入流 | 输出流 |
---|---|---|
InputStream | OutputStream | |
Reader | Writer |
常用的节点流
- :
FileInputStream
FileOutputStrean
FileReader
FileWriter
- :
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
(对数组进行处理的节点流,对应的不再是文件,而是内存中的一个数组)
常用的处理流
- :
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
- 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率
- :
InputStreamReader
OutputStreamWriter
- 作用:实现字节流和字符流之间的转换
- :
ObjectInputStream
、ObjectOutputStream
- 作用:
Java
- 作用:
节点流1
:FileReader
\ FileWriter
Reader
与Writer
Java
。不能操作图片、视频等非文本文件。常见的文本文件格式:.txt
.java
.c
.cpp
.py
等。
:.doc
.xls
.ppt
这些都不是文本文件
:Reader
java.io.Reader
抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中,
public int read()
:从输入流读取1
个字符。 虽然读取了1
个字符,但是会自动提升为int
类型,返回该字符的Unicode
编码值。如果已经到达流末尾了,则返回-1
public int read(char[] cbuf)
:从输入流中读取一些字符,并将它们存储到字符数组cbuf
。每次最多读取cbuf.length
个字符,返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1
public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组cbuf
中,从cbuf[off]
开始的位置存储,每次最多读取len
个字符。返回实际读取的字符个数,如果已经到达流末尾,没有数据可读,则返回-1
public void close()
:关闭此流并释放与此流相关联的任何系统资源
close()
:Writer
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地,
public void write(int c)
:写出单个字符public void write(char[] cbuf)
:写出字符数组public void write(char[] cbuf, int off, int len)
:写出字符数组的某一部分,off
:数组的开始索引,len
:写出的字符个数public void write(String str)
:写出字符串public void write(String str, int off, int len)
:写出字符串的某一部分,off
:字符串的开始索引,len
:写出的字符个数public void flush()
:刷新该流的缓冲public void close()
:关闭此流
close()
FileReader
与FileWriter
FileReader
java.io.FileReader
类
FileReader(File file)
:创建一个新的FileReader
,给定要读取的File
对象FileReader(String fileName)
:创建一个新的FileReader
,给定要读取的文件名称
案例:读取hello.txt
文件中的字符数据,并显示在控制台上
import org.junit.Test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest {
/**
* 读取方式1
*/
@Test
public void test1() throws IOException {
// 读取方式1
// 1. 创建File对象 对应某个磁盘上的文件
File file = new File("hello.txt");
// 2. 创建FileReader 将File作为参数初始化
FileReader fileReader = new FileReader(file);
// 3. 读取文件中的数据
int data;
// 每次读取一个字符 返回-1表示到达文件末尾
while ((data = fileReader.read()) != -1) {
System.out.println(((char) data));
}
// 4. 关闭相关流资源 避免出现内存泄露
fileReader.close();
}
/**
* 读取方式2
* 使用try-catch-finally结构
*/
@Test
public void test2() {
// 读取方式1
// 1. 创建File对象 对应某个磁盘上的文件
File file = new File("hello.txt");
FileReader fileReader = null;
try {
// 2. 创建FileReader 将File作为参数初始化
fileReader = new FileReader(file);
// 3. 读取文件中的数据
int data;
// 每次读取一个字符 返回-1表示到达文件末尾
while ((data = fileReader.read()) != -1) {
System.out.println(((char) data));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭相关流资源 避免出现内存泄露
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取方式3
* 使用read(char[] buff) 每次读取多个字符
*/
@Test
public void test3() {
// 读取方式1
// 1. 创建File对象 对应某个磁盘上的文件
File file = new File("hello.txt");
FileReader fileReader = null;
try {
// 2. 创建FileReader 将File作为参数初始化
fileReader = new FileReader(file);
// 字符数组 存储读取的字符
char[] chars = new char[5];
// 3. 读取文件中的数据
int len;
// 一次读取chars.length 个数的字符 返回每次读取的字符个数
while ((len = fileReader.read(chars)) != -1) {
String s = new String(chars, 0, len);
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭相关流资源 避免出现内存泄露
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
不同实现方式的类比:
FileWriter
java.io.FileWriter
类
FileWriter(File file)
:创建一个新的FileWriter
,给定要读取的File
对象FileWriter(String fileName)
:创建一个新的FileWriter
,给定要读取的文件的名称FileWriter(File file,boolean append)
:FileWriter
import org.junit.Test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTest {
@Test
public void test1() throws IOException {
// 指定文件
File file = new File("hello.txt");
// 创建FileWriter对象
FileWriter fileWriter = new FileWriter(file);
// write(int c)
fileWriter.write(97);
fileWriter.write('b');
fileWriter.write('C');
fileWriter.write(30000);
fileWriter.write('\n');
// write(char[] buff)
char[] cBuff = {'a', 'a', 'a'};
fileWriter.write(cBuff);
fileWriter.write('\n');
// write(char[] buff, int off, int len)
char[] cBuff2 = new char[10];
"hello world!".getChars(6, 12, cBuff2, 0);
fileWriter.write(cBuff2, 0, 5);
fileWriter.write('\n');
// write(String str, int off, int len)
fileWriter.write(new String("hello world!"), 0, 5);
// 关闭资源
fileWriter.close();
}
@Test
public void test2() {
// 指定文件
File file = new File("hello.txt");
// 创建FileWriter对象
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(file);
// write(int c)
fileWriter.write(97);
fileWriter.write('b');
fileWriter.write('C');
fileWriter.write(30000);
fileWriter.write('\n');
// write(char[] buff)
char[] cBuff = {'a', 'a', 'a'};
fileWriter.write(cBuff);
fileWriter.write('\n');
// write(char[] buff, int off, int len)
char[] cBuff2 = new char[10];
"hello world!".getChars(6, 12, cBuff2, 0);
fileWriter.write(cBuff2, 0, 5);
fileWriter.write('\n');
// write(String str, int off, int len)
fileWriter.write(new String("hello world!"), 0, 5);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
小结
try-catch-finally
- 对于来说,
File
类的对象必须在物理磁盘上存在,否则执行就会报FileNotFoundException
;如果传入的是一个目录则会报IOException
异常 - 对于来说,
File
类的对象是可以不存在的- 如果
File
类的对象不存在,则可以在输出的过程中自动创建File
类的对象 - 如果
File
类的对象存在- 如果调用
FileWriter(File file)
或FileWriter(File file,false)
,输出时会新建File
文件覆盖已有的文件 FileWriter(File file,true)
- 如果调用
- 如果
关于flush
刷新
FileWriter
flush()
flush()
:刷新缓冲区,close()
:先刷新缓冲区,然后通知系统释放资源,
:即便是 flush()
方法写出了数据,操作的最后还是要调用 close
方法,释放系统资源
案例:
public class FWWriteFlush {
//注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
@Test
public void test() throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
// 写出第1个字符
fw.write('刷');
fw.flush();
// 继续写出第2个字符,写出成功
fw.write('新');
fw.flush();
// 写出数据,通过close
// 写出第1个字符
fw.write('关');
fw.close();
// 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.write('闭');
fw.close();
}
}
节点流2
:FileInputStream
\ FileOutputStream
Reader
Writer
InputStream
和OutputStream
:InputStream
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。
public int read()
:从输入流读取一个字节,返回读取的字节值。虽然读取了一个字节,但是会自动提升为int
类型。如果已经到达流末尾,没有数据可读,则返回-1
public int read(byte[] b)
:从输入流中读取一些字节数,并将它们存储到字节数组b
中,每次最多读取b.length
个字节,返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1
public int read(byte[] b,int off,int len)
:从输入流中读取一些字节数,并将它们存储到字节数组b
中,从b[off]
开始存储,每次最多读取len
个字节,返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源
:close()
方法,当完成流的操作时,必须调用此方法,释放系统资源。
:OutputStream
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地,
public void write(int b)
:将指定的字节写入输出流。int
4
public void write(byte[] b)
:将b.length
字节从指定的字节数组写入输出流public void write(byte[] b, int off, int len)
:从指定的字节数组写入len
字节,从偏移量off
开始输出到此输出流public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出public void close()
:关闭此输出流并释放与此流相关联的任何系统资源
:close()
方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileInputStream
与FileOutputStream
FileInputStream
java.io.FileInputStream
类是文件输入流,从文件中读取字节
FileInputStream(File file)
:通过打开与实际文件的连接来创建一个FileInputStream
,该文件由文件系统中的File
对象file
命名FileInputStream(String name)
:通过打开与实际文件的连接来创建一个FileInputStream
,该文件由文件系统中的路径名name
命名
举例:
read.txt
文件中的内容:abcde
,使用FileInputStream
读取文件内容。
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest {
/**
* 读取方式1
*/
@Test
public void test1() throws IOException {
// 使用File对象创建流对象
File file = new File("read.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// 读取数据 返回1个字节
int read = fileInputStream.read();
System.out.println(((char) read));
read = fileInputStream.read();
System.out.println(((char) read));
read = fileInputStream.read();
System.out.println(((char) read));
read = fileInputStream.read();
System.out.println(((char) read));
read = fileInputStream.read();
System.out.println(((char) read));
// 读取到末尾 返回-1
read = fileInputStream.read();
System.out.println(read);
// 关闭资源
fileInputStream.close();
}
/**
* 读取方式2
*/
@Test
public void test2() throws IOException {
// 使用文件名创建流对象
FileInputStream fileInputStream = new FileInputStream("read.txt");
// 定义变量 保存数据
int read;
// 循环读取到末尾
while ((read = fileInputStream.read()) != -1) {
System.out.println(((char) read));
}
// 关闭资源
fileInputStream.close();
}
/**
* 读取方式3
*/
@Test
public void test3() throws IOException {
// 使用文件名创建流对象
FileInputStream fileInputStream = new FileInputStream("read.txt");
// 存储每次读取的字节数
int len;
// 存储读取出来的字节
byte[] b = new byte[3];
// 循环读取
while ((len = fileInputStream.read(b)) != -1) {
// 字节数组构造字符串 并输出
System.out.println(new String(b, 0, len));
}
// 关闭资源
fileInputStream.close();
}
}
输出1
:
a
b
c
d
e
-1
输出2
:
a
b
c
d
e
输出3
:
abc
de
FileOutputStream
java.io.FileOutputStream
类是文件输出流,用于将字节数据写出到文件
public FileOutputStream(File file)
:创建文件输出流,写出到指定File
对象表示的文件public FileOutputStream(String name)
:创建文件输出流,写出到指定name
表示的文件public FileOutputStream(File file, boolean append)
:创建文件输出流,指明是否在现有文件
举例:
import org.junit.Test;
import java.io.*;
public class FileOutputStreamTest {
/**
* 写出方式1 单个写出
* 正规写法:应该使用try-catch-finally处理异常
*/
@Test
public void test1() throws IOException {
// 使用File创建流对象
File file = new File("write.txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
// 写出数据
fileOutputStream.write(97);
fileOutputStream.write(98);
fileOutputStream.write(99);
// 关闭资源
fileOutputStream.close();
}
/**
* 写出方式2 多个写出 指定位置
*/
@Test
public void test2() throws IOException {
// 使用文件名创建流对象
FileOutputStream fileOutputStream = new FileOutputStream("write.txt");
// 字符串转为字节数组
byte[] bytes = "abcde".getBytes();
// 写出从索引2开始的两个字节: 实际写出的是cd两个字符
fileOutputStream.write(bytes, 2, 2);
// 关闭资源
fileOutputStream.close();
}
/**
* 写出方式3 追加写出
*/
@Test
public void test3() throws IOException {
// 使用文件名创建流对象
FileOutputStream fileOutputStream = new FileOutputStream("write.txt", true);
// 字符串转为字节数组
byte[] bytes = "abcde".getBytes();
// 写出从索引2开始的两个字节: 实际写出的是cd两个字符
fileOutputStream.write(bytes);
// 关闭资源
fileOutputStream.close();
}
/**
* 利用FileInputStream 和 FileOutputStream 实现文件复制
*/
@Test
public void fileCopyTest() {
FileInputStream fis = null;
FileOutputStream fos = null;
// 1. 造文件 造流
try {
fis = new FileInputStream("read.txt");
fos = new FileOutputStream("read-copy.txt");
// 2. 复制操作(读、写)
byte[] buffer = new byte[1024];
// 每次读取的字节数
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
练习: 实现图片加密操作。
提示:
代码:
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class PicEncryptTest {
/**
* 图片文件加密
* 字节 = 字节 ^ 5
*/
@Test
public void encryptPic() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("testPic.png");
fos = new FileOutputStream("testPic-enc.png");
int len;
byte[] buff = new byte[1024];
while ((len = fis.read(buff)) != -1) {
for (int i = 0; i < buff.length; i++) {
buff[i] = ((byte) (buff[i] ^ 5));
}
fos.write(buff, 0, len);
}
System.out.println("加密成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 图片文件解密
* 字节 = 字节 ^ 5
* 解释:两次相同的异或操作会抵消
*/
@Test
public void decryptPic() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("testPic-enc.png");
fos = new FileOutputStream("testPic-dec.png");
int len;
byte[] buff = new byte[1024];
while ((len = fis.read(buff)) != -1) {
for (int i = 0; i < buff.length; i++) {
buff[i] = ((byte) (buff[i] ^ 5));
}
fos.write(buff, 0, len);
}
System.out.println("加密成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流1
:缓冲流
Java API
- ,根据数据可以把缓冲流分为:
- 缓冲流:
BufferedInputStream
BufferedOutputStream
- 缓冲流:
BufferedReader
BufferedWriter
- 缓冲流:
- 缓冲流的基本原理:
- 缺省使用
8192
个字节(8KB
)的缓冲区 IO
- 缺省使用
构造器
public BufferedInputStream(InputStream in)
:创建一个新的字节型的缓冲public BufferedOutputStream(OutputStream out)
:创建一个新的字节型的缓冲
代码举例:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg"));
public BufferedReader(Reader in)
:创建一个新的字符型的缓冲public BufferedWriter(Writer out)
:创建一个新的字符型的缓冲
代码举例:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
效率测试
查询API
,缓冲流读写方法与基本的流是一致的,通过复制大文件(263MB
),测试它的效率
import org.junit.Test;
import java.io.*;
public class BufferedStreamEfficiencyTest {
/**
* 方式1: 通过 FileInputStream 和 FileOutputStream 复制非文本文件
*/
public void copyFileWithFileStream(String srcPath, String destPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 1. 造流
fis = new FileInputStream(srcPath);
fos = new FileOutputStream(destPath);
// 2. 复制文件
int len;
byte[] buff = new byte[100];
while ((len = fis.read(buff)) != -1) {
fos.write(buff, 0, len);
}
System.out.println("复制成功...");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 方式1 效率测试
*/
@Test
public void copyFileWithFileStreamTest() {
String src = "F:/aaa/深入浅出SpringBoot2.x.pdf";
String dest = "F:/aaa/copy1-深入浅出SpringBoot2.x.pdf";
long start = System.currentTimeMillis();
copyFileWithFileStream(src, dest);
long end = System.currentTimeMillis();
// 14145ms
System.out.println("copyFileWithFileStreamTest 花费时间: " + (end - start) + "ms");
}
/**
* 通过 BufferedInputStream 和 BufferedOutputStream复制文件
*/
public void copyWithBufferedStream(String srcPath, String destPath) {
FileInputStream fis = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
// 1. 造流
fis = new FileInputStream(srcPath);
bis = new BufferedInputStream(fis);
fos = new FileOutputStream(destPath);
bos = new BufferedOutputStream(fos);
// 2. 复制文件
int len;
byte[] buff = new byte[100];
while ((len = bis.read(buff)) != -1) {
fos.write(buff, 0, len);
}
System.out.println("复制成功...");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 方式2 效率测试
*/
@Test
public void copyWithBufferedStreamTest() {
String src = "F:/aaa/深入浅出SpringBoot2.x.pdf";
String dest = "F:/aaa/copy2-深入浅出SpringBoot2.x.pdf";
long start = System.currentTimeMillis();
copyWithBufferedStream(src, dest);
long end = System.currentTimeMillis();
// 10425ms
System.out.println("copyWithBufferedStream 花费时间: " + (end - start) + "ms");
}
}
字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,:
BufferedReader:public String readLine()
:读一行文字BufferedWriter:public void newLine()
:写一行行分隔符,由系统属性定义符号
public class BufferedIOLine {
@Test
public void testReadLine()throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
System.out.println(line);
}
// 释放资源
br.close();
}
@Test
public void testNewLine()throws IOException{
// 创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
// 写出数据
bw.write("尚");
// 写出换行
bw.newLine();
bw.write("硅");
bw.newLine();
bw.write("谷");
bw.newLine();
// 释放资源
bw.close();
}
}
:
- 涉及到嵌套的多个流时,如果都显式关闭的话,需要
- :
Java
BufferedReader
InputStreamReader
close()
close()
BufferedReader
的close()
方法会调用其内部Reader
的close()
方法,而这个Reader
可能又是一个InputStreamReader
,它又会调用其内部InputStream
的close()
方法
- :
练习
姓氏统计:一个文本文件中存储着姓名,现在想统计所有的姓氏在文件中出现的次数,文件格式如下:
张 三
李 四
王 小五
测试代码:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class StatisticNamesTest {
public static void main(String[] args) {
// 存储统计结果
HashMap<String, Integer> map = new HashMap<>();
// 读取文件进行统计
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader("names.txt"));
String line = null;
StringBuilder stringBuffer = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
char[] chars = line.toCharArray();
for (char aChar : chars) {
if (aChar != ' ') {
stringBuffer.append(aChar);
} else {
String s = stringBuffer.toString();
if (map.containsKey(s)) {
map.put(s, map.get(s) + 1);
} else {
map.put(s, 1);
}
stringBuffer.delete(0, stringBuffer.length());
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 输出结果
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
输出结果:
张 1
王 1
李 1
处理流2
:转换流
- 情况
1
:使用FileReader
读取项目中的文本文件,由于IDEA
设置中针对项目设置了UTF-8
编码,当读取Windows
系统中创建的文本文件时,如果Windows
系统默认是GBK
编码,则那么如何读取GBK
编码的文件呢?
import java.io.FileReader;
import java.io.IOException;
public class FileReaderEncodeProblemTest {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("file-gbk.txt");
int data;
StringBuilder stringBuffer = new StringBuilder();
while ((data = fileReader.read()) != -1) {
stringBuffer.append(((char) data));
}
fileReader.close();
System.out.println(stringBuffer.toString());
}
}
// 输出结果:����һ��GBK������ַ���
- 情况
2
:针对文本文件,使用一个字节流进行数据的读入,希望将数据显示在控制台上。
转换流的理解
- 作用:
- 具体来说:
InputStreamReader
与OutputStreamWriter
InputStreamReader
转换流
java.io.InputStreamReader
是Reader
的子类,是从字节流到字符流的桥梁。,构造器
InputStreamReader(InputStream in)
:创建一个使用默认字符集的字符流InputStreamReader(InputStream in, String charsetName)
:创建一个指定字符集的字符流
// 使用默认字符集 InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt")); // 使用指定字符集 InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
- 样例代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderTest {
public static void main(String[] args) throws IOException {
// 方式1
// 使用默认的utf-8编码
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("file-gbk.txt"));
int charData1;
StringBuilder stringBuffer = new StringBuilder();
while ((charData1 = inputStreamReader.read()) != -1) {
stringBuffer.append(((char) charData1));
}
// 字符串乱码
// 输出:����һ��GBK������ַ���
System.out.println(stringBuffer);
// 方式2
// 指定使用gbk编码
InputStreamReader inputStreamReader2 = new InputStreamReader(new FileInputStream("file-gbk.txt"), "GBK");
int charData2;
StringBuilder stringBuilder2 = new StringBuilder();
while ((charData2 = inputStreamReader2.read()) != -1) {
stringBuilder2.append(((char) charData2));
}
// 字符串正常输出
// 输出:这是一个GBK编码的字符串
System.out.println(stringBuilder2);
}
}
OutputStreamWriter
转换流
java.io.OutputStreamWriter
是Writer
的子类,是从字符流到字节流的桥梁。构造器
OutputStreamWriter(OutputStream in)
:创建一个使用默认字符集的字符流OutputStreamWriter(OutputStream in,String charsetName)
:创建一个指定字符集的字符流
// 使用默认字符集 OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt")); // 使用指定的字符集 OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
- 样例代码:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterTest {
public static void main(String[] args) throws IOException {
// 使用默认utf-8 编码字符
OutputStreamWriter outputStreamWriter1 = new OutputStreamWriter(new FileOutputStream("file-out-utf8.txt"));
outputStreamWriter1.write("你好");
outputStreamWriter1.close();
// 指定 gbk 编码字符
OutputStreamWriter outputStreamWriter2 = new OutputStreamWriter(new FileOutputStream("file-out-gbk.txt"), "GBK");
outputStreamWriter2.write("你好");
outputStreamWriter2.close();
}
}
字符编码和字符集
编码与解码
- 计算机中储存的信息都是用二进制数表示的,。按照某种规则编码解码
- 字符编码(
Character Encoding
):一套自然语言字符与二进制数之间的 - 编码表:生活中文字和计算机中二进制的对应规则
- 乱码的情况:按照
A
规则存储,同样按照A
规则解析,那么就能显示正确的文本符号;反之,A
B
- 编码(
Encode
):(人能看懂的)-->
字节(人看不懂的) - 解码(
Decode
):字节(人看不懂的)-->
(人能看懂的)
字符集
- 字符集(
Charset
):也叫编码表,。 - 计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集:
ASCII
字符集、GBK
字符集、Unicode
字符集等。 ASCII
字符集- 上个世纪
60
年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系做了统一规定,这被称为ASCII
码(American Standard Code for Information Interchange
,美国信息交换标准代码) ASCII
,主要包括:(回车键、退格、换行键等)和(英文大小写字符、阿拉伯数字和西文符号)- 基本的
ASCII
字符集:使用7
位(bits
)表示1
个字符,最前面的1
位统一规定为0
,共128
个字符。比如:SPACE
32
00100000
A
65
01000001
- :不能表示所有字符
- 上个世纪
ISO-8859-1
字符集- 拉丁码表,别名
Latin-1
,,包括:荷兰语、德语、意大利语、葡萄牙语等
- 拉丁码表,别名
GBxxx
字符集GB
GB2312
:简体中文码表,127
ASCII
127
,大约包含7000
多个简体汉字,此外还包含:数学符号、罗马希腊的字母、日文的假名等,即:常说的"全角"字符,而原来在127
号以下的那些符号就叫"半角"字符GBK
:最常用的中文码表,是在GB2312
标准基础上的扩展规范,使用了方案,共收录了21003
个汉字,完全兼容GB2312
标准,GB18030
:最新的中文码表,收录汉字70244
个,采用,每个字可以由1
个、2
个或4
个字节组成,,同时支持繁体汉字以及日韩汉字等
Unicode
字符集Unicode
,又称:统一码、标准万国码,Unicode
将世界上所有的文字用2
个字节统一进行编码,为每个字符设定唯一的二进制编码,以满足跨语言、跨平台进行文本处理的要求Unicode
的缺点:1.
英文字母只用1
个字节表示就够了,用更多的字节存储是极大的浪费2.
如何才能区别Unicode
和ASCII
?计算机怎么知道2
个字节表示1
个符号,而不是分别表示2
个符号呢?3.
如果和GBK
等双字节编码方式一样,用最高位是1
或0
表示2
个字节和1
个字节,就少了很多值无法用于表示字符,不够表示所有字符
Unicode
在很长一段时间内无法推广,直到互联网的出现。为解决Unicode
如何在网络上传输的问题,于是面向传输的众多UTF
(Unicode Transformation Format
)标准出现,具体来说有三种编码方案:UTF-8
、UTF-16
和UTF-32
UTF-8
字符集Unicode
是字符集,UTF-8
、UTF-16
、UTF-32
是三种将数字转换到程序数据的编码方案。UTF-8
就是每次8
个位传输数据,而UTF-16
就是每次16
个位,其中UTF-8
是在互联网上使用最广的一种Unicode
的实现方式- 互联网工程工作小组(
IETF
)要求所有互联网协议都必须支持UTF-8
编码。所以,我们开发Web应用,也要使用UTF-8
编码。UTF-8
是一种变长的编码方式,它使用1~4
个字节为每个字符编码,编码规则:1.
128
个US-ASCII
字符,只需1
个字节编码2.
拉丁文等字符,需要2
个字节编码3.
3
4.
其他极少使用的Unicode
辅助字符,使用4
字节编码
举例:
Unicode 符号范围(十六进制) | UTF-8 编码方式(二进制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx(兼容原来的ASCII ) |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
小结
:在中文操作系统上,ANSI
(美国国家标准学会:AMERICAN NATIONAL STANDARDS INSTITUTE: ANSI
)编码即为GBK
;在英文操作系统上,ANSI
编码即为ISO-8859-1
。
练习
把当前module
下的康师傅的话.txt
字符编码为GBK
,复制到电脑桌面目录下的寄语.txt
, 字符编码为UTF-8
。康师傅的话.txt
的文本内容:
六项精进:
(一)付出不亚于任何人的努力
(二)要谦虚,不要骄傲
(三)要每天反省
(四)活着,就要感谢
(五)积善行、思利他
(六)不要有感性的烦恼
测试代码:
import java.io.*;
public class InputStreamReaderAndOutputStreamWriterTest {
public static void main(String[] args) {
// 创建流
InputStreamReader inputStreamReader = null;
OutputStreamWriter outputStreamWriter = null;
try {
inputStreamReader = new InputStreamReader(new FileInputStream("康师傅的话.txt"), "GBK");
outputStreamWriter = new OutputStreamWriter(new FileOutputStream("寄语.txt"), "UTF-8");
// 复制文件内容
int len;
char[] buff = new char[512];
while ((len = inputStreamReader.read(buff)) != -1) {
outputStreamWriter.write(buff, 0, len);
}
System.out.println("文件复制成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (outputStreamWriter != null) {
outputStreamWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流3
和4
:数据流、对象流
数据流与对象流说明
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
String name = "巫师";
Student stu = new Student("张三",23,89);
Java
- 数据流:
DataOutputStream
DataInputStream
DataOutputStream
:允许应用程序将基本数据类型、String
类型的变量写入输出流中DataInputStream
:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String
类型的变量DataInputStream
中的方法:byte readByte()
short readShort()
int readInt()
long readLong()
float readFloat()
double readDouble()
char readChar()
boolean readBoolean()
String readUTF()
void readFully(byte[] b)
DataOutputStream
中的方法:read
write
- :
Java
Java
而ObjectOutputStream
和ObjectInputStream
既支持Java
基本数据类型的数据读写,又支持Java
对象的读写
- 对象流:
ObjectOutputStream
ObjectInputStream
ObjectOutputStream
:将Java
写入字节输出流中,通过在流中使用文件可以实现Java
ObjectInputStream
:ObjectInputStream
对以前使用ObjectOutputStream
写出的
说明:Java
。
对象流API
ObjectOutputStream
构造器
public ObjectOutputStream(OutputStream out)
:创建一个指定的ObjectOutputStream
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
方法
public void writeBoolean(boolean val)
:写出一个boolean
值public void writeByte(int val)
:写出一个8
位字节public void writeShort(int val)
:写出一个16
位的short
值public void writeChar(int val)
:写出一个16
位的char
值public void writeInt(int val)
:写出一个32
位的int
值public void writeLong(long val)
:写出一个64
位的long
值public void writeFloat(float val)
:写出一个32
位的float
值public void writeDouble(double val)
:写出一个64
位的double
值public void writeUTF(String str)
:将表示长度信息的2
个字节写入输出流,后跟字符串s
中每个字符的UTF-8
修改版表示形式。根据字符的值,将字符串s
中每个字符转换成1
个字节、2
个字节或3
个字节的字节组。注意:String
Object
s
null
NullPointerException
public void writeObject(Object obj)
:写出一个obj
对象public void close()
:关闭此输出流并释放与此流相关联的任何系统资源
ObjectInputStream
构造器
public ObjectInputStream(InputStream in)
:创建一个指定的ObjectInputStream
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
方法
public boolean readBoolean()
:读取一个boolean
值public byte readByte()
:读取一个8
位的字节public short readShort()
:读取一个16
位的short
值public char readChar()
:读取一个16
位的char
值public int readInt()
:读取一个32
位的int
值public long readLong()
:读取一个64
位的long
值public float readFloat()
:读取一个32
位的float
值public double readDouble()
:读取一个64
位的double
值public String readUTF()
:读取UTF-8
修改版格式的String
public void readObject(Object obj)
:读入一个obj
对象public void close()
:关闭此输入流并释放与此流相关联的任何系统资源
认识对象序列化机制
- 何为对象序列化机制?
对象序列化机制允许把内存中的Java
对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java
对象。
- :用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息
- :该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、类型和对象中存储的数据信息,都可以用来在内存中创建对象
- 序列化机制的重要性
- 序列化是
RMI
(Remote Method Invoke
:远程方法调用)过程的参数和返回值都必须实现的机制,而RMI
是JavaEE
的基础,因此序列化机制是JavaEE
平台的基础 - 序列化的好处:可将任何实现了
Serializable
接口的对象转化为字节数据,使其在保存和传输时可被还原
- 实现原理
- 序列化:用
ObjectOutputStream
类保存基本类型数据或对象public final void writeObject (Object obj)
: 将指定的对象写出
- 反序列化:用
ObjectInputStream
类读取基本类型数据或对象public final Object readObject ()
: 读取一个对象
如何实现序列化机制
java.io.Serializable
Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
- 若对象的某个属性也是引用数据类型,如果该属性也要序列化的话,也要实现
Serializable
接口 - 要序列化的类的所有属性必须是可序列化的,如果某个属性不需要序列化,则该属性必须使用
transient
- 静态
static
变量的值不会序列化,因为静态变量的值不属于某个对象
案例代码1
(简单数据类型序列化与反序列化):
import org.junit.Test;
import java.io.*;
public class ReadAndWriteDataOfSimpleTypeTest {
/**
* 简单数据类型序列化
*/
@Test
public void serializationTest() throws IOException {
String name = "weew12";
int age = 25;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ReadAndWriteDataOfAnyTypeTest.dat"));
objectOutputStream.writeUTF(name);
objectOutputStream.writeInt(age);
objectOutputStream.writeChar(gender);
objectOutputStream.writeInt(energy);
objectOutputStream.writeDouble(price);
objectOutputStream.writeBoolean(relive);
objectOutputStream.close();
}
/**
* 简单数据类型反序列化
*/
@Test
public void deSerializationTest() throws IOException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ReadAndWriteDataOfAnyTypeTest.dat"));
String name = objectInputStream.readUTF();
int age = objectInputStream.readInt();
char gender = objectInputStream.readChar();
int energy = objectInputStream.readInt();
double price = objectInputStream.readDouble();
boolean relive = objectInputStream.readBoolean();
objectInputStream.close();
System.out.println(name + " " + age + " " + gender + " " + energy + " " + price + " " + relive);
}
}
案例代码2
(复杂数据类型序列化与反序列化):
import org.junit.Test;
import java.io.*;
public class ReadAndWriteDataOfComplexTypeTest {
/**
* 序列化
*/
@Test
public void serialization() throws IOException {
RwEmployee.setCompany("weew12.io");
RwEmployee rwEmployee = new RwEmployee("weew12", "China", 26);
// 创建序列化流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ReadAndWriteDataOfComplexTypeTest.dat"));
// 写出对象
objectOutputStream.writeObject(rwEmployee);
// 关闭资源
objectOutputStream.close();
System.out.println("写出成功...");
System.out.println(rwEmployee);
}
/**
* 反序列化
*/
@Test
public void deSerialization() throws IOException, ClassNotFoundException {
// 创建反序列化流对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ReadAndWriteDataOfComplexTypeTest.dat"));
// 读取一个对象
Object readObject = objectInputStream.readObject();
// 关闭资源
objectInputStream.close();
System.out.println("读取成功...");
System.out.println(readObject);
}
}
class RwEmployee implements Serializable {
static final long serialVersionUID = 1733382052207L;
/**
* 静态变量不会被序列化
*/
public static String company;
private String name;
private String address;
/**
* transient 瞬态修饰 不会被序列化
*/
private transient int age;
public RwEmployee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
RwEmployee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "RwEmployee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
'}';
}
}
案例代码3
:
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
public class MultipleObjectSerializationTest {
/**
* 序列化
*/
@Test
public void multipleObjSerialization() throws IOException {
ArrayList<RwEmployee> rwEmployees = new ArrayList<>();
rwEmployees.add(new RwEmployee("张三", "上海", 28));
rwEmployees.add(new RwEmployee("李四", "长沙", 25));
rwEmployees.add(new RwEmployee("王五", "广州", 27));
// 创建序列化流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("MultipleObjectSerializationTest.dat"));
// 写出对象
objectOutputStream.writeObject(rwEmployees);
// 关闭资源
objectOutputStream.close();
System.out.println("写出成功...");
System.out.println(rwEmployees);
}
/**
* 反序列化
*/
@Test
public void multipleObjDeSerialization() throws IOException, ClassNotFoundException {
// 创建反序列化流对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("MultipleObjectSerializationTest.dat"));
// 读取一个对象
Object o = objectInputStream.readObject();
// 释放资源
objectInputStream.close();
System.out.println("读入成功...");
System.out.println(o);
}
}
反序列化失败问题
问题1
:
对于JVM
可以反序列化的对象,它必须是能够找到class
文件的类,如果找不到该类的class
文件,则抛出一个ClassNotFoundException
异常。
问题2
:
当JVM
反序列化对象时,能找到class
文件,但是class
文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
解决办法:
Serializable
serialVersionUID
Serializable
// 它的值由程序员随意指定即可
static final long serialVersionUID = 234242343243L;
serialVersionUID
用来表明类的不同版本间的兼容性。Java
serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM
会把传来的字节流中的serialVersionUID
serialVersionUID
InvalidCastException
- 如果类没有显示定义这个静态常量,它的值是
Java
运行时环境根据类的内部细节自动生成的。serialVersionUID
serialVersionUID
import java.io.Serializable;
public class Employee implements Serializable {
// 增加serialVersionUID
private static final long serialVersionUID = 1324234L;
//其它结构:略
}
面试题
谈谈你对java.io.Serializable
接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?
- 实现了
Serializable
接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程也可通过网络进行,这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows
机器上创建一个对象,对其序列化,然后通过网络发给一台Unix
机器,然后在那里准确无误地重新“装配”,不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。 - 由于大部分作为参数的类如
String
、Integer
等都实现了java.io.Serializable
的接口,也可以利用多态的性质,作为参数使接口更灵活。
其他流的使用
标准输入、输出流
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)
举例: 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入e
或者exit
时,退出程序。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class OtherStreamExerciseTest {
public static void main(String[] args) {
System.out.println("请输入信息[退出输入 e 或 exit ]:");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String input = null;
try {
while ((input = bufferedReader.readLine()) != null) {
if ("e".equalsIgnoreCase(input) || "exit".equalsIgnoreCase(input)) {
System.out.println("退出...");
break;
}
System.out.println("--->:" + input.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
拓展:
System
类中有三个常量对象:System.out
、System.in
、System.err
。System
类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
问题与解答:
- 这三个常量对象有
final
声明,但是却初始化为null
。final
声明的常量一旦赋值就不能修改,那么null
不会空指针异常吗? - 这三个常量对象为什么要小写?
final
声明的常量按照命名规范不是应该大写吗? - 这三个常量的对象有
set
方法?final
声明的常量不是不能修改值吗?set
方法是如何修改它们的值的?
final
Java
C/C++
set
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
private static void checkIO() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setIO"));
}
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
打印流
实现将基本数据类型的数据格式转化为字符串输出
PrintStream
和PrintWriter
提供了一系列重载的
print()
和println()
方法,用于多种数据类型的输出PrintStream
和PrintWriter
的输出不会抛出IOException
异常PrintStream
和PrintWriter
有自动flush
功能PrintStream
打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用PrintWriter
类System.out
返回的是PrintStream
的实例
构造器
PrintStream(File file)
:创建具有指定文件且不带自动行刷新的新打印流PrintStream(File file, String csn)
:创建具有指定文件名称和字符集且不带自动行刷新的新打印流PrintStream(OutputStream out)
:创建新的打印流PrintStream(OutputStream out, boolean autoFlush)
:创建新的打印流,autoFlush
如果为true
,则每当写入byte
数组、调用其中一个println
方法、写入换行符或字节('\n')
时都会刷新输出缓冲区PrintStream(OutputStream out, boolean autoFlush, String encoding)
:创建新的打印流PrintStream(String fileName)
:创建具有指定文件名称且不带自动行刷新的新打印流PrintStream(String fileName, String csn)
:创建具有指定文件名称和字符集且不带自动行刷新的新打印流
案例代码1
(PrintStream
测试):
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
// 创建流对象
PrintStream printStream = new PrintStream("PrintStreamTest.txt");
// 写出数据
printStream.println("hello");
printStream.println(1);
printStream.println(1.5);
// 关闭资源
printStream.close();
}
}
案例代码2
(autoFlush
测试):
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamAutoFlushTest {
public static void main(String[] args) {
FileOutputStream fileOutputStream = null;
PrintStream printStream = null;
try {
fileOutputStream = new FileOutputStream("PrintStreamAutoFlushTest.txt");
// 创建打印输出流 设置为自动刷新模式 (写入换行符或字节 '\n' 时都会刷新输出缓冲区)
printStream = new PrintStream(fileOutputStream, true);
// 把掉准输出流改为文件
System.setOut(printStream);
// 输出ASCII字符
for (int i = 0; i <= 255; i++) {
System.out.print(((char) i));
// 50个数据一行
if (i % 50 == 0) {
// 换行
System.out.println();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (printStream != null) {
printStream.close();
}
}
}
}
案例代码3
(自定义一个日志工具):
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PrintStreamLoggerTest {
public static void main(String[] args) {
MyLogger.log("调用了System类的gc()方法...");
MyLogger.log("调用了TeamView的addMember()方法");
MyLogger.log("用户尝试进行登录,验证失败");
}
}
/**
* 自定义日志工具
*/
class MyLogger {
/**
* 日志记录方法
*/
public static void log(String msg) {
try {
// 指向一个文件 追加
PrintStream printStream = new PrintStream(new FileOutputStream("PrintStreamLoggerTest-logs.txt", true));
// 改变输出方向
System.setOut(printStream);
// 当前时间
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss SSS]");
String formatDate = simpleDateFormat.format(date);
// 输出
System.out.println(formatDate + ": " + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
Scanner
类
构造器
Scanner(File source)
:构造Scanner
,它生成的值是从指定文件扫描的Scanner(File source, String charsetName)
:构造Scanner
,它生成的值是从指定文件扫描的,指定字符集设置Scanner(InputStream source)
:构造Scanner
,它生成的值是从指定的输入流扫描的Scanner(InputStream source, String charsetName)
:构造Scanner
,它生成的值是从指定的输入流扫描的,指定字符集设置
常用方法
boolean hasNextXxx()
:nextXxx()
Xxx
true
Xxx nextXxx()
:将输入信息的下一个标记扫描为一个Xxx
apache-common
包的使用
介绍
IO
Apache
IO
commonsIO
IO
导包
- 导入
commons-io-2.5.jar
包之后,内部的API
都可以使用
使用样例
IOUtils
类的使用
IOUtils.copy(InputStream in,OutputStream out)
: 传递字节流实现文件复制IOUtils.closeQuietly(任意流对象)
: 悄悄的释放资源,自动处理close()
方法抛出的异常
public class CommonsIOTest {
/**
* IOUtils.copy(InputStream in,OutputStream out)
*/
@Test
public void copyTest() {
try {
IOUtils.copy(new FileInputStream("file-gbk.txt"), new FileOutputStream("copy-file-gbk.txt"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* IOUtils.closeQuietly(任意流对象)
*/
@Test
public void closeQuietlyTest() {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("copy-file-gbk.txt");
fileWriter.write("test");
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(fileWriter);
}
}
}
FileUtils
类的使用
void copyDirectoryToDirectory(File src,File dest)
:整个目录的复制,,src
要复制的文件夹路径,dest
要将文件夹粘贴到哪里去void writeStringToFile(File file,String content)
:将内容content
写入到file
中String readFileToString(File file)
:读取文件内容,并返回一个String
void copyFile(File srcFile,File destFile)
:文件复制