1.File类
java.io.File
类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
文件和目录路径名的抽象表示
java把文件和文件夹封装位为一个File类,我们可以用File类的对文件和文件夹进行操作
使用File类我们可以
- 创建一个文件夹/文件
- 删除文件/文件夹
- 获取文件/文件夹
- 判断文件/文件夹是否存在
- 获取文件大小
File是一个与系统无关的类,任何系统都可以使用这个类中的方法。
重点:
file:文件
directory:目录
path:路径
static String pathSeparator
:路径分隔符
static char pathSeparator
:路径分隔符
Srting pathSeparator=File.pathSeparator;
windows:分号
linux:冒号
static String separator
:默认名称分隔符
static char separator
:默认名称分隔符
Srting separator=File.separator;
windows:\
linux:/
路径不能写死了
“c:“+File.separator+”develop“+File.separator+a.txt”
绝对路径,相对路径
绝对路径:完整路径c:\a.txt
相对路径:简化路径,相对当前项目的根目录
注意:
- 路径不区分大小写
- 路径中文件名称分隔符windows使用双反斜杠
File中的构造方法
-
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
可以是以文件/文件夹结尾。可以相对路径也可以是绝对路径,可以是存在的也可以不存在。创建File对象,只把字符串路径封装给File对象,不考虑路径对象 它重写了File的toString方法。 -
public File(String parent, String child)
:从父路径名字符串和子路径名字符串创建新的 File实例。
父路径和子路径,可以单独书写,使用灵活 -
public File(File parent, String child)
:从父抽象路径名和子路径名字符串创建新的 File实例。
父路径和子路径,可以单独书写,使用灵活,父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
获取功能的方法
-
public String getAbsolutePath()
:返回此File的绝对路径名字符串。 -
public String getPath()
:将此File转换为路径名字符串。 -
public String getName()
:返回由此File表示的文件或目录的名称。 -
public long length()
:返回由此File表示的文件的长度。方法演示,代码如下:
public class FileGet {
public static void main(String[] args) {
File f = new File("d:/aaa/bbb.java");
System.out.println("文件绝对路径:"+f.getAbsolutePath());
System.out.println("文件构造路径:"+f.getPath());
System.out.println("文件名称:"+f.getName());
System.out.println("文件长度:"+f.length()+"字节");
File f2 = new File("d:/aaa");
System.out.println("目录绝对路径:"+f2.getAbsolutePath());
System.out.println("目录构造路径:"+f2.getPath());
System.out.println("目录名称:"+f2.getName());
System.out.println("目录长度:"+f2.length());
}
}
输出结果:
文件绝对路径:d:\aaa\bbb.java
文件构造路径:d:\aaa\bbb.java
文件名称:bbb.java
文件长度:636字节
目录绝对路径:d:\aaa
目录构造路径:d:\aaa
目录名称:aaa
目录长度:4096
判断功能的方法
public boolean exists()
:此File表示的文件或目录是否实际存在。public boolean isDirectory()
:此File表示的是否为目录。public boolean isFile()
:此File表示的是否为文件。
方法演示,代码如下:
public class FileIs {
public static void main(String[] args) {
File f = new File("d:\\aaa\\bbb.java");
File f2 = new File("d:\\aaa");
// 判断是否存在
System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
System.out.println("d:\\aaa 是否存在:"+f2.exists());
// 判断是文件还是目录
System.out.println("d:\\aaa 文件?:"+f2.isFile());
System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
}
}
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true
创建删除功能的方法
-
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。 只能创建文件。不能创建文件夹。需要处理异常,必须路径存在,否则会抛出io异常。 -
public boolean delete()
:删除由此File表示的文件或目录。 不走回收站 -
文件/文件夹删除成功,返回true
-
文件夹有内容不回删除,返回false,构造方法中不存在也返回false
public boolean mkdir()
:创建由此File表示的目录。只能创建单集空文件夹public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。可以创建单级文件夹也可以创建多级空文件夹。- 返回值:true:文件夹不存在,创建文件夹,返回true
返回false:文件夹存在,不会创建,构造方法中给出的路径不存在也返回false
只能创建文件夹不能创建文件
创建文件的路径必须存在,否则会抛出异常
方法演示,代码如下:
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// 文件的创建
File f = new File("aaa.txt");
System.out.println("是否存在:"+f.exists()); // false
System.out.println("是否创建:"+f.createNewFile()); // true
System.out.println("是否存在:"+f.exists()); // true
// 目录的创建
File f2= new File("newDir");
System.out.println("是否存在:"+f2.exists());// false
System.out.println("是否创建:"+f2.mkdir()); // true
System.out.println("是否存在:"+f2.exists());// true
// 创建多级目录
File f3= new File("newDira\\newDirb");
System.out.println(f3.mkdir());// false
File f4= new File("newDira\\newDirb");
System.out.println(f4.mkdirs());// true
// 文件的删除
System.out.println(f.delete());// true
// 目录的删除
System.out.println(f2.delete());// true
System.out.println(f4.delete());// false
}
}
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
目录的遍历
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。把获取到的多个名称存储到一个String类型的数组中。
public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。遍历构造方法中给出的目录,获取所有文件或文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
list和listFiles方法遍历的是构造方法中给出的目录
如果走早方法中给出路径不存在,会抛出空指针异常
如果构造方法中给出的路径不是一个目录,会抛出空指针异常
public class FileFor {
public static void main(String[] args) {
File dir = new File("d:\\java_code");
//获取当前目录下的文件以及文件夹的名称。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
//获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
小贴士:
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
递归
递归:指再当前方法体调用自己
分为直接递归和间接递归
注意事项:
- 递归一定要有条件限制,保证递归能够停止
- 递归次数不能太多,否则可能会发生栈内存溢出
- 构造方法禁止递归
递归方法使用前提:
当调用方法的时候,方法主体不变,每次调用方法的参数不同,可以使用递归
使用递归计算1–n的和
使用递归必须明确:
- 递归的结束条件:获取到一结束
- 递归的目的:获取下一个被加的数字
实现代码:
public class DiGuiDemo {
public static void main(String[] args) {
//计算1~num的和,使用递归完成
int num = 5;
// 调用求和的方法
int sum = getSum(num);
// 输出结果
System.out.println(sum);
}
/*
通过递归算法实现.
参数列表:int
返回值类型: int
*/
public static int getSum(int num) {
/*
num为1时,方法返回1,
相当于是方法的出口,num总有是1的情况
*/
if(num == 1){
return 1;
}
/*
num不为1时,方法返回 num +(num-1)的累和
递归调用getSum方法
*/
return num + getSum(num-1);
}
}
递归求阶乘
阶乘:所有小于及等于该数的正整数的积。
n的阶乘:n! = n * (n-1) *...* 3 * 2 * 1
推理得出:n! = n * (n-1)!
代码实现:
public class DiGuiDemo {
//计算n的阶乘,使用递归完成
public static void main(String[] args) {
int n = 3;
// 调用求阶乘的方法
int value = getValue(n);
// 输出结果
System.out.println("阶乘为:"+ value);
}
/*
通过递归算法实现.
参数列表:int
返回值类型: int
*/
public static int getValue(int n) {
// 1的阶乘为1
if (n == 1) {
return 1;
}
/*
n不为1时,方法返回 n! = n*(n-1)!
递归调用getValue方法
*/
return n * getValue(n - 1);
}
}
递归打印多级目录
分析:多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。
代码实现:
public class DiGuiDemo2 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
/*
判断:
当是文件时,打印绝对路径.
当是目录时,继续调用打印目录的方法,形成递归调用.
*/
for (File file : files) {
// 判断
if (file.isFile()) {
// 是文件,输出文件绝对路径
System.out.println("文件名:"+ file.getAbsolutePath());
} else {
// 是目录,输出目录绝对路径
System.out.println("目录:"+file.getAbsolutePath());
// 继续遍历,调用printDir,形成递归
printDir(file);
}
}
}
}
文件搜索
搜索D:\aaa
目录中的.java
文件。
分析:
- 目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。
- 遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。
代码实现:
public class DiGuiDemo3 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
for (File file : files) {
if (file.isFile()) {
// 是文件,判断文件名并输出文件绝对路径
if (file.getName().endsWith(".java")) {
System.out.println("文件名:" + file.getAbsolutePath());
}
} else {
// 是目录,继续遍历,形成递归
printDir(file);
}
}
}
}
文件过滤器优化
listFiles(FileFilter fileter)
File类中有两个和ListFiles重载的方法,方法参数就是过滤器
java.io.FileFilter
是一个接口,是File的过滤器。 该接口的对象可以传递给File类的
用于抽象路径名(File对象)的过滤器,用来过滤文件
boolean accept(File pathname)
:测试pathname是否应该包含在当前File目录中,符合则返回true。
listFiles(FilenameFilter fileter)
:
java.io.FilenameFilter
接口:实现此接口的类实例可用于过滤器文件名
用于过滤文件名称
boolean accept(File dir ,String name)
:测试文件是否包含在某一文件夹中
File dir:构造方法中传递的被遍历的目录
String name::使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称
注意:两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤方法accep,在方法中自己定义过滤器规则。
分析:
- 接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。
accept
方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:- 要么是.java文件。
- 要么是目录,用于继续遍历。
- 通过过滤器的作用,
listFiles(FileFilter)
返回的数组元素中,子文件对象都是符合条件的,可以直接打印。
代码实现:
public class DiGuiDemo4 {
public static void main(String[] args) {
File dir = new File("D:\\aaa");
printDir2(dir);
}
public static void printDir2(File dir) {
// 匿名内部类方式,创建过滤器子类对象
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".java")||pathname.isDirectory();
}
});
// 循环打印
for (File file : files) {
if (file.isFile()) {
System.out.println("文件名:" + file.getAbsolutePath());
} else {
printDir2(file);
}
}
}
}
FileFilter过滤器的原理和使用
必须明确两件事情:
- 过滤器中accept方法是谁调用的,
- accept方法的参数pathname是什么。
ListFiles方法一共做了三件事情:- ListFiles方法对构造中传递的目录进行遍历,获取目录中的每一个文件夹/文件----封装成File对象
- ListFiles方法会调用参数传递的过滤器中的方法accept
- listFiles方法会把遍历的到的File对象,传递过accept方法的参数pathname
accept方法返回的是一个boolean值
true: 会把传递过去的File对象保存到File数组中
false:就不会把传递过去的File对象保存到File数组中
因此,过滤的规则:
在accept方法中,判断File对象
Lambda优化
分析:FileFilter
是只有一个方法的接口,因此可以用lambda表达式简写。
lambda格式:
()->{ }
代码实现:
public static void printDir3(File dir) {
// lambda的改写
File[] files = dir.listFiles(f ->{
return f.getName().endsWith(".java") || f.isDirectory();
});
// 循环打印
for (File file : files) {
if (file.isFile()) {
System.out.println("文件名:" + file.getAbsolutePath());
} else {
printDir3(file);
}
}
}
IO
IO的分类
根据数据的流向分为:输入流和输出流。
- 输入流 :把数据从
其他设备
上读取到内存
中的流。 - 输出流 :把数据从
内存
中写出到其他设备
上的流。
格局数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 字符流 :以字符为单位,读写数据的流。
流:数据(字符,字节)一个字符=两个字节
一个字节=八个二进制(八位)
:: | 输入流 | 输出流 |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
IO字节流
以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此
字节输出流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方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileOutputStream类:文件输出流
java.io.FileOutputStream
类是文件输出流,用于将内存数据写出到硬盘的文件中。
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
参数:写入数据的目的地。
String name文件路径
File file:目的地是一个文件
构造方法地作用:
1.创建一个 FileOutputStream对象
2.会根据构造方法中传递的文件/文件路径,创建一个空的文件
3.会把 FileOutputStream对象指向创建好的问文件
字节流写入数据到文件
写出字节
写入数据的原理:(内存–>硬盘)
java程序—>JVM(Java虚拟机)—>Os(操作系统)—>OS调用写数据的方法—>把数据写入到文件中
字节输出流的使用步骤:
- 创建一个 FileOutputStream对象,构造方法中传递写入的数据的目的地
- 调用 FileOutputStream对象中的方法write,把数据写入到文件中
- 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
需要抛出异常
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
输出结果:
abc
写数据的时候,会把10进制的整数转换为二进制整数97
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
写出字节数组:write(byte[] b)
,
一次写多个字节
- 创建 FileOutputStream对象, 构造方法中绑定要写入的数据的目的地
- 调用 FileOutputStream对象中的方法
write(byte[] b)
(如果第一个字节是正数(0–127 ),那么显示的时候会查询ascii表 ,如果第一个字节是负数,那么第一个字节会和第二个字节,两个字节组成 一个中文显示,查询系统默认码表(GBK)),把数据写入到文本中. - 释放资源
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "黑马程序员".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
写出指定长度字节数组:write(byte[] b, int off, int len)
,每次写出从off索引开始,len个字节。把字节数组的一部写入到文件中
.getBytes()把字符串转换为字节数组
arrays.toString(字节数组)转换为十进制数组
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();
}
}
输出结果:
cd
数据追加续写
** 追加写**:使用两个参数的构造方法
-
public FileOutputStream(File file, boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。
-创建一个向指定File对象表示的文件中写入数据的文件输出流 -
public FileOutputStream(String name, boolean append)
: 创建文件输出流以指定的名称写入文件 -
创建一个向具有指定name的文件中写入数据的输出文件流
String 那么,File file:写入数据的目的地
boolean append:追加写开关true:创建对象不回覆盖原文件,继续在文件的末尾追加写数据
false:创建一个新文件,覆盖原文件
写换行:写换行符号
windows:\r\n
linux: /n
mac:/r
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 定义字节数组
byte[] words = {97,98,99,100,101};
// 遍历数组
for (int i = 0; i < words.length; i++) {
// 写出一个字节
fos.write(words[i]);
// 写出一个换行, 换行符号转成数组写出
fos.write("\r\n".getBytes());
}
// 关闭资源
fos.close();
}
}
输出结果:
a
b
c
d
e
字节输入流InputStream
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
所有子类共性的方法:
-
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。 -
public abstract int read()
: 从输入流读取数据的下一个字节。 -
public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileInputStream类
作用:把硬盘中的文件数据,读取到内存中使用
构造方法:
-
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。 -
FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
参数:读取文件的数据源
String name:文件路径
File file:文件
构造方法的作用:
1.会创建一个FileInputStream对象
2.会把FileInputStream对象指定给构造方法中读取的文件
读取数据的原理(硬盘–>内存)
java程序 -->JVM -->OS–>OS读取数据的方法–>读取文件
自己输入流的使用步骤(重点):
- 创建FileInputStream对象,构造方法中绑定要读取的数据源。
- 使用FileInputStream对象中的方法read,读取文件
- 释放文件
读取字节数据
- 读取字节:
read
方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
,代码使用演示:
每次读取一个字节
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 读取数据,返回一个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾,返回-1
read = fis.read();
System.out.println( read);
// 关闭资源
fis.close();
}
}
输出结果:
a
b
c
d
e
-1
循环改进读取方式,代码使用演示:
不知道文件有多少字节,使用while循环
结束条件:读取到-1的时候
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量,保存数据
int b ;
// 循环读取,必须用变量接收
while ((b = fis.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fis.close();
}
}
输出结果:
a
b
c
d
e
. 使用字节数组读取:read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
,:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
}
// 关闭资源
fis.close();
}
}
输出结果:
ab
cd
e
一次读取多个字节
原理:
1.创建一个流对象,并把它指向要读取的文件
2.创建一个byte数组
3.读取数据
- 读取数组长度大小的数据
- 返回有效读取字节个数
数组起到缓冲作用,存储读取到的多个字节 一般定义为1024的整数倍
字节流练习:图片复制
文件复制:一读一写
明确:
数据源
数据的目的地:
- 创建一个字节输入流对象,在构造方法中绑定要读取的数据源
- 创建一个
- 流对象,构造方法中绑定要写入的目的地
- 使用字节输入流对象中的当打中read读取文件
- 使用字节输出流中的方法write,把读取到的字节写入到目的地文件中
- 释放资源
public class Copy {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定目的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义长度
int len;
// 2.3 循环读取
while ((len = fis.read(b))!=-1) {
// 2.4 写出数据
fos.write(b, 0 , len);
}
// 3.关闭资源
fos.close();
fis.close();
}
}
字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
GBK:占用两个字节
utf-8:占用三个字节
字符输入流【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
共有成员方法
public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取多个字符,并将它们存储到字符数组 cbuf中 。public void close()
:关闭此流并释放与此流相关联的任何系统资源。
FileReader类 文件字符输入流
java.io.FileReader
extend InputStreamReader··
extendReader
类
是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
作用:把硬盘中的数据以字符的方式读取到内存中
构造方法:
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
参数:读取的数据源
String fileName:文件的路径
File file:文件
FileReader构造方法中的作用:
1.创建一个FileReader的对象
2.会把FileReader对象指向要读取的文件
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
字符输入流的使用步骤?
1.创建FileReader对象,构造方法中要绑定要读取的数据源
2.使用FileReader对象中的方法Read读取文件
3.释放资源
读取字符数据
- 读取字符:
read
方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
,循环读取,代码使用演示:
public class FRRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存数据
int b ;
// 循环读取
while ((b = fr.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fr.close();
}
}
输出结果:
黑
马
程
序
员
使用字符数组读取:read(char[] cbuf)
,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
,代码使用演示:
String类构造方法:
String(char[] value):把字符数组转换为字符串
String(char[] value,int offset ,int count):把字符数组一部分转换为字符串
public class FRRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf));
}
// 关闭资源
fr.close();
}
}
输出结果:
黑马
程序
员序
获取有效的字符改进,代码使用演示:
public class FISRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();
}
}
输出结果:
黑马
程序
员
字符输出流【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()
关闭此流,但要先刷新它。
FileWriter类 文件字符输出流
作用: 把内存中的字符数数剧写入到文件中
java.io.FileWriter
extends OutputStreamWrite
extends··Write
java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法:
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
参数:写入文件的目的地
String fileName:文件的路径
File file:文件
构造方法中的作用:
- 会创建一个FileWriter对象
- 会根据构造方法中传递的文件/文件路径创建文件
- 会把FileWriter对象指向创建好的文件
字符输出流的使用步骤:
- 创建FileWriter对象。构造方法中绑定要写入的数据的目的地
- 使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
- 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
- 释放资源(会先把内存缓冲区中的数据,刷新到文件中)
基本写出数据
写出单个字符:write(int b)
方法,每次可以写出一个字符数据,代码使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据
fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
/*
【注意】关闭资源时,与FileOutputStream不同。
如果不关闭,数据只是保存到缓冲区,并未保存到文件。
*/
// fw.close();
}
}
输出结果:
abC田
- 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
- 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
写出其他数据
- 写出字符数组 :
write(char[] cbuf)
和write(char[] cbuf, int off, int len)
,每次可以写出字符数组中的数据,用法类似FileOutputStream,代码使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串转换为字节数组
char[] chars = "黑马程序员".toCharArray();
// 写出字符数组
fw.write(chars); // 黑马程序员
// 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
fw.write(b,2,2); // 程序
// 关闭资源
fos.close();
}
}
- 写出字符串:
write(String str)
和write(String str, int off, int len)
,每次可以写出字符串中的数据,更为方便,代码使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串
String msg = "黑马程序员";
// 写出字符数组
fw.write(msg); //黑马程序员
// 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
fw.write(msg,2,2); // 程序
// 关闭资源
fos.close();
}
}
- 续写和换行:操作类似于FileOutputStream。
使用两个参数的构造方法:
FileWriter(String fileName,Boolean append)
FileWriter(File file,Boolean append)
Boolean append:
true:可以续写,不回创建新的文件覆盖源文件
false:创建新的文件覆盖源文件
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象,可以续写数据
FileWriter fw = new FileWriter("fw.txt",true);
// 写出字符串
fw.write("黑马");
// 写出换行
fw.write("\r\n");
// 写出字符串
fw.write("程序员");
// 关闭资源
fw.close();
}
}
输出结果:
黑马
程序员
流中的异常处理
jdk1.7之前使用try...catch...finally
处理流中的异常
public class HandleException1 {
public static void main(String[] args) {
// 声明变量,提高变量作用域,声明时可以没有值,使用时必须有值
FileWriter fw = null;
try {
//可能会出现异常的代码创建流对象
fw = new FileWriter("fw.txt");
// 写出数据
fw.write("黑马程序员"); //黑马程序员
} catch (IOException e) {
//异常的处理逻辑
e.printStackTrace();
} finally {
try {
//若创建对象失败,fw默认值时null,null不能调用方法,会抛出nullponitException,需要增加一个判断不是null,则释放资源
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK7的新特性:
在try之后增加一个();
在括号中可以定义流对象,那么这个流对象的作用域就在try中有效,try中代码执行完毕,会自动把流对象释放掉,不用写finally,
格式如下:
try (创建流对象语句,如果多个,使用';'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
代码使用演示:
public class HandleException2 {
public static void main(String[] args) {
// 创建流对象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 写出数据
fw.write("黑马程序员"); //黑马程序员
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK9的改进(扩展知识点了解内容)
try的前边可以定义流对象
在try后边()中可以直接引入流对象的名称(变量名)
在try代码执行完毕之后流对象也可以释放掉,不用写finally
JDK9中try-with-resource
的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
A a =new A();
B b=new B();
try(a,b){
可能会产出异常的代码,
} catch(异常类变量 变量名){
异常的处理逻辑
}
public class TryDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
** final ** FileReader fr = new FileReader("in.txt");
FileWriter fw = new FileWriter("out.txt");
// 引入到try中
try (fr; fw) {
// 定义变量
int b;
// 读取数据
while ((b = fr.read())!=-1) {
// 写出数据
fw.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
属性集
概述
java.util.Properties
继承于Hashtable
,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties
方法就是返回一个Properties
对象。
java.util.Properties
extends hashtable<K,v>implements Map<K,v>
Properties类
表示了一个持久的属性集Properties可保存在流中或从流中加载。
Properties集合是唯一一个和IO流相结合的集合
可以使用Properties集合中的临时数据,持久化写入到硬盘中存储、
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
Properties属性列表中每个键及其对对应的值都是一个字符串
Properties是一个双列集合,key和value默认都是字符串
构造方法
public Properties()
:创建一个空的属性列表。
基本的存储方法
使用Properties结合存储数据,遍历取出Properties集合中的数据
是一个双列集合,Key和value都是字符串
public Object setProperty(String key, String value)
: 保存一对属性。 调用hashtable的方法putpublic String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。通过key找到value值,此方法相当于map集合中的get(key)方法public Set<String> stringPropertyNames()
:所有键的名称的集合。返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的KeySet方法
public class ProDemo {
public static void main(String[] args) throws FileNotFoundException {
// 创建属性集对象
Properties properties = new Properties();
// 添加键值对元素
properties.setProperty("filename", "a.txt");
properties.setProperty("length", "209385038");
properties.setProperty("location", "D:\\a.txt");
// 打印属性集对象
System.out.println(properties);
// 通过键,获取属性值
System.out.println(properties.getProperty("filename"));
System.out.println(properties.getProperty("length"));
System.out.println(properties.getProperty("location"));
// 遍历属性集,获取所有键的集合
Set<String> strings = properties.stringPropertyNames();
// 打印键值对
for (String key : strings ) {
System.out.println(key+" -- "+properties.getProperty(key));
}
}
}
输出结果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename -- a.txt
length -- 209385038
location -- D:\a.txt
与流相关的方法
可以使用properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
void store (OutputStream out,String comments)
void store(Write write,String comments)
参数:
OutputStream out:不可以写入中文
Write write:字符输出流,可以写入中文
String comments:注释,用来解释说明保存的文件是做什么用的,不能使用中文,会产生乱码默认unicide编码,一般使用“空字符串”
使用步骤:
- 创建Properties 集合对象,添加数据
- 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
- 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
- 释放资源
Properties pro = new Properties();创建Properties集合
pro.setProperty(“11”,“11 ”);
FileWrite fw=new FileWrite("Filename");
pro.store(fw,"savedate");
fw.close();
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
public void load(InputStream inStream)
: 从字节输入流中读取键值对。public void load(Reader reader)
:
参数:
(InputStream inStream):字节输入流,不能读取含有中文的键值对,
Reader reader:字符输入流,可以读取含有中文的键值对
通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式:
使用步骤:
- 创建Properties集合对象
- 使用Properties集合对象中的方法load读取保存键值对文件
- 遍历Properties集合
注意:
- 存储键值对的文件中,键与值默认的连接符号使用=, 空格(其他符号)
- 存储键值对的文件中,可使用#进行注释,被注释的键值对不会再被读取
- 键与值默认都是字符串,不用再加引号
filename=a.txt
length=209385038
location=D:\a.txt
加载代码演示:
一般使用字符流
public class ProDemo2 {
public static void main(String[] args) throws FileNotFoundException {
// 创建属性集对象
Properties pro = new Properties();
// 加载文本中信息到属性集
pro.load(new FileInputStream("read.txt"));
// 遍历集合并打印
Set<String> strings = pro.stringPropertyNames();
for (String key : strings ) {
System.out.println(key+" -- "+pro.getProperty(key));
}
}
}
输出结果:
filename -- a.txt
length -- 209385038
location -- D:\a.txt
小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分类:
缓冲流
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
字节缓冲流
BufferedInputStream
,字节缓冲输入流
所有子类共性的方法:
-
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。 -
public abstract int read()
: 从输入流读取数据的下一个字节。 -
public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
BufferedOutputStream
字节缓冲输出流
共性方法:
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方法,当完成流的操作时,必须调用此方法,释放系统资源。
字节缓冲输入流
构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。 ,保存其参数,即输入流in,以便将来使用
public BufferedInputStream(InputStream in,int size )
:创建一个 具有指定缓冲区大小的缓冲输入流,并保存其参数
参数:
InputStream in字节输入流:我们可以传递FileInputStream,增加一个缓冲区,提高FileInputStream的读取效率
int size:指定缓冲流内部缓冲区大小,不指定默认
使用步骤
1.创建FileInputStream对象,构造方法中绑定要读取的数据源
2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象效率
3.使用BufferedInputStream对象中的方法read ,读取文件
4.释放资源(会调用Flue方法刷新数据)
字节缓冲输出流
-
public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。以将数据写入指定的底层输出流 -
public BufferedOutputStream(OutputStream out,int size)
: 创建一个新的缓冲输出流。以将数据写入指定的底层输出流
参数:
OutputStream out:字节输出流
我们可以传递FileOutputStream,缓冲流会给FileOutoutStream增加一个缓冲区,提高FileOutputStream 的写入效率
使用步骤:
1.创建FileOutputStream对象,构造方法中绑定要输出目的地
2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象 对象,提高FileOutputStream对象效率
3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区,
4.使用BufferedOutputStream对象中的方法flush把内部缓冲区中的数据,刷新到文件中
5.释放资源(会调用Flue方法刷新数据)
构造举例,代码如下:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
关闭缓冲流,会自动关闭基本流
效率测试
- 基本流,代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
FileInputStream fis = new FileInputStream("jdk9.exe");
FileOutputStream fos = new FileOutputStream("copy.exe")
){
// 读写数据
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
}
}
十几分钟过去了...
- 缓冲流,代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
}
}
缓冲流复制时间:8016 毫秒
如何更快呢?
使用数组的方式,代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
}
}
缓冲流使用数组复制时间:666 毫秒
字符缓冲流
共性方法
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()
关闭此流,但要先刷新它。
BufferedWriter
字符缓冲输出流
构造方法
BufferedWriter(writer out):创建一个使用默认大小输出缓冲区的缓冲输出流
BufferedWriter(writer out,int sz):创建一个使用给定大小输出缓冲区的新缓冲字符输出流
参数:
writer out:字符输出流
我们可以传递一个FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWrite的写入效率
int sz:指定缓冲区的大小,不写默认大小
特有成员方法:
Void newLine():写入一个分隔符,会根据不同的操作系统,获取不同的行分隔符
换行符号:
windows:\r\n
linux:/n
mac:/r
使用步骤:
1.创建字符缓冲输出流对象,构造方法中传递字符输出流
2.调用字符缓冲流中的方法,write,把数据写入到内存缓冲区中
3.调用字符缓冲流中的方法flush,把内存缓冲区中的数据,刷新到文件中
4.释放资源
BufferedReader
,extends Reader 字符缓冲输入流
- 共有成员方法
public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取多个字符,并将它们存储到字符数组 cbuf中 。public void close()
:关闭此流并释放与此流相关联的任何系统资源。
构造方法
BufferedReader(Reader in):创建一个使用默认大小的输出缓冲的缓冲字符输入流
BufferedReader(Reader in,int sz):创建一个使用指定大小的缓冲区的缓冲字符输入流
参数
Reader in:字符输入流我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
int sz:
特有成员方法
String readLine():读取一个文本行 。读一行数据,行的终止符号:通过列字符之一即可认为某行已终止,换行\n 回车\r,回车后跟着换行(\r\n)
返回值:包含该行内容字符串,不包含任何行终止符,如果已到达流末尾,则返回null
使用步骤
- 创建字符缓冲输入流对象,构造方法中传递字符输入流
- 使用字符缓冲输入流对象中的方法read/readline读取文本
- 释放资源
readLine
方法演示,代码如下:
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
System.out.print(line);
System.out.println("------");
}
// 释放资源
br.close();
}
}
1.4 练习:文本排序
练习:
对文本的内容进行排序
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
案例分析
1.创建一个HashMap集合对象,可以存储每行文本的序号,value存储每行的文本
2.创建字符缓冲输入流对象,构造方法绑定字符输入流
3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
4.使用字符缓冲输入流的方法readLine,逐行读取文本
5.对读取到的文本 进行切割,获取行中的序号和文本内容
6.把切割好的序号和文本的内容存储到Hashmap集合中(key序号是有序的,会自动排序)
7.遍历HashMap集合,获取每一个键值对
8.把每一个键值对拼接为一个文本行
9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中
10.释放资源
案例实现
public class BufferedTest {
public static void main(String[] args) throws IOException {
//1. 创建map集合,保存文本数据,键为序号,值为文字
HashMap<String, String> lineMap = new HashMap<>();
// 2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
//3.创建一个字符缓冲输出流对象,构造方法中绑定字符串输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
//4.使用字符缓冲输出流中的方法readLine逐行读取文本
// 读取数据
String line = null;
while ((line = br.readLine())!=null) {
// 解析文本
String[] split = line.split("\\.");
// 保存到集合
lineMap.put(split[0],split[1]);
}
// 释放资源
br.close();
// 遍历map集合
for (int i = 1; i <= lineMap.size(); i++) {
String key = String.valueOf(i);
// 获取map中文本
String value = lineMap.get(key);
// 写出拼接文本
bw.write(key+"."+value);
// 写出换行
bw.newLine();
}
// 释放资源
bw.close();
}
}
转换流
字符编码和字符集
按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
字符编码:就是一套自然语音字符与二进制数之间的对应规则
字符集:也叫编码表,是一个系统支持所有字符的集合
Idea默认编码utf-8
FileReader可以读取IO默认编码格式UTF-8的文件
FileReader读取系统默认编码GBK中文 会产生乱码
InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。
它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
参数:
InputSrteam in:字节输入流,用来读取文件中保存的字节
String charsetName:指定的编码表名称,不区分大小写,可以实Utf-8,Gbk。。。。不指定默认使用UTF-8
使用步骤:
- 创建InputStreamReader对象,构造方法重视传递字节输出流和指定的编码表名称。
- 使用InputStreamReader对象中的方法read读取文件
- 释放资源
注意事项:
构造方法中指定的编码表民晨光要和文件的编码相同,否则会发生乱码
OutputStreamWriter类
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
参数
OutputStream out:字节输出流,用来写转换之后的字节到文件中
String charsetName:指定的编码表名称,不区分大小写,可以是Utf-8,GBK…,不指定默认使用Utf-8
使用步骤
- 创建OutputStreamWrite对象,构造方法中传递字节输出流和指定编码表名称
- 使用OutputStreamWrite,把字符转换为字节存储在缓冲区中(编码)
- 使用OutputStreamWrite对象中的方法flush,把内存缓冲区的字节刷新到文件中(使用字节流写字节的过程)
- 释放资源
指定编码写出
public class OutputDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "E:\\out.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
osw.close();
// 定义文件路径
String FileName2 = "E:\\out2.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}
练习:转换文件编码
将GBK编码的文本文件,转换为UTF-8编码的文本文件。
案例分析
1.创建InputStreanReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
2.创建OutputStreamWrite对象,构造方法中传递字节输出流和指定的编码表utf-8
3.使用InputSteamReader对象中的方法read读取文件
4.使用OutputStreamWrite方法write,把读取的数据写入到文件中
5.释放资源
案例实现
public class TransDemo {
public static void main(String[] args) {
// 1.定义文件路径
String srcFile = "file_gbk.txt";
String destFile = "file_utf8.txt";
// 2.创建流对象
// 2.1 转换输入流,指定GBK编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
// 2.2 转换输出流,默认utf8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
// 3.读写数据
// 3.1 定义数组
char[] cbuf = new char[1024];
// 3.2 定义长度
int len;
// 3.3 循环读取
while ((len = isr.read(cbuf))!=-1) {
// 循环写出
osw.write(cbuf,0,len);
}
// 4.释放资源
osw.close();
isr.close();
}
}
序列化
概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
把对象以流的方式,写入到文件中保存,叫写对象,也可以叫对象的序列化
对象中包含的不仅仅是字符使用字节流
ObjectOutputStream:对象的序列化writeObject(p)
把文件中保存的对象,以流的方式读取出来,叫做读对象,也叫对象的反序列化
读取的文件保存的都是字节使用字节流
ObjectInputStream:对象的反序列化流
readObject()
ObjectOutputStream类
作用:把对象以流的方式写入到文件中保存
java.io.ObjectOutputStream
extends OutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
参数:
OutputStream out:字节输出流
构造举例,代码如下:
特有的成员方法:
public final void writeObject (Object obj)
: 将指定的对象写出。
使用步骤,需要序列化对象实现类
1.创建ObjectOutputStream对象,构造方法中传递字节输出流
2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3.释放资源
序列化操作
1.一个对象要想序列化,必须满足两个条件:
-
该类必须 实现
java.io.Serializable
接口来启用序列化功能,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。实现Serializable
接口,会给类添加一个标记,当序列化和反序列化的时候,就会检测类上是否有这个标记 -
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。 -
static关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)被static修饰的成员变量不能被序列化
transient
瞬态关键字: 被其修饰,不能被序列化
public class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
2.写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
public class SerializeDemo{
public static void main(String [] args) {
Employee e = new Employee();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
fileOut.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
} catch(IOException i) {
i.printStackTrace();
}
}
}
输出结果:
Serialized data is saved
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
作用:把文件中保存的对象,以流的方式读取出来使用
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
参数InputStream in:字节输入流
特有成员方法:
public final Object readObject ()
: 从ObjectInputStream读取一个对象。
使用步骤:
1.创建一个ObjectInputStream对象,构造方法中传递字节输入流
2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
3.释放资源
4.使用读取出来的对象
反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream
读取对象的方法:
public final Object readObject ()
: 读取一个对象。
readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)当不存在对象的class文件时抛出异常
反序列化前提:
- 类必须实现
Serializable
- 必须存在类对应的class文件
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
e = (Employee) in.readObject();
// 释放资源
in.close();
fileIn.close();
}catch(IOException i) {
// 捕获其他异常
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
// 捕获类找不到异常
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
// 无异常,直接打印输出
System.out.println("Name: " + e.name); // zhangsan
System.out.println("Address: " + e.address); // beiqinglu
System.out.println("age: " + e.age); // 0
}
}
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
反序列化操作2
**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。**发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
问题:
每次修改类的定义,都会给class文件生成一个新得序列号
解决方法:
无论是否对类的定义进行修改,都不生成新的序列号
可以手动给类添加一个序列号
格式在Serializable
接口规定:
可序列化类可以通过声明为serialVersionUID的字段(该字段必须是static)最终final的long星字段,显示声明其自己的serialVersionUID
static final long serialVersionUID=42L
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
练习:序列化集合
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。 - 反序列化
list.txt
,并遍历集合,打印对象信息。
练习:
当我们想在文件中保存多个对象的时候
可以把多个对象存储到一个集合中
对象进行序列化和反序列化
分析:
-
定义一个存储Person对象的Array List集合
-
往ArrayList集合中存储Person对象
-
创建一个序列化流ObjectOutputStream
-
使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
-
创建一个反序列化Object InputStream对象
-
使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
-
把Object类型的集合转换为ArraysList类型
-
遍历ArrayList集合
-
释放资源
案例分析
- 把若干学生对象 ,保存到集合中。
- 把集合序列化。
- 反序列化读取时,只需要读取一次,转换为集合类型。
- 遍历集合,可以打印所有的学生信息
案例实现
public class SerTest {
public static void main(String[] args) throws Exception {
// 创建 学生对象
Student student = new Student("老王", "laow");
Student student2 = new Student("老张", "laoz");
Student student3 = new Student("老李", "laol");
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student2);
arrayList.add(student3);
// 序列化操作
// serializ(arrayList);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
// 读取对象,强转为ArrayList类型
ArrayList<Student> list = (ArrayList<Student>)
ois.readObject();
for (int i = 0; i < list.size(); i++ ){
Student s = list.get(i);
System.out.println(s.getName()+"--"+ s.getPwd());
}
}
private static void serializ(ArrayList<Student> arrayList) throws Exception {
// 创建 序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
// 写出对象
oos.writeObject(arrayList);
// 释放资源
oos.close();
}
}
打印流
概述
平时我们在控制台打印输出,是调用print
方法和println
方法完成的,这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
PrintStream类extends OutputStream
为其他输出流添加了功能,是他们能够方便的打印各种数据值表示形式
特点:
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,其永远不回抛出IOexception
- 特有方法:print,println可以输出任意类型的值
构造方法
-
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的打印流。输出目的地是一个文件路径 -
public PrintStream(File file)
: 使用指定的文件创建一个新的打印流。输出目的地是一个文件 -
public PrintStream(Output Stream)
: 使用指定的输出流创建一个新的打印流。输出目的地是一个字节输出流
构造举例,代码如下:
PrintStream ps = new PrintStream("ps.txt");
继承自父类的成员方法
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)
:将指定的字节输出流。
注意事项
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表
如果使用自己特有的print/println方法写数据,写的数据会原样输出
使用步骤
- 创建打印流PrintStream对象,构造方法中绑定要输出的目的地(会抛出文件找不到异常,不回抛出IO异常)
- 使用父类方法或自己特有方法
- 释放资源
改变打印流向
System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。
可以改变输出语句的目的地(打印流的流向)
输出语句,默认在控制台输出
使用System.setOut方法改变输出语句的目的地改为参数传递的打印流的目的地
static void setOut(PrintStream out )
重新分配“标准”输出流
public class PrintDemo {
public static void main(String[] args) throws IOException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);
// 创建打印流,指定文件的名称
PrintStream ps = new PrintStream("ps.txt");
// 设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出97
System.out.println(97);
ps.close();
}
}