File类是java.io包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。
在API中File的解释是文件和目录路径名的抽象表示形式,即File类是文件或目录的路径,而不是文件本身,因此File类不能直接访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。public File(String parent, String child)
:从父路径名字符串和子路径名字符串创建新的 File实例。public File(File parent, String child)
:从父抽象路径名和子路径名字符串创建新的 File实例。import java.io.File;public class FileObjectTest {public static void main(String[] args) {// 文件路径名String pathname = "D:\";File file1 = new File(pathname);// 文件路径名String pathname2 = "D:\aaa\";File file2 = new File(pathname2);// 通过父路径和子路径字符串String parent = "d:\aaa";String child = ";File file3 = new File(parent, child);// 通过父级File对象和子路径字符串File parentDir = new File("d:\aaa");String childFile = ";File file4 = new File(parentDir, childFile);}
}
小贴士:
- 一个File对象代表硬盘或网络中可能存在的一个文件或者目录。
- 无论该路径下是否存在文件或者目录,都不影响File对象的创建。
- 如果File对象代表的文件或目录存在,则File对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File对象的属性赋值,否则除了路径和名称,File对象的其他属性将会保留默认值。
public String getName()
:返回由此File表示的文件或目录的名称。
public long length()
:返回由此File表示的文件的长度。
public String getPath()
:将此File转换为路径名字符串。
public long lastModified()
:返回File对象对应的文件或目录的最后修改时间(毫秒值)
方法演示,代码如下:
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;public class FileInfoMethod {public static void main(String[] args) {File f = new File("d:/");System.out.println("文件构造路径:"Path());System.out.println("文件名称:"Name());System.out.println("文件长度:"+f.length()+"字节");System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai")));File f2 = new File("d:/aaa");System.out.println("目录构造路径:"Path());System.out.println("目录名称:"Name());System.out.println("目录长度:"+f2.length()+"字节");System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai")));}
}输出结果:
文件构造路径:d:aaabbb.java
文件名称:bbb.java
文件长度:636字节
文件最后修改时间:2019-07-23T22:01:32.065目录构造路径:d:aaa
目录名称:aaa
目录长度:4096字节
文件最后修改时间:2019-07-23T22:01:32.065
API中说明:length(),表示文件的长度。如果File对象表示目录,则返回值未指定。
public String getPath()
:将此File转换为路径名字符串。public String getAbsolutePath()
:返回此File的绝对路径名字符串。String getCanonicalPath()
:返回此File对象所对应的规范路径名。File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。
默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。
import org.junit.Test;import java.io.File;
import java.io.IOException;public class FilePath {@Testpublic void test1() throws IOException{File f1 = new File("d:\atguigu\javase\HelloIO.java"); //绝对路径System.out.println("文件/目录的名称:" + f1.getName());System.out.println("文件/目录的构造路径名:" + f1.getPath());System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());System.out.println("文件/目录的规范路径名:" + f1.getCanonicalPath());System.out.println("文件/目录的父目录名:" + f1.getParent());}@Testpublic void test02()throws IOException{File f2 = new File("/HelloIO.java");//绝对路径,从根路径开始System.out.println("文件/目录的名称:" + f2.getName());System.out.println("文件/目录的构造路径名:" + f2.getPath());System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());System.out.println("文件/目录的父目录名:" + f2.getParent());}@Testpublic void test03() throws IOException {File f3 = new File("HelloIO.java");//相对路径System.out.println("user.dir =" + Property("user.dir"));System.out.println("文件/目录的名称:" + f3.getName());System.out.println("文件/目录的构造路径名:" + f3.getPath());System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());System.out.println("文件/目录的规范路径名:" + f3.getCanonicalPath());System.out.println("文件/目录的父目录名:" + f3.getParent());}@Testpublic void test04() throws IOException{File f4 = new File("../../HelloIO.java");//相对路径System.out.println("user.dir =" + Property("user.dir"));System.out.println("文件/目录的名称:" + f4.getName());System.out.println("文件/目录的构造路径名:" + f4.getPath());System.out.println("文件/目录的绝对路径名:" + f4.getAbsolutePath());System.out.println("文件/目录的规范路径名:" + f4.getCanonicalPath());System.out.println("文件/目录的父目录名:" + f4.getParent());}public static void main(String[] args)throws IOException {File f5 = new File("HelloIO.java");//相对路径System.out.println("user.dir =" + Property("user.dir"));System.out.println("文件/目录的名称:" + f5.getName());System.out.println("文件/目录的构造路径名:" + f5.getPath());System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath());System.out.println("文件/目录的规范路径名:" + f5.getCanonicalPath());System.out.println("文件/目录的父目录名:" + f5.getParent());}
}
public boolean exists()
:此File表示的文件或目录是否实际存在。public boolean isDirectory()
:此File表示的是否为目录。public boolean isFile()
:此File表示的是否为文件。方法演示,代码如下:
import java.io.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 是否存在:"ists());System.out.println("d:\aaa 是否存在:"ists());// 判断是文件还是目录System.out.println("d:\aaa 文件?:"+f2.isFile());System.out.println("d:\aaa 目录?:"+f2.isDirectory());}
}输出结果:
d:aaabbb.java 是否存在:true
d:aaa 是否存在:true
d:aaa 文件?:false
d:aaa 目录?:true
如果文件或目录不存在,那么exists()、isFile()和isDirectory()都是返回true
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。public boolean delete()
:删除由此File表示的文件或目录。 只能删除空目录。public boolean mkdir()
:创建由此File表示的目录。public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。方法演示,代码如下:
import java.io.File;
import java.io.IOException;public class FileCreateDelete {public static void main(String[] args) throws IOException {// 文件的创建File f = new File(");System.out.println(是否存在:"ists()); System.out.println(是否创建:"ateNewFile()); System.out.println(是否存在:"ists()); // 目录的创建File f2= new File("newDir");System.out.println("newDir是否存在:"ists());System.out.println("newDir是否创建:"+f2.mkdir());System.out.println("newDir是否存在:"ists());// 创建一级目录File f3= new File("newDira\newDirb");System.out.println("newDira\newDirb创建:" + f3.mkdir());File f4= new File("newDir\newDirb");System.out.println("newDir\newDirb创建:" + f4.mkdir());// 创建多级目录File f5= new File("newDira\newDirb");System.out.println("newDira\newDirb创建:" + f5.mkdirs());// 文件的删除System.out.println(删除:" + f.delete());// 目录的删除System.out.println("newDir删除:" + f2.delete());System.out.println("newDir\newDirb删除:" + f4.delete());}
}运行结果:
是否存在:false
是否创建:true
是否存在:true
newDir是否存在:false
newDir是否创建:true
newDir是否存在:true
newDiranewDirb创建:false
newDirnewDirb创建:true
newDiranewDirb创建:true
删除:true
newDir删除:false
newDirnewDirb删除:true
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。public File[] listFiles(FileFilter filter)
:返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(File pathname)方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。public String[] list(FilenameFilter filter)
:返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。public File[] listFiles(FilenameFilter filter)
:返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。import org.junit.Test;import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;public class DirListFiles {@Testpublic void test01() {File dir = new File("d:/atguigu");String[] subs = dir.list();for (String sub : subs) {System.out.println(sub);}}@Testpublic void test02() {File dir = new File("d:/atguigu");listSubFiles(dir);}public void listSubFiles(File dir) {if (dir != null && dir.isDirectory()) {File[] listFiles = dir.listFiles();if (listFiles != null) {for (File sub : listFiles) {listSubFiles(sub);//递归调用}}}System.out.println(dir);}
小结:File类是对磁盘文件进行一个功能使用比如增删改查,判断是否存在文件,生成该文件,查看该文件大小,与数据库的操作原理一致。值得的注意的是File类带给我们的是一个磁盘操作,不似之前一样,只能讨论内存。
生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s
,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input
和输出output
,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
根据数据的流向分为:输入流和输出流。
其他设备
上读取到内存
中的流。 内存
中写出到其他设备
上的流。 根据数据的类型分为:字节流和字符流。
根据IO流的角色不同分为:节点流和处理流。
这种设计是装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),其使用一种对客户端透明的方式来动态地扩展对象的功能,它是通过继承扩展功能的替代方案之一。在现实生活中你也有很多装饰者的例子,例如:人需要各种各样的衣着,不管你穿着怎样,但是,对于你个人本质来说是不变的,充其量只是在外面加上了一些装饰,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”…
常用的节点流:
常用处理流:
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int b)
:将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。小贴士:close方法,当完成流的操作时,必须调用此方法,释放系统资源。
OutputStream
有很多子类,我们从最简单的一个子类开始。java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。当你创建一个流对象时,必须传入一个文件路径。如果该文件不存在,会创建该文件。如果有这个文件,会清空这个文件的数据。如果传入的是一个目录,则会报IOException异常。
import org.junit.Test;import java.io.FileOutputStream;
import java.io.IOException;public class FOSWrite {@Testpublic void test01() throws IOException {// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream(");// 写出数据fos.write(97); // 写出第1个字节fos.write(98); // 写出第2个字节fos.write(99); // 写出第3个字节// 关闭资源fos.close();/* 输出结果:abc*/}@Testpublic void test02()throws IOException {// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream(");// 字符串转换为字节数组byte[] b = "尚硅谷".getBytes();// 写出字节数组数据fos.write(b);// 关闭资源fos.close();}@Testpublic void test03()throws IOException {// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream(");// 字符串转换为字节数组byte[] b = "abcde".getBytes();// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。fos.write(b,2,2);// 关闭资源fos.close();}
}
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
public FileOutputStream(File file, boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name, boolean append)
: 创建文件输出流以指定的名称写入文件。这两个构造方法,参数中都需要传入一个boolean类型的值,true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
import java.io.FileOutputStream;
import java.io.IOException;public class FOSWriteAppend {public static void main(String[] args) throws IOException {// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream(",true);// 字符串转换为字节数组byte[] b = "abcde".getBytes();fos.write(b);// 关闭资源fos.close();}
}
//这段程序如果多运行几次,每次都会在原来文件末尾追加abcde
r
和换行符n
: 回车+换行
,即rn
;换行
,即n
;回车
,即r
。从 Mac OS X开始与Linux统一。代码使用演示:
import java.io.FileOutputStream;
import java.io.IOException;public class FOSWriteNewLine {public static void main(String[] args) throws IOException {// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream(");// 定义字节数组byte[] words = {97,98,99,100,101};// 遍历数组for (int i = 0; i < words.length; i++) {// 写出一个字节fos.write(words[i]);// 写出一个换行, 换行符号转成数组写出fos.write("rn".getBytes());}// 关闭资源fos.close();}
}/*
输出结果:abcde*/
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b,int off,int len)
:从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。小贴士:close方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。当你创建一个流对象时,必须传入一个文件路径。如果文件不存在,会抛出FileNotFoundException
。如果传入的是一个目录,则会报IOException异常。
import org.junit.Test;import java.io.FileInputStream;
import java.io.IOException;public class FISRead {@Testpublic void test() throws IOException {// 使用文件名称创建流对象FileInputStream fis = new FileInputStream(");// 读取数据,返回一个字节int read = ad();System.out.println((char) read);read = ad();System.out.println((char) read);read = ad();System.out.println((char) read);read = ad();System.out.println((char) read);read = ad();System.out.println((char) read);// 读取到末尾,返回-1read = ad();System.out.println( read);// 关闭资源fis.close();/*文件内容:abcde输出结果:abcde-1*/}@Testpublic void test02()throws IOException{// 使用文件名称创建流对象FileInputStream fis = new FileInputStream(");// 定义变量,保存数据int b;// 循环读取while ((b = ad())!=-1) {System.out.println((char)b);}// 关闭资源fis.close();}@Testpublic void test03()throws IOException{// 使用文件名称创建流对象.FileInputStream fis = new FileInputStream("); // 文件中为abcde// 定义变量,作为有效个数int len;// 定义字节数组,作为装字节数据的容器byte[] b = new byte[2];// 循环读取while (( len= ad(b))!=-1) {// 每次读取后,把数组变成字符串打印System.out.println(new String(b));}// 关闭资源fis.close();/*输出结果:abcded最后错误数据`d`,是由于最后一次读取时,只读取一个字节`e`,数组中,上次读取的数据没有被完全替换,所以要通过`len` ,获取有效的字节*/}@Testpublic void test04()throws IOException{// 使用文件名称创建流对象.FileInputStream fis = new FileInputStream("); // 文件中为abcde// 定义变量,作为有效个数int len;// 定义字节数组,作为装字节数据的容器byte[] b = new byte[2];// 循环读取while (( len= ad(b))!=-1) {// 每次读取后,把数组的有效字节部分,变成字符串打印System.out.println(new String(b,0,len));// len 每次读取的有效字节个数}// 关闭资源fis.close();/*输出结果:abcde*/}
}
复制图片文件,代码使用演示:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class FileCopy {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 = ad(b))!=-1) {// 2.4 写出数据fos.write(b, 0 , len);}// 3.关闭资源fos.close();fis.close();}
}
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流。
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此流并释放与此流相关联的任何系统资源。小贴士:close方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。如果该文件不存在,则报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。
import org.junit.Test;import java.io.FileReader;
import java.io.IOException;public class FRRead {@Testpublic void test01() throws IOException {// 使用文件名称创建流对象FileReader fr = new FileReader(");// 定义变量,保存数据int b;// 循环读取while ((b = fr.read())!=-1) {System.out.println((char)b);}// 关闭资源fr.close();
/*输出结果:火柴人*/}@Testpublic void test02()throws IOException {// 使用文件名称创建流对象FileReader fr = new FileReader(");// 定义变量,保存有效字符个数int len;// 定义字符数组,作为装字符数据的容器char[] cbuf = new char[2];// 循环读取while ((len = fr.read(cbuf))!=-1) {System.out.println(new String(cbuf));}// 关闭资源fr.close();/*输出结果:火柴火柴最后错误数据人,是因为最后一次流中只有一个字符“人”,读取一个字符没有覆盖char[]数组cbuf的所有元素*/}@Testpublic void test03() throws IOException {// 使用文件名称创建流对象FileReader fr = new FileReader(");// 定义变量,保存有效字符个数int len;// 定义字符数组,作为装字符数据的容器char[] cbuf = new char[2];// 循环读取while ((len = fr.read(cbuf)) != -1) {System.out.println(new String(cbuf, 0, len));}// 关闭资源fr.close();/*输出结果:火柴人*/}}
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int c)
写入单个字符。public void write(char[] cbuf)
写入字符数组。public void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。public void write(String str)
写入字符串。public void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。public void flush()
刷新该流的缓冲。public void close()
关闭此流,但要先刷新它。java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。如果文件不存在,则会自动创建。如果文件已经存在,则会清空文件内容,写入新的内容。
import org.junit.Test;import java.io.FileWriter;
import java.io.IOException;public class FWWrite {@Testpublic void test01()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter(");// 写出数据fw.write(97); // 写出第1个字符fw.write('b'); // 写出第2个字符fw.write('C'); // 写出第3个字符fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。/*【注意】FileWriter与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并未保存到文件。*/// fw.close();}@Testpublic void test02()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter(");// 字符串转换为字节数组char[] chars = "火柴人".toCharArray();// 写出字符数组fw.write(chars); // 火柴人// 写出从索引1开始,2个字符。fw.write(chars,1,2); // 火柴// 关闭资源fw.close();}@Testpublic void test03()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter(");// 字符串String msg = "火柴人";// 写出字符数组fw.write(msg); //火柴人// 写出从索引1开始,2个字符。fw.write(msg,1,2); // 柴人// 关闭资源fw.close();}
}
public FileWriter(File file,boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。public FileWriter(String fileName,boolean append)
: 创建文件输出流以指定的名称写入文件。这两个构造方法,参数中都需要传入一个boolean类型的值,true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
操作类似于FileOutputStream。
import org.junit.Test;import java.io.FileWriter;
import java.io.IOException;public class FWWriteAppend {@Testpublic void test01()throws IOException {// 使用文件名称创建流对象,可以续写数据FileWriter fw = new FileWriter(",true);// 写出字符串fw.write("火柴人大战");// 关闭资源fw.close();}
}
import java.io.FileWriter;
import java.io.IOException;public class FWWriteNewLine {public static void main(String[] args) throws IOException {// 使用文件名称创建流对象,可以续写数据FileWriter fw = new FileWriter(");// 写出字符串fw.write("尚");// 写出换行fw.write("rn");// 写出字符串fw.write("硅谷");// 关闭资源fw.close();}
}
【注意】FileWriter与FileOutputStream不同。因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。代码使用演示:
import java.io.FileWriter;
import java.io.IOException;public class FWWriteFlush {public static void main(String[] args)throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter(");// 写出数据,通过flushfw.write('刷'); // 写出第1个字符fw.flush();fw.write('新'); // 继续写出第2个字符,写出成功fw.flush();// 写出数据,通过closefw.write('关'); // 写出第1个字符fw.close();fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closedfw.close();}
}
小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
缓冲流,也叫高效流,按照数据类型分类:
BufferedInputStream
,BufferedOutputStream
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。构造举例,代码如下:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("));
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。构造举例,代码如下:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("));
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
public String readLine()
: 读一行文字。public void newLine()
: 写一行行分隔符,由系统属性定义符号。import org.junit.Test;import java.io.*;public class BufferedIOLine {@Testpublic void testReadLine()throws IOException {// 创建流对象BufferedReader br = new BufferedReader(new FileReader("));// 定义字符串,保存读取的一行文字String line;// 循环读取,读取到最后返回nullwhile ((line = br.readLine())!=null) {System.out.println(line);}// 释放资源br.close();}@Testpublic void testNewLine()throws IOException{// 创建流对象BufferedWriter bw = new BufferedWriter(new FileWriter("));// 写出数据bw.write("火");// 写出换行bw.newLine();bw.write("柴");bw.newLine();bw.write("人");bw.newLine();// 释放资源bw.close();}
}
import org.junit.Test;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;public class IOClose {@Testpublic void test01() throws IOException {FileWriter fw = new FileWriter("d:/1.txt");BufferedWriter bw = new BufferedWriter(fw);bw.write("hello");fw.close();bw.close();//java.io.IOException: Stream closed/*缓冲流BufferedWriter,把数据先写到缓冲区,默认情况下是当缓冲区满,或调用close,或调用flush这些情况才会把缓冲区的数据输出。bw.close()时,需要把数据从缓冲区的数据输出。数据的流向: 写到bw(缓冲区)-->fw ->"d:/1.txt"此时,我先把fw关闭了,bw的数据无法输出了*/}@Testpublic void test02() throws IOException {FileWriter fw = new FileWriter("d:/1.txt");BufferedWriter bw = new BufferedWriter(fw);bw.write("hello");bw.close();fw.close();/*原则:先关外面的,再关里面的。例如:FileWriter fw = new FileWriter("d:/1.txt"); //里面 穿内衣BufferedWriter bw = new BufferedWriter(fw); //外面 穿外套关闭bw.close(); //先关外面的 先脱外套fw.close(); //再关里面的 再脱内衣*/}
}
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。构造举例,代码如下:
InputStreamReader isr = new InputStreamReader(new FileInputStream("));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(") , "GBK");
示例代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;public class InputStreamReaderDemo {public static void main(String[] args) throws IOException {// 定义文件路径,文件为gbk编码String fileName = "E:\";// 创建流对象,默认UTF8编码InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));// 定义变量,保存字符int charData;// 使用默认编码字符流读取,乱码while ((charData = ad()) != -1) {System.out.print((char)charData); // ��Һ�}isr1.close();// 创建流对象,指定GBK编码InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName) , "GBK");// 使用指定编码字符流读取,正常解析while ((charData = ad()) != -1) {System.out.print((char)charData);// 大家好}isr2.close();}
}
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。构造举例,代码如下:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream(") , "GBK");
示例代码:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;public class OutputStreamWriterDemo {public static void main(String[] args) throws IOException {// 定义文件路径String FileName = "E:\";// 创建流对象,默认UTF8编码OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));// 写出数据osw.write("你好"); // 保存为6个字节osw.close();// 定义文件路径String FileName2 = "E:\";// 创建流对象,指定GBK编码OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");// 写出数据osw2.write("你好");// 保存为4个字节osw2.close();}
}
转换流是字节与字符间的桥梁!
前面学习的IO流,在程序代码中,要么将数据直接按照字节处理,要么按照字符处理。那么,如果读写Java其他数据类型的数据,怎么办呢?
String name = “巫师”;
int age = 300;
char gender = ‘男’;
int energy = 5000;
double price = 75.5;
boolean relive = true;Student stu = new Student("张三",23,89);
Java提供了数据流和对象流来处理这些类型的数据:
因为DataOutputStream和DataInputStream只支持Java基本数据类型和字符串的读写,而不支持Java对象的对象。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以下面直接介绍对象流ObjectOutputStream和ObjectInputStream即可。
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。构造举例,代码如下:
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ObjectOutpuStream也从OutputStream父类中继承基本方法:
public void write(int b)
:将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。还支持将各种Java数据类型的数据写入输出流中:
ObjectInputStream除了从InputStream父类中继承基本方法之外,
public int read()
: 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b,int off,int len)
:从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。还支持从输入流中读取各种Java数据类型的数据:
注意:读的顺序和方法与写的顺序和方法要一一对应。
示例代码:
import org.junit.Test;import java.io.*;public class ReadWriteDataOfAnyType {@Testpublic void save() throws IOException {String name = "巫师";int age = 300;char gender = '男';int energy = 5000;double price = 75.5;boolean relive = true;ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));oos.writeUTF(name);oos.writeInt(age);oos.writeChar(gender);oos.writeInt(energy);oos.writeDouble(price);oos.writeBoolean(relive);oos.close();}@Testpublic void reload()throws IOException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));String name = adUTF();int age = adInt();char gender = adChar();int energy = adInt();double price = adDouble();boolean relive = adBoolean();System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);ois.close();}
}
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
ObjectOutputStream流中支持序列化的方法是:
public final void writeObject (Object obj)
: 将指定的对象写出。ObjectInputStream流中支持反序列化的方法是:
public final Object readObject ()
: 读取一个对象。某个类的对象需要序列化输出时,该类必须实现java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
Serializable
接口transient
关键字修饰。import java.io.Serializable;public class Employee implements Serializable {public static String company; //static修饰的类变量,不会被序列化public String name;public String address;public transient int age; // transient瞬态修饰成员,不会被序列化public Employee(String name, String address, int age) {this.name = name;this.address = address;this.age = age;}public static String getCompany() {return company;}public static void setCompany(String company) {Employeepany = company;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + ''' +", address='" + address + ''' +", age=" + age +", company=" + company +'}';}
}
import org.junit.Test;import java.io.*;public class ReadWriteObject {@Testpublic void save() throws IOException {Employee.setCompany("火柴人");Employee e = new Employee("张三", "汤臣一品", 23);// 创建序列化流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));// 写出对象oos.writeObject(e);// 释放资源oos.close();System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。}@Testpublic void reload() throws IOException, ClassNotFoundException {// 创建反序列化流FileInputStream fis = new FileInputStream("employee.dat");ObjectInputStream ois = new ObjectInputStream(fis);// 读取一个对象Employee e = (Employee) adObject();// 释放资源ois.close();fis.close();System.out.println(e);}@Testpublic void writeString()throws IOException{ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("str1.dat"));oos1.writeUTF("atguigu");oos1.writeInt(1);oos1.close();ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("str2.dat"));oos2.writeObject("atguigu");oos2.writeObject(1);oos2.close();}
}
首先,对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
其次,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。如果没有声明serialVersionUID,则每次编译都会产生新的serialVersionUID序列化版本ID值,这样如果在序列化完成之后修改了类导致类重新编译,则原来的数据将无法反序列化。所以通常我们都会在实现Serializable接口时,声明一个serialVersionUID,并为其指定一个值。serialVersionUID必须是static和final修饰的long类型的数据,它的值由程序员随意指定即可。
如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
import java.io.Serializable;public class Employee implements Serializable {private static final long serialVersionUID = 1L; //增加serialVersionUIDpublic static String company; //static修饰的类变量,不会被序列化public String name;public String address;public transient int age; // transient瞬态修饰成员,不会被序列化public Employee(String name, String address, int age) {this.name = name;this.address = address;this.age = age;}public static String getCompany() {return company;}public static void setCompany(String company) {Employeepany = company;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + ''' +", address='" + address + ''' +", age=" + age +", company=" + company +'}';}
}
如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。
package com.atguigu.object;import org.junit.Test;import java.io.*;
import java.util.ArrayList;public class ReadWriteCollection {@Testpublic void save() throws IOException {ArrayList<Employee> list = new ArrayList<>();list.add(new Employee("张三", "汤臣一品", 23));list.add(new Employee("李四", "汤臣二品", 24));list.add(new Employee("王五", "汤臣三品", 25));// 创建序列化流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));// 写出对象oos.writeObject(list);// 释放资源oos.close();}@Testpublic void reload() throws IOException, ClassNotFoundException {// 创建反序列化流FileInputStream fis = new FileInputStream("employees.dat");ObjectInputStream ois = new ObjectInputStream(fis);// 读取一个对象ArrayList<Employee> list = (ArrayList<Employee>) adObject();// 释放资源ois.close();fis.close();System.out.println(list);}
}
小结:IO流主体分为字节与字符,字符最后也会转换为字节进行最后的转换。输入流:将内容输入,输出流:将内容输出。输入与输出都需要一个文件,因此可以进行一个文件的复制。输入流与输出流方法大致雷同。缓冲流的话则是生成一个小空间进行一个提前编写,需要进行缓冲释放,否则无法将内容输入。转换流的话则可以让字符与字节进行一个转换。最后的是关于一个数据与对象流。对象流因为有序列化,所以值得注意的是我们需要给类进行一个接口绑定,以便于识别(Serializable)。此外在该类中的静态方法属性是无法被序列化的。若还想让其他方法不被序列化则需要添加签名:transient。
总结:File进行磁盘空间的操作,IO则是在该基础上进行磁盘文件的内容大小进行一个处理。(终于摆脱只能讨论内存咯)。
每日金句:
不要总是怀念过去,昨天的太阳,永远晒不干今天的衣服。
本文发布于:2024-02-01 04:01:19,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170673127933705.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |