File类
内存中的数据不能永久保存,只能用于临时运算
在计算机上能永久保存数据的只有硬盘,但是我们所写的数据只能跟内存进行交互,为了让数据可以与硬盘交互,从而引出了File类
- 定义
代表硬盘上的文件或文件夹(目录),它能新建、删除、重命名文件和目录。为java.io包下的类。
- 注意
File类是文件或目录的路径,而不是文件本身,因此File类不能直接访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
构造方法
构造方法 | 解释 |
---|---|
public File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。 String pathname 为硬盘文件或文件夹的路径 |
public File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例。 |
public File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的 File实例。 |
以盘符为起点的路径为绝对路径(对文件存放的位置无要求),如第一种构造方法中的参数。
相对路径(对文件存放的位置有要求),—项目的根目录
- 第一种构造方法
public File(String pathname)
该构造法并不直接在硬盘上创建一个新文件,它只是创建一个 File
对象,用于表示指定路径的文件。
import java.io.File;
public class TestOne {
public static void main(String[] args) {
//创建File类对象,其中参数pathname为硬盘上文件的路径,此时file代表硬盘上的hello.txt文件
File file = new File("F:\\node\\file\\hello.txt");
System.out.println(file.exists());//判断给出的文件路径是否有你要的这个hello1.txt文件
File file1 = new File("F:\\node\\file\\hello1.txt");
System.out.println(file1.exists());
System.out.println("-------------------------");
//创建File类对象,其中参数pathname为硬盘上文件夹的路径,此时file2代表硬盘上的file文件夹
File file2 = new File("F:\\node\\file");
System.out.println(file2.exists());//判断给出的文件路径是否有你要的这个file文件夹
File file3 = new File("F:\\node\\file1");
System.out.println(file3.exists());//判断给出的文件路径是否有你要的这个file1文件夹
//file4代表工程根目录下是否有hello.txt文件即F:\node\idea\Project01下是否有hello.txt文件
File file4 = new File("hello.txt");//相对路径---当前工程根目录下的hello.txt文件
System.out.println(file4.exists());
String absolutePath = file4.getAbsolutePath();//查看File对象的绝对路径
System.out.println("Directory: " + absolutePath);
File file5 = new File("");//相对路径---为工程根目录
String absolutePath1 = file5.getAbsolutePath();//查看File对象file5的绝对路径
System.out.println("Directory: " + absolutePath1);
}
}
- 第二种构造方法
public File(String parent, String child)
File
对象表示的路径将由 parent
和 child
连接而成(即通过父路径和子路径的组合创建一个 File
对象,表示文件或目录的路径)。如果 parent
为空(null 或者空字符串),则 child
参数直接作为路径名。如果 child
也为空(null 或者空字符串),则构造方法表示的路径就是 parent
。
public class TestTwo {
public static void main(String[] args) {
//创建File类对象,路径将由parent和child连接而成,此时file代表着F:\node\file文件夹下的hello.txt文件
File file = new File("F:\\node\\file", "hello.txt");
System.out.println(file.exists());//判断该文件夹下是否存在hello.txt文件
//创建File类对象,路径将由parent和child连接而成,此时file1代表着F:\node\file文件夹下的hello1.txt文件
File file1 = new File("F:\\node\\file", "hello1.txt");
System.out.println(file1.exists());//判断该文件夹下是否存在hello1.txt文件
System.out.println("------------------------------");
//创建File类对象,路径将由parent和child连接而成,此时file2代表着F:\node文件夹下的file文件夹
File file2 = new File("F:\\node", "file");
System.out.println(file2.exists());//判断该文件夹下是否存在file文件夹
//创建File类对象,路径将由parent和child连接而成,此时file3代表着F:\node文件夹下的file1文件夹
File file3 = new File("F:\\node", "file1");
System.out.println(file3.exists());//判断该文件夹下是否存在file1文件夹
System.out.println("------------------------------");
//创建File类对象,路径将由parent和child连接而成,若child为空则parent的值将作为整个路径,此时file4代表f盘下的node文件夹
File file4 = new File("F:\\node", "");
System.out.println(file4.exists());//判断f盘下是否有node文件夹
System.out.println("------------------------------");
//创建File类对象,路径将由parent和child连接而成,若parent为空则child的值将作为整个路径,且该路径是相对于当前工作目录的
//此时file5代表当前工作目录所在的盘下(f盘)是否有hello.txt文件---我的项目在f盘
File file5 = new File("", "hello.txt");
System.out.println(file5.exists());//判断当前工作目录所在的盘下是否有hello.txt文件
System.out.println(file5.getAbsolutePath());//查看File对象的绝对路径
System.out.println("------------------------------");
File file55 = new File("Day16", "hello.txt");
System.out.println(file55.exists());//判断当前工作目录所在的项目目录下的Day16目录下是否有hello.txt文件
System.out.println(file55.getAbsolutePath());//查看File对象的绝对路径
System.out.println("------------------------------");
File file555 = new File("123", "hello.txt");
System.out.println(file555.exists());//判断当前工作目录所在的项目目录下的123目录下是否有hello.txt文件
System.out.println(file555.getAbsolutePath());//查看File对象的绝对路径
System.out.println("------------------------------");
//若该构造方法的参数均为空则此时file6代表该项目所在的盘即file6为f盘
File file6 = new File("", "");
System.out.println(file6.exists());//判断f盘是否存在
System.out.println(file6.getAbsolutePath());//查看File对象的绝对路径
}
}
- 第三种构造方法
public File(File parent, String child)
路径将由 parent
对象的路径和 child
字符串连接而成。如果 child
是一个相对路径,则它将被追加到 parent
的路径上。如果 parent
为 null
,则 child
将被解释为相对于当前工作目录的路径。
public class TestThree {
public static void main(String[] args) {
File file0 = new File("F:\\node\\file");
File file2 = new File(file0, "hello.txt");
System.out.println(file2.exists());
File file3 = new File(file0, "hello1.txt");
System.out.println(file3.exists());
System.out.println("------------------------------");
File file00 = new File("F:\\node");
File file4 = new File(file00, "file");
System.out.println(file4.exists());
File file5 = new File(file00, "file1");
System.out.println(file5.exists());
System.out.println("------------------------------");
File file6 = new File(file00, "");
System.out.println(file6.exists());
String absolutePath0 = file6.getAbsolutePath();//查看File对象的绝对路径
System.out.println("Directory: " + absolutePath0);
}
}
- 三种构造方法直观对比
- 与上述三种构造方法不同的是:该内容为补充内容,项目名称为Day16,而上述三个构造方法的例子是在项目Project01下的模块Day16中
- 与上述三种构造方法不同的是:该内容为补充内容,项目名称为Day16,而上述三个构造方法的例子是在项目Project01下的模块Day16中
常用方法一
方法 | 解释 |
---|---|
public String getName() | 返回此抽象路径名表示的文件或目录的名称。(不包括该文件或目录的路径) |
public String getParent() | 返回此抽象路径名父项的路径名字符串,如果此路径名未指定父目录,则返回 null 。(即返回抽象路径名表示的文件或目录的名称所在的路径) |
public File getParentFile() | 返回此抽象路径名所在文件夹的File对象 |
public String getPath() | 将此抽象路径名转换为路径名字符串。 (即返回抽象路径名父项的路径名字符串和抽象路径名表示的文件或目录名称) |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串。 (不考虑创建文件时的方式) |
public File getAbsoluteFile() | 返回由绝对路径创建的File对象。 (不考虑创建文件时的方式) |
public String getCanonicalPath() | 与 public String getAbsolutePath() 功能一样,但使用该方法时会抛出异常 |
public File getCanonicalFile() | 与 public File getAbsoluteFile() 功能一样,但使用该方法时会抛出异常 |
- 注意
(1) public File getParentFile()
针对第一种构造方法
该方法在返回文件所在的文件夹对象时会考虑创建文件时的方式(即相对路径和绝对路径)。若是相对路径有两种情况:
若在根目录下:此时返回的对象为null
若在根目录下的目录下:此时返回的对象为根目录下的那个目录
public class MethodOne {
public static void main(String[] args) {
File file = new File("Day16\\hello.txt");//相对路径
File file1 = new File("hello.txt");//相对路径
File file2 = new File("F:\\node\\file\\hello.txt");//绝对路径
System.out.println(file1.exists());
System.out.println(file2.exists());
System.out.println(file1.getName());//返回没有路径的文件名
System.out.println(file2.getName());//返回没有路径的文件名
System.out.println("-------------------------------");
System.out.println(file.getParent());//返回文件所在的文件夹路径
System.out.println(file1.getParent());//返回文件所在的文件夹路径
System.out.println(file2.getParent());//返回文件所在的文件夹路径
System.out.println("-------------------------------");
System.out.println(file.getParentFile());//返回文件所在的文件夹对象
System.out.println(file1.getParentFile());//返回文件所在的文件夹对象
System.out.println(file2.getParentFile());//返回文件所在的文件夹对象
}
}
针对第二种构造方法
public class MethodOneOne {
public static void main(String[] args) {
File file = new File("Day16", "hello.txt");//相对路径------当前项目根目录下的Day16目录下的hello.txt文件
File file1 = new File("", "hello.txt");//相对路径------f盘下的hello.txt文件
File file2 = new File("F:\\node\\file", "hello.txt");//绝对路径
System.out.println(file.exists());
System.out.println(file1.exists());
System.out.println(file2.exists());
System.out.println(file.getAbsolutePath());
System.out.println("-------------------------------");
System.out.println(file.getName());//返回没有路径的文件名
System.out.println(file1.getName());//返回没有路径的文件名
System.out.println(file2.getName());//返回没有路径的文件名
System.out.println("-------------------------------");
System.out.println(file.getParent());//返回文件所在的文件夹路径
System.out.println(file1.getParent());//返回文件所在的文件夹路径
System.out.println(file2.getParent());//返回文件所在的文件夹路径
System.out.println("-------------------------------");
System.out.println(file.getParentFile());//返回文件所在的文件夹对象
System.out.println(file1.getParentFile());//返回文件所在的文件夹对象
System.out.println(file2.getParentFile());//返回文件所在的文件夹对象
}
}
(2) public String getPath()
该方法返回带有路径的文件名,它会根据创建文件时的方式(即相对路径和绝对路径)进行返回。若用的是绝对路径则该方法返回的带有路径的文件名就是绝对路径;反之则返回带有相对路径的文件名
public class MethodTwo {
public static void main(String[] args) {
File file1 = new File("hello.txt");//相对路径
File file2 = new File("F:\\node\\file\\hello.txt");//绝对路径
System.out.println(file1.getPath());
System.out.println(file2.getPath());
}
}
(3) public String getAbsolutePath()
只返回绝对路径,不考虑创建文件时的方式
public class MethodTwo {
public static void main(String[] args) {
File file1 = new File("hello.txt");//相对路径
File file2 = new File("F:\\node\\file\\hello.txt");//绝对路径
System.out.println(file1.getAbsolutePath());//返回绝对路径
System.out.println(file2.getAbsolutePath());//返回绝对路径
}
}
(4) public String getAbsoluteFile()
public class MethodTwo {
public static void main(String[] args) {
File file1 = new File("hello.txt");//相对路径
File file2 = new File("F:\\node\\file\\hello.txt");//绝对路径
System.out.println(file1.getAbsoluteFile());//返回用绝对路径创建的File对象
System.out.println(file2.getAbsoluteFile());//返回用绝对路径创建的File对象
}
}
(5)什么时候用getAbsolutePath()
和 getAbsoluteFile()
方法,什么时候用 getCanonicalPath()
和 getCanonicalFile()
方法
当需要非常精确的路径,且如果路径错误时能给出提示或有对应的处理方式则使用后两个方法;反之则使用前两个。
public class MethodTwo {
public static void main(String[] args) {
File file1 = new File("hello.txt");//相对路径
File file2 = new File("F:\\node\\file\\hello.txt");//绝对路径
try {
System.out.println(file1.getCanonicalPath());
System.out.println(file1.getCanonicalFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
System.out.println(file2.getCanonicalPath());
System.out.println(file2.getCanonicalFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
常用方法二
方法 | 解释 |
---|---|
public boolean createNewFile() | 创建路径下的硬盘文件(仅能创建文件,不能创建文件夹),如果文件已存在,则不会创建新文件,并返回 false 。 |
public boolean mkdir() | 创建此抽象路径名指定的硬盘文件夹(目录),如果目录已经存在,则mkdir() 方法不执行任何操作,并返回false 。 注意:该方法只能创建一级文件夹 |
public boolean mkdirs() | 创建此抽象路径名指定的硬盘文件夹(目录),包括任何必需但不存在的父目录。如果目录已经存在,则mkdirs() 方法不执行任何操作,并返回false 。 注意:该方法可创建多级文件夹 |
public long length() | 返回此抽象路径名表示的文件的长度(单位:字节)或目录所占用的空间大小 |
public boolean canRead() | 测试应用程序是否可以读取此抽象路径名表示的文件或目录。 (即检查指定的文件或目录是否可读) |
public boolean canWrite() | 测试应用程序是否可以修改此抽象路径名表示的文件或目录。 (即检查指定的文件或目录是否可写) |
public boolean isHidden() | 测试此抽象路径名指定的文件或目录是否隐藏。(即检查指定的文件或目录是否被标记为隐藏)如果文件或目录不是隐藏的或者不存在,则返回 false |
public boolean isFile() | 测试此抽象路径名表示的文件是否为一个文件。 如果 File 对象代表一个文件,则返回 true ;代表一个目录或者不存在,则返回 false |
public boolean isDirectory() | 测试此抽象路径名表示的文件是否为一个文件夹(目录)。 |
public boolean delete() | 删除此抽象路径名表示的文件或目录。 (即删除文件或文件夹)注意:删除文件夹时必须保证文件夹为空,否则无法删除 |
public class TestFour {
public static void main(String[] args) {
File file = new File("F:\\node\\file\\hello.txt");
long l = file.length();
System.out.println(l);
System.out.println(file.canRead());//判断文件是否可读
System.out.println(file.canWrite());//判断文件是否可写
System.out.println(file.isHidden());//判断是否时隐藏文件
System.out.println(file.isFile());//判断是否是文件
System.out.println(file.isDirectory());//判断是否是文件夹
}
}
- 注意
(1) public boolean createNewFile()
利用File类创建的file对象所指的文件若不存在则此时利用该方法将创建一个新文件
该方法会抛出异常,所以在使用时需要提前对异常进行处理
若由于文件系统或其他原因导致文件无法被创建时该方法会返回false并抛出
IOException
异常。;若文件已经存在,则此时该方法只会返回false,并不会抛出异常
代码示例------创建he.txt文件
F:\node\file文件夹下此时没有he.txt文件,如图所示:
public class TestFour {
public static void main(String[] args) {
File file = new File("F:\\node\\file\\he.txt");
try {
boolean created = file.createNewFile();
if (created) {
System.out.println("文件创建成功!");
} else {
System.out.println("文件已经存在!");
}
} catch (IOException e) {
System.out.println("创建文件时出现异常:" + e.getMessage());
}
}
}
运行截图如下:
(2)public boolean mkdir()
和public boolean mkdirs()
mkdir方法和mkdirs方法如果因为某种原因(如权限问题等)无法创建目录,则会抛出
SecurityException
异常。在编译时可以选择对异常进行处理也可不进行处理(不会报错),具体是否处理视情况而定。
代码示例
public boolean mkdir()
------创建he文件夹
F:\node\file文件夹下此时没有he文件夹
public class TestFour {
public static void main(String[] args) {
File file = new File("F:\\node\\file\\he");
boolean res = file.mkdir();
if (res) {
System.out.println("目录创建成功!");
} else {
System.out.println("目录已经存在或者创建失败!");
}
}
}
public boolean mkdirs()
------创建hee文件夹及其子文件夹hee1
F:\node\file文件夹下此时没有hee文件夹,如图所示:
public class TestFour {
public static void main(String[] args) {
File file = new File("F:\\node\\file\\hee\\hee1");
boolean res = file.mkdirs();
if (res) {
System.out.println("目录及其父目录创建成功!");
} else {
System.out.println("目录已经存在或者创建失败!");
}
}
}
常用方法三
方法 | 解释 |
---|---|
public String[] list() | 返回一个String数组,用于命名此抽象路径名表示的目录中的文件和目录。 (即返回该File目录中的所有子文件和子目录) |
public String[] list(FilenameFilter filter) | 返回一个String数组,用于命名由此抽象路径名表示的目录中的文件和目录,以满足指定的过滤器。 (即返回所有满足指定过滤器的文件和目录) |
public File[] listFiles() | 返回一个抽象路径名数组,表示此抽象路径名表示的目录中的文件。(即返回一个文件夹下所有的子文件夹及文件File对象)注意:只能返回文件夹下的一级 |
public File[] listFiles(FileFilter filter) | 返回一个抽象路径名数组,表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 (即返回所有满足指定过滤器的文件和目录的File对象) |
public File[] listFiles(FilenameFilter filter) | 返回一个抽象路径名数组,表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 (即返回所有满足指定过滤器的文件和目录的File对象) |
public String[] list()
和public File[] listFiles()
public class MethodThree {
public static void main(String[] args) {
File file1 = new File("F:\\node\\file");
File file2 = new File("F:\\node");
String[] s1 = file1.list();//返回该File目录中的所有子文件和子目录
for (String s : s1) {
System.out.println(s);
}
System.out.println("-----------------------------");
String[] s2 = file2.list();
for (String s : s2) {
System.out.println(s);
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
File[] file0 = file1.listFiles();//返回一个文件夹下所有的子文件夹及文件File对象
for (File file3 : file0) {
System.out.println(file3);
}
System.out.println("-------------------------------");
File[] file00 = file2.listFiles();
for (File file : file00) {
System.out.println(file);
}
}
}
public String[] list(FilenameFilter filter)
public class TestFour {
public static void main(String[] args) {
File file = new File("F:\\node");
String[] ss = file.list(new FilenameFilter() {
@Override
//dir------表示当前正在处理的目录的File对象。
//name------表示目录中的文件或子目录的名称。
//accept()方法的主要功能是判断给定目录中的文件或子目录是否应该被包含在结果列表中。
public boolean accept(File dir, String name) {
return name.length() == 4;//判断文件或文件夹名长是否为4,若为4则返回true
}
});
for (String s : ss) {
System.out.println(s);
}
}
}
public File[] listFiles(FileFilter filter)
public class TestFive {
public static void main(String[] args) {
File file = new File("F:\\node");
File[] files = file.listFiles(new FileFilter() {
@Override
//filee------表示目录中的文件或子目录的File对象
//accept()方法的主要功能是判断给定的文件或子目录是否应该被包含在结果列表中
public boolean accept(File filee) {
return filee.isFile();//若该File对象是文件则返回true
}
});
for (File file1 : files) {
System.out.println(file1);
}
}
}
IO流
- 作用
(1)将数据写到文件中,实现数据的永久化存储
(2)读取文件中已经存在的数据
- IO解释
(1)I表示input,是数据从硬盘进内存的过程,称为读
(2)O表示output,是数据从内存到硬盘的过程,称为写
- 注意:后期会简化IO的输入输出代码
- Web项目的pom.xml文件中可导入apache的
commons-io
依赖,它是apache提供的一种IO工具类可简化输入输出流 - 它有一个类
IOUtils
,可利用该类中的copy()方法来直接将输入流数据copy到输出流,各种copy方法如图所示。(具体使用可详见Http&Servlet&Request&Response
这部分内容中的Response响应字节数据
)
- Web项目的pom.xml文件中可导入apache的
IO流的分类
IO流的分类一般是按照数据类型来分的
- 按数据流向分:输入输出流
(1)输入流:把数据从其他设备上读取到内存中的流(数据从硬盘进内存的过程)。------以InputStream
,Reader结尾
(2)输出流:把数据从内存中写出到其他设备上的流(数据从内存到硬盘的过程)。------以OutputStream,Writer结尾
- 按数据类型分:字节字符流
(1)字节流:以字节为单位,读写数据的流。------以InputStream,OutputStream结尾
(2)字符流:以字符为单位,读写数据的流。------以Reader,Writer结尾
注意:
字节流可以操作所有类型的文件,包括音频、视频、图片、office等
字符流只能操作纯文本文件(即能用windows自带记事本打开并且能读懂的文件就是纯文本文件),比如:java文件、txt文件等
word、excel不属于纯文本文件,因为用记事本打开是乱码,读不懂,如图所示
- 按角色分:节点流和处理流
(1)节点流:可以从或像一个特定的地方(节点)读写数据,如FileReader
(2)处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。
四大顶级抽象父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
英文是一个字节属于一个字符,而中文是三个字节属于一个字符
字节流
字节输出流------FileOutputStream
类
- 字节流写数据步骤:
(1)创建字节输出流对象
若文件不存在则创建一个新文件;若文件存在则清空文件内容并从头写入
(2)写数据
传递整数时,实际写到文件中的是这个整数在ASCII表中对应的字符
(3)释放资源
若无此步骤则若想删除硬盘上的文件时就会无法删除
- 构造器
构造方法 | 解释 |
---|---|
public FileOutputStream(String name) throws FileNotFoundException | 创建一个文件输出流以写入由指定的文件名表示的文件。如果文件不存在,则创建一个新的文件;如果文件已存在,则清空文件内容,并从头开始写入。如果文件名指定的路径不存在,会抛出 FileNotFoundException 异常。name 是文件的路径名 |
public FileOutputStream(File file) throws FileNotFoundException | File file :要写入的文件对象。 |
public FileOutputStream(String name, boolean append) throws FileNotFoundException | boolean append :是否在文件末尾追加写入数据。如果为 true,则会在文件末尾追加写入数据;如果为 false,则会清空文件内容,并从头开始写入。 |
(1)第一种和第三种构造方法底层用的都是File类中的
public File(String pathname)
构造方法,所以相对路径与该构造方法的相对路径一致(2)以上三种构造方法都会在文件不存在时自动创建文件(但JDK1.8更早版本不会自动创建文件,所以JDK1.8要先判断文件是否存在,如下代码所示)
public class TestOne {
public static void main(String[] args) {
FileOutputStream fos = null;
File file = new File("F:\\node\\55.avi");
if (!(file.exists())) {//判断文件是否存在
try {
file.createNewFile();//若文件不存在则创建文件
} catch (IOException e) {
e.printStackTrace();
}
}
try {
fos = new FileOutputStream(file);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:
在使用
FileOutputStream
时,需要注意文件路径的正确性,若文件名指定的路径不存在,会抛出FileNotFoundException
受检异常。所以需要在编译时对异常进行处理
public class TestOne {
public static void main(String[] args) throws IOException {
//Step1:创建字节输出流对象------告诉虚拟机要往哪个文件中写数据
FileOutputStream fos = new FileOutputStream("F:\\node\\hello.txt");
//Step2:写数据
//传递整数时,实际写到文件中的是这个整数在ASCII表中对应的字符
fos.write(98);//98对应小写字母b
//Step3:释放资源------若无此步骤则若想删除硬盘上的文件时会无法删除
fos.close();
}
}
字节流写数据的四种方式
前三种方式
方法 | 解释 |
---|---|
public void write(int b) | 一次写一个字节数据 |
public void write(byte[] b) | 一次写一个字节数组数据 |
public void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据。即将字节数组 b 中从索引 off 开始的 len 个字节写入输出流 |
第四种方式
将字符转换成字节:在字符串后加入方法public byte[] getbytes()
,然后带入上述三种方法中即可
例题:向当前路径下的hello.txt文件中写入abc
- 方式一------一次写一个字节数据
public class TestOne {
public static void main(String[] args) throws IOException {
//Step1:创建字节输出流对象------告诉虚拟机要往哪个文件中写数据
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
//Step2:写数据
//传递整数时,实际写到文件中的是这个整数在ASCII表中对应的字符
fos.write(97);//97对应小写字母a
fos.write(98);//98对应小写字母b
fos.write(99);//99对应小写字母c
//Step3:释放资源------若无此步骤则若相删除硬盘上的文件时会无法删除
fos.close();
}
}
- 方式二------一次写一个字节数组数据
public class TestOne {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
byte[] bys = {97,98,99};
fos.write(bys);
fos.close();
}
}
- 方式三------一次写一个字节数组的部分数据
public class TestOne {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
byte[] bys = {97,98,99,100,101,102};
fos.write(bys, 0, 3);
fos.close();
}
}
- 方式四------利用方法
public byte[] getbytes()
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
//JDK1.8之前
public class FosOne {
public static void main(String[] args) throws IOException {
FileOutputStream fos = null;
File file = new File("fos.txt");
if (!(file.exists())) {
file.createNewFile();
}
fos = new FileOutputStream(file);
fos.write("Hello World111".getBytes());
fos.close();
}
}
注意:利用字节流写数据时如何实现换行
- 针对方式一
写完数据后利用write方法加换行符,但是write方法参数是byte字节,所以需要将换行符号转换成字节,此时就需要用到String类中的
public byte[] getbytes()
方法—作用是:使用平台默认的字符集将该String编码为一系列字节并将结果存储到新的字节数组中windows:\r\n
Linux:\n
Mac:\r
public class TestOne {
public static void main(String[] args) throws IOException {
//Step1:创建字节输出流对象------告诉虚拟机要往哪个文件中写数据
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
//Step2:写数据
//传递整数时,实际写到文件中的是这个整数在ASCII表中对应的字符
fos.write(97);//97对应小写字母a
fos.write("\r\n".getBytes());
fos.write(98);//98对应小写字母b
fos.write("\r\n".getBytes());
fos.write(99);//99对应小写字母c
fos.write("\r\n".getBytes());
//Step3:释放资源------若无此步骤则若相删除硬盘上的文件时会无法删除
fos.close();
}
}
- 针对方式二和方式三
换行符对应的整数是10
public class TestOne {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
byte[] bys = {97,10,98,10,99};
fos.write(bys);
fos.close();
}
}
public class TestOne {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hello.txt");//相对路径---当前工程根目录下的hello。txt文件
byte[] bys = {97,10,98,10,99,10,100,10,101,10,102};
fos.write(bys, 0, 6);
fos.close();
}
}
字节流写数据时的异常处理
- 方式一:利用try…catch…finally语句块进行异常处理
在进行写数据异常处理时,使用finally快来执行所有的清除操作(eg:IO流中的释放资源),因为finally语句块一定会执行,除非JVM退出
public class TestOne {
public static void main(String[] args) {
//字节输出流`FileOutputStream`类的声明必须在main块中,不能在try块中
FileOutputStream fos = null;
try {
fos = new FileOutputStream("hello.txt");
fos.write(97);//97对应小写字母a
fos.write("\r\n".getBytes());
fos.write(98);//98对应小写字母b
fos.write("\r\n".getBytes());
fos.write(99);//99对应小写字母c
fos.write("\r\n".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:
(1)字节输出流
FileOutputStream
类的声明必须在main块中,不能在try块中,因为当声明在try块中时它的作用域仅限于try
块内部,在catch
和finally
块中无法访问fos
变量,因为超出了它的作用域;而try块、catch块、finally块均在main块中,所以可以在main块中进行字节输出流类的声明(2)
FileOutputStream
类构造方法可能会抛出FileNotFoundException
异常,而write方法可能会抛出IOException
异常,这两个异常都是受检异常,所以需要在编译时进行处理,又因为两种异常均属于Exception异常的子类,所以catch块直接catch (Exception e)
(3)为什么释放资源语句
fos.close()
放在finally语句块中?在执行
fos = new FileOutputStream("hello.txt");
这句代码时可能会因为路径不存在而抛出FileNotFoundException
异常,若fos.close()
放在try块中且此时刚好fos = new FileOutputStream("hello.txt");
抛出异常则此时不会继续执行try块的内容,转而执行catch块中的内容,这就会导致无法释放资源,所以需要把fos.close()
放在finally块中,但是又因为若fos = new FileOutputStream("hello.txt");
抛出异常则fos=null,此时就不需要释放资源,所以在进行fos.close()
之前要利用if语句来判断fos是否为空
- 方式二:利用
try-with-resources
语句块进行异常处理
try-with-resources
是 Java 7 中引入的一个语言特性,用于简化资源管理和异常处理。它是一种用于自动关闭可关闭资源的机制。
**注意:
(1)资源对象必须实现
AutoCloseable
或Closeable
接口,否则无法在try-with-resources
中使用。(2)在
try-with-resources
语句中,如果资源初始化失败,将不会尝试去关闭未初始化的资源(即此时不会去隐式的释放资源),因为资源对象可能为空或者不可用。
public class TestOne {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("hello.txt");){
fos.write(97);//97对应小写字母a
fos.write("\r\n".getBytes());
fos.write(98);//98对应小写字母b
fos.write("\r\n".getBytes());
fos.write(99);//99对应小写字母c
fos.write("\r\n".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
字节输入流------FileInputStream
类
从硬盘中读取数据到内存中
由于字节输入流
InputStream
为抽象类(无法实例化),所以需要用到其子类FileInputStream
类来进行字节输入的实现
- 字节流读数据步骤:
(1)创建字节输入流对象
(2)读数据
(3)释放资源
- 构造器
该构造方法会在编译时抛出异常,需在编译时进行处理
构造方法 | 解释 |
---|---|
public FileInputStream(String name) throws FileNotFoundException | 创建一个文件输入流以从文件系统中的文件读取数据。如果文件不存在,或者由于某些其他原因无法打开文件进行读取,则会抛出 FileNotFoundException 异常。 name 是要读取的文件的路径名 |
FileInputStream(File file) throws FileNotFoundException | 通过传入一个 File 对象,指定要读取的文件,如果文件不存在或者由于某些原因无法打开,则会抛出 FileNotFoundException 异常。 |
第一种构造方法底层用的是File类中的
public File(String pathname)
构造方法,所以相对路径与该构造方法的相对路径一致
注意:
在使用
FileInputStream
时,需要注意文件路径的正确性,若文件名指定的路径不存在,会抛出FileNotFoundException
受检异常。所以需要在编译时对异常进行处理
读hello.txt文件,hello.txt文件内容如图所示
public class TestOne {
public static void main(String[] args) throws IOException {
//Step1:创建字节输入流对象------告内存要从哪个文件中读数据
FileInputStream fis = new FileInputStream("F:\\node\\hello.txt");
//Step2:读数据
//读到的字符对应会转换为ASCII集中的整数
int read = fis.read();
//若想 得到字符数据则需要进行强转即:char read = (char)fis.read();
System.out.println(read);
//Step3:释放资源------若无此步骤则若想删除硬盘上的文件时会无法删除
fis.close();
}
}
字节流读数据的三种方式
方法 | 解释 |
---|---|
public int read() | 从输入流中读取下一个字节的数据。该方法会返回读取到的字节数据的整数表示(0-255),如果已经到达流的末尾,则返回-1(即此时已经读取完毕)。注意:read()方法返回的是字节数据的整数表示,如果需要将其转换为字符或其他数据类型,需要进行相应的类型转换。在使用完输入流后,应该及时关闭输入流以释放资源。 |
public int read(byte[] b) | 用于从输入流中读取一定数量的字节数据到字节数组 b 中,并返回实际读取的字节数量。(即存储从输入流中读取的字节数据,一次读取一个字节数组的内容)如果在读取字节时已经到达流的末尾,则返回-1(即此时已经读取完毕) |
public int read(byte[] b, int off, int len) | 从文件输入流中读取字节数据到指定的字节数组中。即将最多 len 个字节从文件输入流读取到字节数组 b 中,从 off 指定的位置开始存储。方法返回实际读取的字节数,如果到达文件末尾,则返回 -1。 |
注意:
(1)第一个read方法一次只读取一个字节,返回值即为本次读到的那个字节数据(即字符在码表中对应的整数),如果想要得到字符数据则需要进行强转
若想利用第一个read方法进行文件所有内容的读取需要利用while循环,如下所示
public class TestTwo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("hello.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.println((char) b);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
注意:
第二个read方法可读取多个字节,但需搭配File类使用,如下代码所示
其中使用String类的原因:因为可能文件中可能有中文也可能有英文,会带来乱码问题,所以直接利用String来处理乱码,转变为能看懂的文字。
public class TestTwo {
public static void main(String[] args) {
File file = new File("hello.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] b = new byte[(int)(file.length())];
fis.read(b);//一次读取一个字节数组的内容
String word = new String(b);//将字节数组传到String中进行编码成能看懂的内容
System.out.println(word);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
文件复制
将硬盘上的某个文件复制到硬盘另一个地方
- 原理
复制文件:把文件的内容从文件中(数据源)读取出来并写入到另一个文件中(目的地)
小文件复制
- 示例
将E:\大数据\Java开发手册(嵩山版).pdf复制到当前工程目录(Project01)下
步骤:
- 读数据------
FileInputStream
- 写数据------
FileOutputStream
public class TestThree {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建字节输入流------读取数据
fis = new FileInputStream("E:\\大数据\\Java开发手册(嵩山版).pdf");
//创建字节输出流------写入数据
fos = new FileOutputStream("Java开发手册(嵩山版).pdf");
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
大文件复制------小数组拷贝
小文件复制的方法比较慢,若遇到大文件就耗时较长
- 解决方式
字节流通过创建字节数组,可以此次读写多个数据
- 代码实现
public class TestThree {
public static void main(String[] args) {
File file = new File("E:\\大数据\\Java开发手册(嵩山版).pdf");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建字节输入流------读取数据
fis = new FileInputStream(file);
//创建字节输出流------写入数据
fos = new FileOutputStream("Java开发手册(嵩山版).pdf");
byte[] b = new byte[(int)(file.length())];
int len;
while ((len = fis.read(b)) != -1) {
fos.write(b, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
字节缓冲流------提高读和写的效率
- 对应的类
字节缓冲输出流------BufferedOutputStream
字节缓冲输入流------BufferedInputStream
- 注意
字节缓冲流不能直接操作文件,需要传递字节流
- 两种缓冲流对应的构造方法
- 字节缓冲输出流
构造方法 | 解释 |
---|---|
public BufferedOutputStream(OutputStream out) | 创建新的缓冲输出流以将数据写入指定的基础输出流。 |
public BufferedOutputStream(OutputStream out, int size) | 创建新的缓冲输出流,以使用指定的缓冲区大小将数据写入指定的基础输出流。 |
参数解释:
out
:是一个OutputStream
类型的参数,表示要包装的目标输出流。也就是说,BufferedOutputStream
将会包装这个输出流,并在其上提供额外的缓冲功能。
size
:这是一个整型参数,表示缓冲区的大小,即缓冲区可以容纳的字节数量。这个参数决定了缓冲输出流在内部的缓冲区中能够存储多少字节的数据。
2.字节缓冲输入流
构造方法 | 解释 |
---|---|
public BufferedInputStream(InputStream in) | 创建一个 BufferedInputStream 并保存其参数,即输入流 in ,供以后使用。 |
public BufferedInputStream(InputStream in, int size) | 创建具有指定缓冲区大小的 BufferedInputStream ,并保存其参数(输入流 in )供以后使用。 |
参数解释:
in
:是一个InputStream
类型的参数,表示要包装的目标输入流。也就是说,BufferedInputStream
将会包装这个输入流,并在其上提供额外的缓冲功能。
size
:这是一个整型参数,表示缓冲区的大小,即缓冲区可以容纳的字节数量。这个参数决定了缓冲输入流在内部的缓冲区中能够存储多少字节的数据。
- 问题
1.为什么构造方法需要的是字节流而不是具体的文件或路径?
字节缓冲流仅仅提供缓冲区,提高读和写的效率,而真正读写数据还得依靠基本的字节流对象进行操作
缓冲流一次读写一个字节
public BufferedInputStream(InputStream in)
底层原理
BufferedInputStream
构造器底层原理:
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
/*
其中DEFAULT_BUFFER_SIZE为私有静态常量代码如下:
其有一个固定不变的值8192
*/
private static final int DEFAULT_BUFFER_SIZE = 8192;
this指的代码如下:
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
由此可知该构造器的底层原理其实是创建了一个长度为8192的字节数组
BufferedInputStream
类中close
方法源码如下:
public void close() throws IOException {
byte[] buffer;
while ( (buffer = buf) != null) {
if (U.compareAndSetReference(this, BUF_OFFSET, buffer, null)) {
InputStream input = in;
in = null;
if (input != null)
input.close();
return;
}
// Else retry in case a new buf was CASed in fill()
}
}
由字节缓冲输入流的
close
方法源码可看出在其close源码中已经进行了判断字节输入流是否为null
,所以在使用字节缓冲输入流中的close
方法时就不需要去判断是否为null
了
public BufferedOutputStream(OutputStream out)
底层原理
BufferedOutputStream
构造器底层原理
public BufferedOutputStream(OutputStream out) {
this(out, initialBufferSize(), DEFAULT_MAX_BUFFER_SIZE);
}
/*
其中DEFAULT_MAX_BUFFER_SIZE为私有静态常量代码如下:
其有一个固定不变的值8192
*/
private static final int DEFAULT_MAX_BUFFER_SIZE = 8192;
this指的代码如下:
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
由此可知该构造器的底层原理其实是创建了一个长度为8192的字节数组
在使用BufferedInputStream类和BufferedOutputStream类中的close方法之前不需要检查字节输入输出流是否为null,因为底层已经进行了检查
- 代码实现
将E:\大数据\Java开发手册(嵩山版).pdf复制到当前工程目录(Project01)下
public class TestFour {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//创建字节缓冲输入流
bis = new BufferedInputStream(new FileInputStream("E:\\大数据\\Java开发手册(嵩山版).pdf"));
//创建字节缓冲输出流
bos = new BufferedOutputStream(new FileOutputStream("Java开发手册(嵩山版)"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bis.close();
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
缓冲流一次读写一个字节数组
public class TestFive {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("E:\\大数据\\Java开发手册(嵩山版).pdf"));
bos = new BufferedOutputStream(new FileOutputStream("开发手册(嵩山版).pdf"));
byte[] b = new byte[1024*1024];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bis.close();
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
字符流
- 延伸
- 计算机中存储的信息都是用二进制表示
- 按照某种规则将字符变成二进制再存储到计算机中称为编码
- 按照同样的规则将存储在计算机中的二进制数解析显示出来称为解码
- 编码和解码方式必须一致,否则会导致乱码
- 过程如下:假设存储一个字符a,首先需要在码表中查到对应的数字是97,然后转换成二进制存储。读取时要先把二进制解析出来再转成97,最后通过查找到对应的字符是a
- Arrays类中的toString方法
toString方法 | 解释 |
---|---|
public static String``toString(boolean[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String``toString(byte[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String``toString(char[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String``toString(double[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String toString(float[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String toString(int[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String toString(long[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String toString(short[] a) | 返回指定数组的内容的字符串表示形式。 |
public static String toString(Object[] a) | 返回指定数组的内容的字符串表示形式。 |
- 编码表
- ASCII:包括了数字、大小写字符和一些常见的标点符号。(ASCII表没有中文)
- GBK:windows系统默认码表,兼容ASCII码表,包含21003个汉字,并支持繁体汉字和部分日韩文字。(GBK是中国的码表,一个中文以两个字节的形式存储,但不包含世界上所有国家的文字)
- Unicode:统一的万国码,是计算机领域的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。(由于可以表示的字符太多,所以Unicode码表中的数字不是直接以二进制形式存储到计算机的,会先通过UTF-7,UTF-8,UTF-16以及UTF-32进行编码,再存储到计算机,其中最为常见的就是UTF-8)
- 汉字存储和展示过程解析
UTF-8编码后一个中文以三个字节的形式存储
idea和以后工作默认使用Unicode的UTF-8编解码格式,一个中文三个字节
- 字符串中的编码解码方法------String类中的方法
编码方法 | 解释 |
---|---|
public byte[] getBytes() | 使用平台的默认字符集(一般默认为UTF-8)将该 String 编码为一系列字节,将结果存储到新的字节数组中。 |
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException | 使用命名的字符集将该 String 编码为一系列字节,将结果存储到新的字节数组中。 |
解码方法—利用String类的构造方法 | 解释 |
---|---|
public String(byte[] bytes) | 通过使用平台的默认字符集(一般默认为UTF-8)解码指定的字节数组来构造新的 String 。 |
public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException | 通过指定的字符集编码指定的字节数组来构造新的String |
charsetName主要有UTF-8、GBK等
public class TestSIx {
public static void main(String[] args) {
//编码
String s = "你好";
byte[] bytes1 = s.getBytes();
System.out.println(Arrays.toString(bytes1));
try {
byte[] bytes2 = s.getBytes("UTF-8");
System.out.println(Arrays.toString(bytes2));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("-----------------以上为编码一下为解码------------------");
//解码
//“你好”对应的UTF-8的编码是-28, -67, -96, -27, -91, -67
byte[] b = {-28, -67, -96, -27, -91, -67};
String s1 = new String(b);
System.out.println(s1);
try {
String s2 = new String(b, "UTF-8");
System.out.println(s2);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
字符流读取中文
- 字节流读取文本文件时为什么可能会出现乱码?
因为字节流一次读一个字节,而不论是GBK还是UTF-8,一个中文都对应多个字节,用字节流每次只能都其中一部分,所以会出现乱码问题
- 字符流读取中文的过程
字符流 = 字节流+编码表
不管在哪种码表中,中文的第一个字节一定是负数,例如:“a你好”的字节(以UTF-8为例)为
97
,
−
28
,
−
67
,
−
96
,
−
27
,
−
91
,
−
67
97, -28, -67, -96, -27, -91, -67
97,−28,−67,−96,−27,−91,−67
读取过程解析:当字符流读到97时会对应编码表显示其为字符a,然后接着往下读,当读到-28时,由于中文的第一个字节一定是个负数,所以此时系统知道读到中文字符了,此时字符流会根据编码格式进行字符的读取(此处的编码格式是UTF-8),所以此时字符流会自动读取三个字节并将其转换为中文字符。
注意:在读取中文字符对应的字节时,系统会根据不同的编码格式进行不同字节个数的读取,然后将其转换为中文字符。例如:编码格式为UTF-8时,系统会一次读取三个字节;编码格式为GBK时,系统会一次读取两个字节
- 结论
- 若要进行拷贝则一律使用字节流或字节缓冲流
- 若要把文本文件中的数据读到内存中,则使用字符输入流
- 若要把内存中的数据写到文本文件中,则使用字符输出流
- GBK码表中一个中文两个字节,UTF-8编码格式中一个中文三个字节
字符输出流------FileWriter
- 字符流写数据步骤
(1)创建字符输出流对象
若文件不存在则会自动创建,但要保证父级路径的存在
若文件存在则会自动清空内容
(2)写数据
写出的int型整数,实际写出的是整数在码表上对应的字符
写出字符串数据,是把字符串本身原样写出(注意:特殊字符串除外,比如换行)
(3)释放资源
- 构造器
构造方法 | 解释 |
---|---|
public FileWriter(File file) | 给 File 写一个 FileWriter ,使用平台的默认编码 |
public FileWriter(File file, Charset charset) | 给 File 写一个 FileWriter ,并使用指定的编码格式 |
public FileWriter(File file, boolean append) | 在给出要写入的 FileWriter 下构造 File ,并使用平台的默认编码,指示是否追加写入的数据。 |
public FileWriter(File file, Charset charset, boolean append) | 在给出要写入的 FileWriter 下构造 File ,并使用指定的编码格式,并指示是否追加写入的数据 |
public FileWriter(String fileName) | 构造一个 FileWriter 给出文件名,使用平台的默认编码 |
FileWriter(String fileName, Charset charset) | 构造一个 FileWriter 给出文件名,并使用指定的编码格式 |
public FileWriter(String fileName, boolean append) | 使用平台的默认编码构造一个 FileWriter 给定一个文件名和一个布尔值,指示是否追加写入的数据。 |
public FileWriter(String fileName, Charset charset, boolean append) | 使用平台的默认编码构造一个 FileWriter ,并使用指定的编码格式,并指示是否追加写入的数据。 |
注意:
以上构造方法的底层用的是File类中的
public File(String pathname)
构造方法,所以相对路径与该构造方法的相对路径一致Charset类中的静态方法
public static Chaeset forName(String charsetName)
用来指定编码格式,带Charset的构造器在JDK11之后才开始出现,在JDK11之前用的是转换流来进行编码格式的转换
字符流写数据的五种方式
方法 | 解释 |
---|---|
public void write(int c) | 写一个字符 |
public void write(char[] cbuf) | 写一个字符数组 |
public void write(char[] cbuf, int off, int len) | 写出字符数组的一部分。即将字符数组 cbuf 中从索引 off 开始的 len 个字节写入输出流 |
public void write(String str) | 写一个字符串 |
public void write(String str, int off, int len) | 写一个字符串的一部分。即将字符串 str 中从索引 off 开始的 len 个字节写入输出流 |
注意:利用字符流写数据时如何实现换行
写完数据后直接利用write方法加换行符
windows:\r\n
Linux:\n
Mac:\r
public class TestSeven {
public static void main(String[] args) throws IOException {
//Step1:创建字符输出流对象
FileWriter fw2 = new FileWriter("a.txt");
//等同于FileWriter fw1 = new FileWriter(new File("a.txt"));
//Step2:写数据------一次写一个字符
fw2.write(97);
fw2.write(98);
fw2.write(99);
fw2.write("\r\n");
//Step2:写数据------一次写一个字符数组
char[] chars = {100, 101, 102, 10};
fw2.write(chars);
//Step2:写数据------一次写一个字符数组的一部分
fw2.write(chars,0, 2);
//Step2:写数据------一次写一个字符串
fw2.write("你好啊");
fw2.write("\r\n");
//Step2:写数据------一次写一个字符串的一部分
fw2.write("你好啊", 0, 2);
//Step3:释放资源
fw2.close();
}
}
- 字符输出流中的
close()
方法和flush()
方法
**close()方法
:**字符输出流中的close()
方法实际上是关闭流的操作,但是在使用close()
方法时会自动进行文件刷新致使数据到文件中,但是一旦使用close()
方法关闭流之后就无法向文件中写数据了
**flush()
方法:**刷新文件使数据到文件中,在使用flush()方法之后仍可向文件中写数据。
字符输入流------FileReader
- 构造器
构造方法 | 解释 |
---|---|
public FileWriter(File file) | 给 File 写一个 FileWriter ,使用平台的默认编码 |
public FileWriter(File file, boolean append) | 在给出要写入的 FileWriter 下构造 File ,使用平台的默认编码,并指示是否附加写入的数据。 |
public FileReader(File file, Charset charset) throws IOException | 在给出要写入的 FileWriter 下构造 File ,并使用指定编码 |
public FileWriter(String fileName) | 构造一个 FileWriter 给出文件名,使用平台的默认编码 |
public FileWriter(String fileName, boolean append) | 使用平台的默认编码构造一个 FileWriter 给定一个文件名和一个布尔值,并指示是否附加写入的数据。 |
public FileReader(String fileName, Charset charset) | 构造一个 FileWriter 给出文件名,并使用指定编码 |
注意:
以上构造方法的底层用的是File类中的
public File(String pathname)
构造方法,所以相对路径与该构造方法的相对路径一致Charset类中的静态方法
public static Chaeset forName(String charsetName)
用来指定编码格式,带Charset
的构造器在JDK11之后才开始出现,在JDK11之前用的是转换流来进行编码格式的转换
字符流读数据的方式
方法 | 解释 |
---|---|
public int read() | 读一个字符 |
public int read(char[] cbuf) | 将字符读入数组 |
public int read(char[] cbuf, int off, int len) | 从文件字符输入流中读取字符数据到指定的字符数组中。即将最多 len 个字符从文件字符输入流读取到字符数组 cbuf 中,从 off 指定的位置开始存储。方法返回实际读取的字符数,如果到达文件末尾,则返回 -1。 |
- 一次读取一个字符
public class TestEight {
public static void main(String[] args) throws IOException {
//Step1:创建字符输入流对象
FileReader fr = new FileReader("a.txt");
// FileReader fr2 = new FileReader(new File("a.txt"));
//Step2:读取数据
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
//Step3:释放资源
fr.close();
}
}
- 一次读取多个字符
public class TestEight {
public static void main(String[] args) throws IOException {
//Step1:创建字符输入流对象
FileReader fr = new FileReader("a.txt");
//Step2:读取数据
char[] chars = new char[1024];
int len;
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
//Step3:释放资源
fr.close();
}
}
字符缓冲流------提高读和写的效率
- 对应的类
字符缓冲输出流------BufferedWriter
字符缓冲输入流------BufferReader
- 两种缓冲流对应的构造方法
-
字符缓冲输出流
构造方法 解释 public BufferedWriter(Writer out)
创建使用默认大小的输出缓冲区的缓冲字符输出流。 public BufferedWriter(Writer out, int sz)
创建一个使用给定大小的输出缓冲区的新缓冲字符输出流。 参数解释:
out
:一个Writer
对象,代表要包装的底层字符输出流。sz
:一个整数,代表缓冲区大小。如果sz
是负数,则抛出IllegalArgumentException
异常。一次写一次字符
public class TestEight { public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); bw.write(97); bw.write(10);//换行,等同于bw.writer("\r\n") bw.write(98); bw.close(); } }
一次写多个字符
public class TestEight { public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); char[] chars = {97, 98, 99, 10}; bw.write(chars);//写一个字符数组 bw.write(chars, 0, 2);//写字符数组的一部分 bw.write("你好啊");//写字符串 bw.write("欸呀呀", 0, 2);//写字符串的一部分 bw.flush(); bw.close(); } }
-
字符缓冲输入流
构造方法 | 解释 |
---|---|
public BufferedReader(Reader in) | 创建使用默认大小的输入缓冲区的缓冲字符输入流。 |
public BufferedReader(Reader in, int sz) | 创建使用指定大小的输入缓冲区的缓冲字符输入流。 |
参数解释:
in
:一个Reader
对象,代表要包装的底层字符输入流。
sz
:一个整数,代表缓冲区大小。如果sz
是负数,则抛出IllegalArgumentException
异常。
一次读一个字符
public class TestEight {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
int ch;
while ((ch = br.read()) != -1) {
System.out.println((char) ch);
}
br.close();
}
}
一次读一个字符数组
public class TestEight {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
int len;
char[] chars = new char[1204];
while ((len = br.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
}
}
字符缓冲流中特有的方法
- 针对字符缓冲输出流------
BufferedWriter
public void newLine()
:写一行行分隔符(即回车换行)。行分隔符字符串由系统属性定义(即对于windows系统、Linux系统、Mac系统来说该方法均可使用)
- 针对字符缓冲输入流------
BufferedReader
public String readLine()
:读一行文字。结果为包含一行内容的字符串,但不包含任何终止字符(如回车换行符),如果流已经到达末尾,则为null
在使用
readLine()
方法时,通常会在一个循环中反复调用,直到返回第一个null
,表示已经读取到文件末尾。
a.txt中的内容如下:
public class TestEight {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
String line1 = br.readLine();
String line2 = br.readLine();
String line3 = br.readLine();
String line4 = br.readLine();
System.out.println(line1);
System.out.println(line2);
System.out.println(line3);
System.out.println(line4);
}
}
转换流------JDK11之前使用,之后不怎么用
-
字节流转换成字符流
InputStreamReader
-
字符流转换成字节流
OutputStreamWriter
-
作用
- 用于在字节流和字符流之间进行转换。它提供了一种将字节流转换成字符流或将字符流转换成字节流的方式。
-
转换流使用场景
- 不同的IDE编码格式和文件的编码格式可能不同,此时若通过字节流将数据传入内存中可能会出现乱码问题,未避免该问题的产生则可以将传入内存的字节流文件转换成字符流文件
字节流转换成字符流—InputStreamReader
类
InputStreamReader
类构造器
构造器 | 解释 |
---|---|
public InputStreamReader(InputStream in) | 将字节流转换为字符流 |
public InputStreamReader(InputStream in, String charsetName) | 将字节流转换为字符流并指定字符编码 |
public InputStreamReader(InputStream in, Charset cs) | 将字节流转换为字符流并指定字符编码 |
public InputStreamReader(InputStream in, CharsetDecoder dec) | 将字节流转换为字符流 |
- 注意
- 参数InputStream为顶级抽象父类,所以此处可利用
Socket
类中的getInputStream()
方法来返回InputStream
对象带入该参数(详见day17练习1);也可以使用该抽象父类的子类
- 参数InputStream为顶级抽象父类,所以此处可利用
- 示例1
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class TestOne {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("fos.txt");
InputStreamReader isr = new InputStreamReader(fis);
int b;
while ((b = isr.read()) != -1) {
System.out.print((char) b);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 示例2
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class TestOne {
public static void main(String[] args) {
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream("fos.txt");
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
while (true) {
String content = br.readLine();
if (content == null) break;
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
isr.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 示例3
利用字符流读取桌面上的a.txt文件
public class TestNine {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("C:\\Users\\10195\\Desktop\\a.txt");
int ch;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}
fr.close();
}
}
由运行结果可知,我们直接进行读取时可能会出现乱码现象,原因是桌面上的a.txt文件的编码格式是ANSI,而idea默认是UTF-8,由于编码格式不一样,所以造成乱码
为解决以上问题即可使用转换流 (JDK11之前就是利用转换流就行乱码的处理) ,即指定编码格式为文件的编码格式
public class TestNine {
public static void main(String[] args) throws Exception {
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\10195\\Desktop\\a.txt"),"GBK");
int ch;
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
isr.close();
}
}
字符流转换成字节流—OutputStreamWriter
类
用于将数据从内存输入到硬盘
OutputStreamWriter
类构造器
构造器 | 解释 |
---|---|
OutputStreamWriter(OutputStream out) | 将字符流转换为字节流 |
OutputStreamWriter(OutputStream out, String charsetName) | 将字符流转换为字节流并指定字符编码 |
OutputStreamWriter(OutputStream out, Charset cs) | 将字符流转换为字节流并指定字符编码 |
OutputStreamWriter(OutputStream out, CharsetEncoder enc) | 将字符流转换为字节流 |
- 示例
import java.io.*;
public class TestOne {
public static void main(String[] args) throws Exception {
FileOutputStream fos = null;
OutputStreamWriter osw = null;
try {
fos = new FileOutputStream("test1.txt");
fos.write("Hello World 小趴菜".getBytes());
osw = new OutputStreamWriter(fos);
osw.write("你好啊");
osw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
osw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
对象操作流
- 使用场景
我们将用户名和密码存储在一个文件中,任何人都能打开看到账户和密码的话就会有风险,由此诞生了对象操作流
- 特点
可以将对象以字节的形式写到本地文件中(即将对象变为二进制数据存到本地文件中),此时若直接打开文件则是读不懂的,若想读取文本内容则需要再次用对象操作流将其读到内存中。
- 分类
对象操作流分为: 对象操作输入流和对象操作输出流
对象操作输入流(对象反序列化流):即将本地文件中的对象读到内存,或者接收网络中传输的对象
对象操作输出流(对象序列化流):将对象写到本地文件中,或者在网络中传输对象
对象操作输出流(对象序列化流)------ObjectOutputStream
其父类为OutputStream
类
- 构造器
变量 | 构造器 | 解释 |
---|---|---|
protected | ObjectOutputStream() | 为完全重新实现ObjectOutputStream 的子类提供一种方法,以便不必分配ObjectOutputStream 的此实现刚刚使用的私有数据。 (即该构造器是给ObjectOutputStream 子类使用的) |
| ObjectOutputStream(OutputStream out) | 创建一个可以写入指定字节输出流(OutputStream )的对象操作输出流(ObjectOutputStream ) |
注意:
第一个构造器的访问修饰符为protected,即代表本类、本包以及其他包子类可以使用该构造器;而第二个构造器的访问修饰符为缺省,代表只有本类、本包可以使用该构造器
- 使用的方法
方法 | 解释 |
---|---|
public void writeObject(Object obj) | 用于将指定的对象序列化,并将序列化后的字节流写入到该对象输出流所关联的目标流中。obj :要序列化并写入输出流的对象。 |
注意:
(1)即该方法的意思是参数必须为可序列化对象 (即实现了
Serializable
接口的类的对象,该接口是一个标记接口,不包含任何方法,仅用于标识类的对象可以被序列化)(2)实现
Serializable
接口的类中的所有字段也必须是可序列化的,若一个类的字段是对象类型则该对象类型也必须是可序列化的(3)实现
Serializable
接口的类中若包含其他类的引用作为其字段则这些类也必须是可序列化的(4)如果在调用
writeObject()
方法时传递了一个未实现Serializable
接口的对象,或者其中的某个字段是不可序列化的,则会抛出NotSerializableException
异常,程序会在此处终止,并且不会将对象写入到输出流中。因此,在使用ObjectOutputStream
的writeObject()
方法时,确保要写入的对象及其所有引用的对象都是可序列化的,以避免异常发生。
- 代码实现
User类
//若想要这个类的对象可以被序列化则该类必须实现Serializable接口
public class User implements Serializable {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
实现类
public class TestNine {
public static void main(String[] args) throws Exception {
User user = new User("张三", "12qwe");
FileOutputStream fos = new FileOutputStream("a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.close();
}
}
此时写入文件中的内容均会以字节的形式保存,如图所示
对象操作输入流(对象反序列化流)------ObjectInputStream
其父类为InputStream
类
- 构造器
变量 | 构造器 | 解释 |
---|---|---|
protected | ObjectInputStream() | 为完全重新实现ObjectInputStream 的子类提供一种方法,以便不必分配此ObjectInputStream 实现刚刚使用的私有数据。 (即该构造器是给ObjectIntputStream 子类使用的) |
ObjectInputStream(InputStream in) | 创建一个从指定的InputStream 读取的ObjectInputStream 。 |
- 使用方法
方法 | 解释 |
---|---|
public Object readObject() | 从ObjectInputStream 中读取一个对象。 |
- 代码实现
将以字节的形式保存的a.txt文件读入内存变为能读懂的数据信息
public class TestNine {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
User o = (User) ois.readObject();
System.out.println(o);
ois.close();
}
}
注意:利用对象输入流进行文件读取时,若读到文件末尾则会报EOFException
异常,所以此时进行文件末尾循环判断时就有所更改,如下代码所示:
public class TestNine {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
Object o;
while (true) {
try {
o = ois.readObject();
System.out.println(o);
} catch (IOException e) {
System.out.println("已读到文件末尾,跳出循环");
break;
}
}
}
}
特殊注意点
JavaBean 类必须满足以下几个条件:
- 必须具有公共的无参构造方法。
- 属性必须是私有的,并且通过公共的 getter 和 setter 方法来访问。
- 属性名必须遵循驼峰命名规则。
- 应该实现
Serializable
接口,以便支持序列化。
- 用对象序列化流序列化了一个对象后,此时若修改了对象所属的JavaBean类则此时读取数据会不会出现问题?
针对这个问题我们可以先写对象(即序列化),然后修改javabean类,最后进行读对象(即反序列化),以此来观察是否会出现问题。
User类为javabean类
//若想要这个类的对象可以被序列化则该类必须实现Serializable接口
public class User implements Serializable {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
写对象(序列化)
public class TestNine {
public static void main(String[] args) throws Exception {
User user = new User("张三", "12qwe");
FileOutputStream fos = new FileOutputStream("a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.close();
}
}
修改javabean类
/*
将User类中的private String password改为public String password
*/
读对象(反序列化)
public class TestNine {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
User o = (User) ois.readObject();
System.out.println(o);
ois.close();
}
}
运行截图如下:
由运行截图可知,报错异常为InvalidClassException
,此时我们可以通过查API来判断是什么原因导致的该异常
Serialization运行时检测到类的以下问题之一时抛出:
该类的串行版本与从流中读取的类描述符的版本不匹配 (即流的序列号和本地类的序列号不一样导致的异常信息)(
serialVersionUID------序列号
) 该类包含未知的数据类型
该类没有可访问的no-arg构造函数 (即无参构造函数)
由分析知属于第一种情况引起的异常
注意:
若我们没有自己定义序列号时,虚拟机会根据类中的信息自动计算出一个序列号;若我们修改了类中的信息则虚拟机会再次计算出一个序列号,此时两序列号不一致则会报异常
- 序列化、反序列化过程解析
Step1:把User对象序列化到本地------生成本地类序列号的同时会将该序列号写到文件中去
Step2:修改javabean类------重新生成本地类序列号
Step3:把文件中的对象读到内存------此时本地中的序列号和修改后类中的序列号不一致引起异常
如何解决该问题------在修改javabean类后手动更新 serialVersionUID
序列号,此时不论你怎么修改javabean类都不会改变类的序列号,在进行读操作时也就不会报错如下代码所示
/*
在User类中加一个静态常量long属性
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
- 若一个对象中的某个成员不想被序列化时该怎么实现
假设User类中属性password
不序列化,则可利用关键字transient
,将其放在属性的特殊修饰区即可(该关键字标记的成员变量不参与序列化过程),如下所示
private String password;//改为private transient String password即可
注意:
此时由于字段password未被序列化,则在对象序列化过程中该字段的内容不会被包含在序列化的字节流中,该字段的内容不会被保存到文件中
练习
创建多个javabean类对象写到文件中并再次读取到内存中
- javabean类------Student类
public class Student implements Serializable {
private static final long serialVersionUID = 12L;
private String name;
private int age;
public Student (String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "name = " + name + ",age = " + age;
}
}
- 序列化及反序列化
public class TestStudent {
public static void main(String[] args) throws Exception {
methodOne();
methodTwo();
}
private static void methodOne() throws IOException {
Student stu1 = new Student("张三", 15);
Student stu2 = new Student("李四", 16);
Student stu3 = new Student("王五", 16);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(stu1);
oos.writeObject(stu2);
oos.writeObject(stu3);
oos.close();
}
private static void methodTwo() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
Object o;
while (true) {
try {
o = ois.readObject();
System.out.println(o);
} catch (IOException e) {
System.out.println("已读到文件末尾,跳出循环");
break;
}
}
ois.close();
}
}
在以上反序列化的过程中是利用while循环进行文件末尾判断,也可利用容器(即集合)进行处理,此时不需要进行循环判断是否达到末尾,而是有几个容器就读几个容器,代码如下:
public class TestStudent {
public static void main(String[] args) throws Exception {
methodOne();
methodTwo();
}
//序列化
private static void methodOne() throws IOException {
Student stu1 = new Student("张三", 15);
Student stu2 = new Student("李四", 16);
Student stu3 = new Student("王五", 16);
ArrayList<Student> list = new ArrayList<>();
list.add(stu1);
list.add(stu2);
list.add(stu3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(list);//往本地文件中写入集合
oos.close();
}
//反序列化
private static void methodTwo() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
ArrayList<Student> list2 = (ArrayList<Student>) ois.readObject();//此时读出的是一个集合
for (Student student : list2) {
System.out.println(student);
}
}
}
Properties
- 简要解释
- 由API可看出
Properties
类是Hashtable
的子类,且其以实现的接口有三个,其子类有一个。 - 在
Properties
类的源码中并未出现其实现了以上三个接口的原因:以上三个接口是其父类实现的,由于Properties
类继承了Hashtable
类,所以也相当于是其实现了以上三个接口。 - 由于
Properties
类是Hashtable
集合的子类,所以Properties
是一个集合(即是一个Map体系的集合类) - 由于
Properties
类后面是没有泛型的,所以在创建Properties
对象时后面不需要有尖括号,此时表示可以在Properties
类中添加任意数据类型,但是实际使用时一般只会添加字符串
- 构造方法
构造方法 | 解释 |
---|---|
public Properties() | 创建一个没有默认值的空属性列表。 |
public Properties(Properties defaults) | 创建具有指定默认值的空属性列表。 |
public Properties(int initialCapacity) | 创建一个没有默认值的空属性列表,并且初始大小容纳指定数量的元素,而无需动态调整大小。 |
- 注意
JDK1.8中只有前两种构造方法,JDK11开始有了第三个构造方法,前两个构造器在JDK1.8和11中的代码如下:
JDK21版本三个构造方法源码如下:
由不同版本的可看出在JDK1.8时前两个构造方法在创建Properties类对象时不会设置一个初始容量大小,而在JDK21中则会设置初始容量大小,在现在的学习中目前只能用到第一种构造方法来创建对象,后续学习会继续补充
由于Properties
属于集合,所以基本操作增删改查遍历是必要的,又因为其属于HashMap
的子集,所以增删改查以及遍历所用到的方法是其父类HashMap
集合中的方法,相关代码如下:
public class TestOne {
public static void main(String[] args) {
Properties prop = new Properties();
//增
prop.put("张三", "12");
prop.put("李四", "13");
prop.put("王五", "114");
System.out.println(prop);
System.out.println("----------------------------");
//删
prop.remove("张三");
System.out.println(prop);
System.out.println("----------------------------");
//改---利用put方法:若键不存在则添加;若键存在则覆盖
prop.put("李四", "20");
System.out.println(prop);
System.out.println("----------------------------");
//查
//方式一
System.out.println(prop.get("李四"));//由键获取对应的值
//遍历
System.out.println("------遍历方式一如下:------");
//方式一:获取所有的键,通过遍历所有的键来获取对应的键值
Set<Object> keys = prop.keySet();//获取所有的键
for (Object key : keys) {//遍历所有的键对应的值
System.out.println(prop.get(key));
}
//方式二:获取所有的键值对
System.out.println("------遍历方式二如下:------");
Set<Map.Entry<Object, Object>> entries = prop.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.println("键为:" + entry.getKey() + ",对应值为:" + entry.getValue());
}
}
}
Properties集合特有的方法
一般方法
方法 | 解释 |
---|---|
public Object setProperty(String key, String value) | 设置集合的键和值,底层调用的是Hashtable 的put 方法(注意:Hashtable 的put 方法虽然没有调用Map 接口中的put 方法,但是它的功能与Map 接口中的put 方法的功能一致) |
public String getProperty(String key) | 使用此属性列表中指定的键搜索属性(即通过键获取值) |
public Set<String> stringPropertyNames() | 从该属性列表中返回一组不可修改的键集,其中键及其对应的值是字符串,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。 |
public class TestTwo {
public static void main(String[] args) {
Properties prop = new Properties();
//setProperty()方法------底层调用的是Hashtable的put方法
prop.put("张三", "15");
System.out.println(prop);
prop.setProperty("张三", "16");
System.out.println(prop);
prop.setProperty("李四", "18");
System.out.println(prop);
System.out.println("---------------------------");
//getProperty()方法
String key = prop.getProperty("张三");
System.out.println(key);
System.out.println("---------------------------");
//StringPropertyNames()方法
Set<String> keys = prop.stringPropertyNames();
for (String s : keys) {
String val = prop.getProperty(s);
System.out.println(val);
}
}
}
和IO流结合的方法
方法 | 解释 |
---|---|
public void load(InputStream inStream) | 从输入字节流中读取属性列表(键和元素对) |
public void load(Reader reader) | 以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 (即将本地文件中的键值对数据读取到集合中) |
public void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream) 方法的格式写入输出字节流 |
public void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(Reader) 方法的格式写入输出字符流。(即将集合中的数据以键值对形式保存在本地) |
注意:
- Properties类所读取的本地文件后缀名需要以
.properties
结尾,因为它会被当做配置文件。在写properties配置文件时注意不要加空格以及标点符号,因为系统在将键值对数据读取到集合中时会自动将多余的符号给添加进去- 在使用
load(Reader)
方法时不要使用匿名的Reader对象,因为我们最后需要利用close
方法进行资源释放- 在使用
load(Reader)
方法所读取到集合中的键值对数据是无序的,因为Properties
类是Hashtable
的子类,而Hashtable
是Map
接口的实现类,Map接口是无序的。comments
:是一个字符串,表示要写入到属性文件开头的注释。可以是任意内容,通常用于标识文件的用途或其他说明信息。若无注释该参数可以为null
实现如下:
创建一个properties文件,并写出键值对,如下图所示
读取代码如下:
public class TestThree {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
//读取
FileReader fr = new FileReader("prop.properties");
prop.load(fr);//调用完load方法后,文件中的键值对数据就已经在集合中
fr.close();
System.out.println(prop);//可打印,Properties集合是无序的
}
}
写入代码如下:
public class TestThree {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
prop.put("张三", "132");
prop.put("李四", "456");
FileWriter fw = new FileWriter("prop.properties", false);//false代表不追加------即覆盖原有的配置文件内容
prop.store(fw, null);
fw.close();
}
}