备注:若有不正之处,请多谅解并欢迎批评指正。转载请标明链接:https://www.cnblogs.com/pmbb/p/11427724.html
Java 流(Stream)、文件(File)和IO
Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。
1. File类的基本介绍
Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。
File对象代表磁盘中实际存在的文件和目录。通过以下构造方法创建一个File对象。
通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。
1 File(File parent, String child);
通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。
1 File(String pathname)
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
1 File(String parent, String child)
通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。
1 File(URI uri)
一般采用的构造方式为: File file = new File(pathname),其它3个由于特定需求较少或者复杂,除非不得已,通常情况下不用。
路径pathname可以是相对路径,也可以是绝对路径。
创建File对象成功后,可以使用以下列表中的方法操作文件。
序号 | 方法描述 |
---|---|
1 | public String getName() 返回由此抽象路径名表示的文件或目录的名称。 |
2 | public String getParent()、 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null 。 |
3 | public File getParentFile() 返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null 。 |
4 | public String getPath() 将此抽象路径名转换为一个路径名字符串。 |
5 | public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。 |
6 | public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串。 |
7 | public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。 |
8 | public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。 |
9 | public boolean exists() 测试此抽象路径名表示的文件或目录是否存在。 |
10 | public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
11 | public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
12 | public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。 |
13 | public long length() 返回由此抽象路径名表示的文件的长度。 |
14 | public boolean createNewFile() throws IOException 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。 |
15 | public boolean delete() 删除此抽象路径名表示的文件或目录。 |
16 | public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
17 | public String[] list() 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。 |
18 | public String[] list(FilenameFilter filter) 返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的。 |
19 | public File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。 |
20 | public File[] listFiles(FileFilter filter) 返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。 |
21 | public boolean mkdir() 创建此抽象路径名指定的目录。 |
22 | public boolean mkdirs() 创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。 |
23 | public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。 |
24 | public boolean setLastModified(long time) 设置由此抽象路径名所指定的文件或目录的最后一次修改时间。 |
25 | public boolean setReadOnly() 标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。 |
26 | public static File createTempFile(String prefix, String suffix, File directory) throws IOException 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 |
27 | public static File createTempFile(String prefix, String suffix) throws IOException 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。 |
28 | public int compareTo(File pathname) 按字母顺序比较两个抽象路径名。 |
29 | public int compareTo(Object o) 按字母顺序比较抽象路径名与给定对象。 |
30 | public boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等。 |
31 | public String toString() 返回此抽象路径名的路径名字符串。 |
实例
下面的实例演示了File对象的使用:
1 import java.io.File; 2 3 public class DirList { 4 public static void main(String args[]) { 5 String dirname = "/java"; 6 File f1 = new File(dirname); 7 if (f1.isDirectory()) { 8 System.out.println("Directory of " + dirname); 9 String s[] = f1.list(); 10 for (int i = 0; i < s.length; i++) { 11 File f = new File(dirname + "/" + s[i]); 12 if (f.isDirectory()) { 13 System.out.println(s[i] + " is a directory"); 14 } else { 15 System.out.println(s[i] + " is a file"); 16 } 17 } 18 } else { 19 System.out.println(dirname + " is not a directory"); 20 } 21 } 22 }
以上实例编译运行结果如下:
Directory of /java
bin is a directory
lib is a directory
demo is a directory
test.txt is a file
README is a file
index.html is a file
include is a directory
2. 相对路径和绝对路径的介绍
- 相对路径:指pathname相对于应用程序执行路径而言(对于Eclipse或者其它IDE编辑工具时执行时,一般是都是相对于项目根路径而言)。在windows中如果以\开头则代表相对该盘符开始。
- 绝对路径:指相对于操作系统的路径(window系统以盘符+\开头,Linux系统以/开头)
一般采用相对路径来进行指定路径字符串(因为最终项目需要打包,采用绝对路径的话打包后移植到不同系统部署的话很可能找不到)。
而进行测试或者练习性代码编写的情况下,为了快捷也可以采用绝对路径(另外:在JavaEE的Web环境中通常可以获取绝对路径进行处理)。如果采用相对路径则需要将相关资源放置到项目目录相关的文件夹下。
不管是绝对路径还是相对路径,其File分隔符在window与Linux中是有区别的。
其中window的为:\ 如:abc\1.txt。其分隔符在Java代码中构造字符串时需要转义,书写成: "abc\\1.txt"
Linux的为 :/ 如: abc/2.txt。其分隔符在Java代码中构造字符串时不需要转义,直接书写成:"abc/1.txt"
为了兼容File分隔符,可以采用File.sperator代表File分隔符字符串或者File.speratorChar代表File分隔符字符。如:
"abc"+File.sperator+"1.txt" 或 "abc"+File.speratorChar+"1.txt" (推荐采用前者,原因:少书写,少转化)
另外还能够通过环境变量 System.getProperties().getProperty("file.separator") 获取分隔符并指定给某个常量来进行拼接操作。
但是采用兼容性的File分隔符方式或系统变量指派常量来拼接字符串是一件费力麻烦事,幸而的是,File分隔符在File构造时,windows下既可以采用 \ 也可以采用 / 。因此,除非要表现自己写了很多代码,要不然还是直接采用 / 来作为分隔。
代码:
1 import java.io.File; 2 3 public class File001 { 4 public static void main(String[] args) { 5 File file = new File("c:/abc/1.txt"); 6 System.out.println(file); 7 } 8 }
输出:
c:\abc\1.txt
说明:
- 虽然Window情况下在构造时可以采用 / 或 \ ,但在内存中仍然是采用Window风格的路径分隔符 \ 。
- new File(pathname)传递pathname时,要求pathname不可为null,否则出错。另外,传递空字符串""虽然可以,但并没有什么意义。
- 另外,如果是Windows,并且是快捷方式文件,则指定路径是必须以.lnk结尾才能被关联到。
3.File类使用
一般情况下自己输入的字符路径是正正规规的,但在外部未明的传递时,可能是比较混乱的情况,如何知道查看查看呢?
File对象可以获取其各种信息,其中获取路径的方法如下:
getPath():获取文件或文件夹路径。File对象的toString()方法直接调用getPath()。
getAbsolutePath():获取文件或文件夹绝对路径(不会处理.以及..等特殊级符,原样保留)
getCanonicalPath():验证后获取规范化的路径(自动消除.以及..等特殊级符。如果.或..特殊级符连接出错,则抛出异常)。
此方法抛出IOException异常。此方法对于处理有级符或者从外部构造而来的路径时可用。一般自己指定的路径或系统分配的路径直接采用getPath()或getAbsolutePath()处理则可。
代码:
1 import java.io.File; 2 import java.io.IOException; 3 4 public class File001 { 5 public static void main(String[] args) { 6 File file = new File("../..///./a/bc/../abc//"); 7 System.out.println(file); 8 System.out.println(file.getPath()); 9 System.out.println(file.getAbsolutePath()); 10 try { 11 String filepath = file.getCanonicalPath(); 12 System.out.println(filepath); 13 14 File file2 = new File(filepath+"/1.txt"); 15 System.out.println(file2.getCanonicalPath()); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 } 20 21 }
输出:
..\..\.\a\bc\..\abc
..\..\.\a\bc\..\abc
E:\java2019\javacode\LearnJavaIO\..\..\.\a\bc\..\abc
E:\java2019\a\abc
G:\java2019\a\abc\1.txt
说明:
-
- 一般情况下结尾的分隔符都会在结果时被消除掉,所以在以结果进行拼接其它路径的时候要注意。
- 只有在 File file = new File("E:") 等处理根盘符的特殊情况下,getAbsolutePath()与getCanonicalPath()才会在结果加上分隔符。
- 对于非结尾的多个相连的分隔符,构造时会自动进行处理成一个。
- 综合以上3条,强烈建议在对以结果进行拼接其它路径时都加上分隔符。
- 通常情况下自主传入路径的处理中,getAbsolutePath()是最常用的。
另外要判断是相对路径还是绝对路径,可以使用以下方法:
isAbsolute():判断是否绝对路径。
代码:
1 import java.io.File; 2 3 public class File001 { 4 public static void main(String[] args) { 5 File file = new File("G:/java2019/1.txt"); 6 System.out.println(file.isAbsolute()); 7 } 8 }
输出:
true
除了可以获取File对象的路径信息,还可以获取其它信息,比如:
String getParent():获取其所在路径上级目录。
String getName():获取其名称(文件或文件夹的名称)。
File getParentFile():获取其所在路径上级目录相关联的File对象。
String[] list():获取其下一级目录列表的字符串形式(如果文件对象则返回null)。
String[] list(FilenameFilter filter):获取其下一级目录列表的字符串形式,但通过文件名称过滤器过滤。
File[] listFiles():获取其下一级目录列表对象(如果是文件对象则返回null)。较list() 更常用,可以更进一步获取各对象的详细信息。
File[] listFiles(FilenameFilter filter):获取其下一级目录列表对象并通过文件名过滤器进行过滤。
File[] listFiles(FileFilter filter):获取其下一级目录列表对象并通文件过滤器进行过滤。
long lastModified():文件(夹)最近被修改的时间。
代码:
import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.text.SimpleDateFormat; import java.util.Date; public class File001 { public static void main(String[] args) { File file = new File("G:/java2019/1.txt"); System.out.println("文件全路径:"+file.getAbsolutePath()); System.out.println("文件名称:"+file.getName()); System.out.println("上级目录:"+file.getParent()); System.out.println("修改时间:"+new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒").format(new Date(file.lastModified()))) ; File file2 = file.getParentFile(); File[] brothers = file2.listFiles(); System.out.println(file.getName()+"同级目录下还有以下内容:"); for(File f : brothers){ if(file.compareTo(f)!=0){ System.out.println("\t"+f); } } System.out.println(file.getName()+"同级目录下文件后缀为zip的有:"); File[] brothers2 = file2.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".zip"); } }); for(File f2 : brothers2){ if(file.compareTo(f2)!=0){ System.out.println("\t"+f2); } } System.out.println(file.getName()+"同级目录下文件大小大于9M的有:"); File[] brothers3 = file2.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.length()>1024*1024*9; } }); for(File f3 : brothers3){ if(file.compareTo(f3)!=0){ System.out.println("\t"+f3); } } } }
输出:
文件全路径:G:\java2019\1.txt
文件名称:1.txt
上级目录:G:\java2019
修改时间:2019年07月01日 01时41分30秒
1.txt同级目录下还有以下内容:
G:\java2019\11.txt.lnk
G:\java2019\apache-tomcat-7.0.52
G:\java2019\apache-tomcat-7.0.52.zip
G:\java2019\apache-tomcat-8.5.11
G:\java2019\apache-tomcat-8.5.11.zip
1.txt同级目录下文件后缀为zip的有:
G:\java2019\apache-tomcat-7.0.52.zip
G:\java2019\apache-tomcat-8.5.11.zip
1.txt同级目录下文件大小大于9M的有:
G:\java2019\apache-tomcat-8.5.11.zip
File对象比较是否同一文件或文件夹相关联的对象,采用:
int compareTo(File pathname):比较确定是否同一文件关联对象,如果是则返回0.可以用来排除当前路径。代码见上边示例。
File判断文件或文件夹是否存在,方法是:
exists():判断文件或文件夹是否存在(在Window中,如果是链接文件,必须最后以.lnk结果才能被查找到)。代码见下边示例。
File根据传入的路径可以判断是文件还是文件夹,方法是:
isFile():判断是否是文件。如果文件不存在,返回false。
isDirectory():判断是否是文件夹。如果文件夹不存在,则返回false。
说明:isFile()与isDrectory()不可能同时为true。但可能同时为false(此时文件或文件夹不存在。或者存在Window情况下的快捷方式文件路径指定时没有以.lnk结果)。所以在未知文件(夹)是否存在的情况下建议先进行文件(夹)存在性的判断。
代码:
import java.io.File; public class File001 { public static void main(String[] args) { File file = new File("G:/java2019/1.txt"); boolean exsits = file.exists(); if(exsits) { System.out.println("文件存在"); System.out.println(file.isFile()); System.out.println(file.isDirectory()); }else{ System.out.println("文件不存在"); } } }
输出:
文件存在
true
false
除了判断,还可以进行设置:
setExecutable(boolean executable):设置为可执行的状态
setWriteable(boolean writeable):设置可写状态
setReadable(boolean readable):设置可读状态
boolean setLastModified(long time):设置最后修改时间。
File对象获取其空间大小相关的方法为(单位:字节 B):
getTotalSpace():获取文件(夹)所在磁盘的总大小。
getFreeSpace():获取文件(夹)所在磁盘的空闲大小(大于或等于可用大小,因为只有分配了的空间才可用)。
getUsableSpace():获取文件(夹)所在磁盘的可用大小。
length():获取文件大小。如果是文件夹,则返回4096。如果是文件夹(通常通过递归获取其下所有文件大小之和)。
File类还有一个用于列出所有磁盘的静态方法:
static File[] listRoots():列出操作系统中所有根磁盘相关联的File对象
File对象可以创建与之关联的新文件,方法:
boolean createNewFile():创建相关联的文件,如果成功则返回true。如果在创建时如果其目录路径不存在,则会出现:java.io.IOException: 系统找不到指定的路径。
所以如果不确保上级路径是否存在的情况下,建议先进行判断。另外如果文件在创建前已经存在或者权限不足,则返回false,建议在创建之前也判断一下(虽然并不能确切保证)。
代码:
import java.io.File; import java.io.IOException; public class File001 { public static void main(String[] args) throws IOException { File file = new File("G:/java2019/a"); System.out.println(file.getCanonicalPath()); boolean flag = file.createNewFile(); if(flag){ System.out.println("创建成功!"); }else{ System.out.println("创建失败,文件已经存在或者权限不足!"); } } }
输出:
G:\java2019\a
创建失败,文件已经存在或者权限不足!
注意:虽然 G:/java2019/a 形式上很像文件夹,但确实是文件(只不过是没有后缀而已),而createNewFile()也确切只能创建文件,不能用它创建文件夹,要创建文件夹可以使用File对象下边的方法。
File对象创建文件夹,可以使用以下方法:
boolean mkdir():创建路径相关联的文件夹,成功返回true。如果上级目录不存在或者权限不足,则返回false。如果要创建的文件夹已经存在,也返回false(如果要区分哪种情况造成的,可以先判断文件夹存在与否)。该方法并不会产生异常。
boolean mkdirs():创建路径相关联的文件夹,成功返回true。该方法会逐级创建不存在的文件夹。如果权限不足时,才返回false。如果要创建的文件夹已经存在,也返回false(如果要区分哪种情况造成的,可以先判断文件夹存在与否)。该方法并不会产生异常。
建议优先使用mkdirs(),它除了能达到mkdir()的意图,也同时不必关心上级目录的存在情况。
代码:
import java.io.File; public class File001 { public static void main(String[] args) { File file = new File("G:/java2019/dir123"); File fileDir = file.getParentFile(); if (!fileDir.exists()) { System.out.println("找不到指定目录,无法创建该目录下的文件夹!"); } else { if (file.exists()) { System.out.println("文件夹已经存在,不需要进行创建!"); } else { boolean flag = file.mkdir(); if (flag) { System.out.println("创建成功!"); } else { System.out.println("创建失败,权限不足!"); } } } File file2 = new File("G:/java2019/dirABC/aaa/c/d/c"); boolean flag2 = file2.mkdirs(); if (flag2) { System.out.println("创建成功!"); } else { System.out.println("创建失败,文件夹已经存在或者权限不足!"); } } }
输出:
创建成功!
创建成功!
再次执行时输出:
文件夹已经存在,不需要进行创建!
创建失败,文件夹已经存在或者权限不足!
File对象要删除文件或者文件夹,可以用以下方法:
boolean delete():该方法删除相关联的文件或文件夹,成功则返回true。如果文件不存在或者权限不足(可以先用exsits()方法区分),则返回false。该方法不会产生异常。
void deleteOnExit():在虚拟机退出(即程序执行结束)时进行文件删除。一般用于清除缓存文件或临时文件等清理操作。
File对象进行文件或文件夹的重命名(或剪贴操作),可以用以下方法:
boolean renameTo(File dest):进行文件或文件夹重命名,操作成功返回true。如果重命名后的目标文件(夹)已经存在的时候,则操作失败,返回false。注意:renameTo不能进行跨盘操作(必须在同一分区),否则返回false并操作不成功。操作时其内容必须不被锁定(即已经close或者没有文件流操作),否则操作也不成功。当renameTo时,如果源与目录相同目录,则进行的是重命名操作,不同目录(同分区)进行的是剪贴操作。对于跨分区的操作renameTo()无法达成,必须采用复制+删除的方式,利用流的读取写入操作进行(这里暂时不进行)。如果目标文件(夹)的上级目录不存在,则也操作失败。权限不足也操作失败。
代码:
import java.io.File; public class File001 { public static void main(String[] args) { File src1 = new File("G:/java2019/1.txt"); File src2 = new File("G:/java2019/dirABC"); File dest1 = new File("G:/java2019/2.txt"); File dest2 = new File("G:/java2019/dirABC22"); boolean flag1 = src1.renameTo(dest1); boolean flag2 = src2.renameTo(dest2); if(flag1){ System.out.println("文件rename操作成功!"); }else{ System.out.println("文件rename操作失败!"); } if(flag2){ System.out.println("文件夹rename操作成功!"); }else{ System.out.println("文件夹rename操作失败!"); } } }
输出:
文件rename操作成功!
文件夹rename操作成功!
再次执行输出:
文件rename操作失败!
文件夹rename操作失败!
对于同分区要将某个文件夹里边的内容剪贴到另一个文件夹的情况,可以用下边代码操作(用到了递归):
import java.io.File; import java.io.IOException; public class File001 { public static void main(String[] args) throws IOException { File src = new File("E:/studentMsg"); File dest = new File("E:/cccccccccc"); long total = cx(src,dest); System.out.println("剪贴成功!总大小:"+(float)total/(1024*1024) + " MB"); } private static long cx(File from, File to) throws IOException{ long total = 0; if(!to.exists()){ to.mkdir(); } File[] files = from.listFiles(); for(File file : files){ if(file.isDirectory()){ cx(file,new File(to.getAbsolutePath()+"/"+file.getName())); }else{ total += file.length(); file.renameTo(new File(to.getAbsolutePath()+"/"+file.getName())); } } //删除空文件夹 for(File file : files){ file.delete(); } return total; } }
输出:
剪贴成功!总大小:9.250087 MB
File类静态方法,用于创建临时文件:
static File createTempFile(String prefix, String suffix):在系统默认的临时目录(比如:C:\Users\ADMINISTRATOR\AppData\Local\Temp\)创建相应前缀与后缀的临时文件。前缀要求不为null,并且不少于3个字符,否则出现错误。后缀如果为null则默认为".tmp"。该方法抛出IOException
static File createTempFile(String prefix, String suffix, File directory):在自定义的目录(由directory指定)创建相应前缀与后缀的临时文件。前缀要求不为null,并且不少于3个字符,否则出现错误。后缀如果为null则默认为".tmp"。该方法抛出IOException
File类当中的静态属性,用于获取兼容各操作系统的路径相关分隔符号:
public static final String separator:兼容的路径分隔符。在windows下为 \ 。在Linux下为 /
public static final String pathSeparator:兼容的路径集分隔符。在windows下为 ;。在Linux下为 :
File对象其它的方法,用于转化成java.nio.Path及java.net.URI对象:
Path toPath():将file对象转化为Path对象,以方便新IO操作。
URI toURI():将file对象转化为URI对象,以方便网络操作。
URL toURL() throws MalformedURLException:将file对象转化为URL对象,以方便网络操作。该方法已经过期,不作详解。
总结:
- File对象可以用于操作文件或文件夹。以其关联性,对文件或文件夹进行创建,删除,复制,重命名(剪贴),获取或设置权限(读、写、执行)。可以获取相关联文件的大小,可以判断存在与否。可以获取磁盘相关信息。可以进行路径比较,获取上级路径等。
- File类静态属性可以获取兼容操作系统的分隔符。
- File对象不对文件具体内容进行读取或修改,所以不需要进行close(关闭)。
5. IO流的概念和工作原理
一,概念
流:流动,流向,从一端到另一端,源头与目的地,
程序与 文件|网络|数组|数据库.... 之间的联系,以程序为中心
二、io流的工作原理
从硬盘中读入一个文件,要求此文件一定存在,若不存在,报FileNotFoundExpection
从程序中输出一个文件到夹岸畔,此文件可以不存在,若不存在,则船舰一个实现输出,若存在,则将存在的文件覆盖
真正开发时,就用缓冲流来代替节点流
主要最后关闭相应的流,先关闭输出流,在关闭输入流,将此操作放入finally
简化(1,建立关联(选择文件) 2,选择流 3,操作,读取写入 4,释放资源)
常用:
6. IO流的分类
1,流向,输入流与输出流
2,数据:字节流:二进制,能读取一切文件
字符流:读取文本文件,只能处理纯文本
字节流与字符流
1,字节流
输入流:InputStream , read(byte [] b), read(byte [] b, int off, int len), close()
FileInputStream
输出流:OutputStream, write(byte [] b), write(byte [] b , int off , int len), flush(), close()
FileOutputStream
2,字符流
输入流:Reader , read(char [] cbuf), read(char [] cbuf, int off, int len), close()
FileReader
输出流:Writer, write(char [] cbuf), write(byte [] b , int off , int len), flush(), close()
FileWriter
7. 文件流的使用
文件流共有四类,(FileOutputStream)文件字节输出流,(FileInputStream)文件字节输入流,(FileWriter)文件字符输出流,(FileReader)文件字符输入流。
输入流是用来从文件中读取数据到内存中,输出流的作用是写入数据到文件中。
FileInputStream
该流用于从文件读取数据,它的对象可以用关键字 new 来创建。
有多种构造方法可用来创建对象。
可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream f = new FileInputStream("C:/java/hello");
也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:
File f = new File("C:/java/hello"); InputStream out = new FileInputStream(f);
创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作。
序号 | 方法及描述 |
---|---|
1 | public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。 |
2 | protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。 |
3 | public int read(int r)throws IOException{} 这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。 |
4 | public int read(byte[] r) throws IOException{} 这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。 |
5 | public int available() throws IOException{} 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。 |
除了 InputStream 外,还有一些其他的输入流,更多的细节参考下面链接:
- ByteArrayInputStream
- DataInputStream
FileOutputStream
该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建 FileOutputStream 对象。
使用字符串类型的文件名来创建一个输出流对象:
OutputStream f = new FileOutputStream("C:/java/hello")
也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
File f = new File("C:/java/hello"); OutputStream f = new FileOutputStream(f);
创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。
序号 | 方法及描述 |
---|---|
1 | public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。 |
2 | protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。 |
3 | public void write(int w)throws IOException{} 这个方法把指定的字节写到输出流中。 |
4 | public void write(byte[] w) 把指定数组中w.length长度的字节写到OutputStream中。 |
除了OutputStream外,还有一些其他的输出流,更多的细节参考下面链接:
- ByteArrayOutputStream
- DataOutputStream
实例
下面是一个演示 InputStream 和 OutputStream 用法的例子:
先看(FileOutputStream)文件字节输出流:
try { // FileOutputStream out=new FileOutputStream(path); 如果只传入一个参数path,则第二个参数默认为false,即原文件内容会新的写入的内容被覆盖 out=new FileOutputStream(path,true); //如果后面参数为true,则在原内容基础上append新内容 String s="hello world"; // out.write(s.getBytes()); 将字符串转换为字节数组,然后写到文件中 out.write(s.getBytes(),0,5); //表示只读取数组中的0到5位 } catch (IOException e) { e.printStackTrace(); }finally { try { if(out!=null){ out.close(); //不要忘记关闭流 } } catch (IOException e) { e.printStackTrace(); } }
注意这里的 path 的上级文件夹一定要全部都存在,否则会报错,提示找不到文件路径。
FileInputStream)文件字节输入流
FileInputStream in=null; try { // File f=new File(path); // in=new FileInputStream(f); //可传入File对象 in=new FileInputStream(path); //也可直接传入path int i; while((i=in.read())!=-1) { //in.read()每次返回数据中的一个字节,当读到结束,会返回-1 System.out.print((char)i); //将字节转换为字符,然后读出 } } catch (IOException e) { e.printStackTrace(); }finally { try { if(in!=null){ in.close(); //关闭流 } } catch (IOException e) { e.printStackTrace(); } }
该方法将数据一个字节一个字节地读取出来
还有一个方法是建立一个byte数组,将该数组作为缓冲区将数据一段一段的读出
FileInputStream in=null; try { // File f=new File(path); // in=new FileInputStream(f); //可传入File对象 in=new FileInputStream(path); //也可直接传入path byte[] buffer=new byte[1024]; //创建一个1024K的byte数组 // int len = in.read(buffer); // 注意,当read()方法中传入数组时,返回的值就变成了 读取的数据的长度,而不是返回数据中的一个字节 // System.out.println(new String(buffer,0,len)); //根据读取的数据的长度打印出数据内容,这样就把多余的缓存区的空格省略掉了 System.out.println(new String(buffer).trim()); //也可以直接使用trim()方法去掉空格,和上面一行代码效果相同 } catch (IOException e) { e.printStackTrace(); }finally { try { if(in!=null){ in.close(); //关闭流 } } catch (IOException e) { e.printStackTrace(); } }
(FileOutputStream)文件字节输出流和(FileInputStream)文件字节输入流结合使用的案例:
把一个文件的内容读出,然后存到存入另一个文件中
String path2="C://Users/wcl/Desktop/demo2.txt"; FileInputStream in=null; FileOutputStream out=null; try { in=new FileInputStream(path); out=new FileOutputStream(path2); byte[] buffer=new byte[1024]; int len; // while((len=in.read(buffer))!=-1){ //一直向数组中读取数据 // String s=new String(buffer).trim(); //先从byte数组中取出转化为String型 // out.write(s.getBytes(), 0, len); //再将字符串转换成byte型数据,写入文件中 // } len=in.read(buffer); //也可以先获取读取文件的长度 for (int i = 0; i < len; i++) { //然后for循环从byte数组中取出数据写入demo2.txt文件中,和上面的效果相同 out.write(buffer[i]); } out.flush(); //把缓冲区内容强制写出,清空缓冲区,防止有剩余数据未写出 } catch (IOException e) { e.printStackTrace(); }finally { try { if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch (IOException e) { e.printStackTrace(); } }
一般当我们在处理 音频文件、图片 等类型的文件时,一般用字节流。而如果是处理中文内容的,最好使用字符流。
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节, 操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是 音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点.
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列.
字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
FileReader类
FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。可以通过以下几种构造方法创建需要的对象。
在给定从中读取数据的 File 的情况下创建一个新 FileReader。
FileReader(File file)
在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。
FileReader(FileDescriptor fd)
在给定从中读取数据的文件名的情况下创建一个新 FileReader。
FileReader(String fileName)
创建FIleReader对象成功后,可以参照以下列表里的方法操作文件。
序号 | 文件描述 |
---|---|
1 | public int read() throws IOException 读取单个字符,返回一个int型变量代表读取到的字符 |
2 | public int read(char [] c, int offset, int len) 读取字符到c数组,返回读取到字符的个数 |
实例
import java.io.*; public class FileRead { public static void main(String args[]) throws IOException { File file = new File("Hello1.txt"); // 创建文件 file.createNewFile(); // creates a FileWriter Object FileWriter writer = new FileWriter(file); // 向文件写入内容 writer.write("This\n is\n an\n example\n"); writer.flush(); writer.close(); // 创建 FileReader 对象 FileReader fr = new FileReader(file); char[] a = new char[50]; fr.read(a); // 读取数组中的内容 for (char c : a) System.out.print(c); // 一个一个打印字符 fr.close(); } }
以上实例编译运行结果如下:
This
is
an
example
FileWriter类
FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。可以通过以下几种构造方法创建需要的对象。
在给出 File 对象的情况下构造一个 FileWriter 对象。
FileWriter(File file)
在给出 File 对象的情况下构造一个 FileWriter 对象。
FileWriter(File file, boolean append)
参数:
-
- file:要写入数据的 File 对象。
- append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。
构造与某个文件描述符相关联的 FileWriter 对象。
FileWriter(FileDescriptor fd)
在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值。
FileWriter(String fileName, boolean append)
创建FileWriter对象成功后,可以参照以下列表里的方法操作文件。
序号 | 方法描述 |
---|---|
1 | public void write(int c) throws IOException 写入单个字符c。 |
2 | public void write(char [] c, int offset, int len) 写入字符数组中开始为offset长度为len的某一部分。 |
3 | public void write(String s, int offset, int len) 写入字符串中开始为offset长度为len的某一部分。 |
8. 转换流的使用
为什么需要使用转换流
//FileReader和BufferedReader从文本文件中读取数据时使用码表默认是GBK码表,不能修改,
//如果文本文件的编码不是GBK时,就不能使用GBK方式去读取,否则会出现乱码,
//此时需要指定编码去读取文件的内容,只能使用转换流,只有转换流才能指定码表。
转换流概述
-
本质就是字符流,只能操作文本文件。
转换流的作用
-
字节流和字符流相互转换的桥梁。
转换流的分类
-
OutputStreamWriter
-
InputStreamReader
OutputStreamWriter类概述
* 继承Writer类
* 本质是一个字符流,只能操作文本文件。
* 是字符流通向字节流的桥梁。
OutputStreamWrtier类构造方法
OutputStreamWriter(OutputStream out)
* 根据字节流创建字符转换输出流。使用默认码表:GBK
* 可以传递的字节输出流:FileOutputStream,BufferedOutputStream
OutputStreamWriter(OutputStream out, String charsetName)
* 根据字节流创建字符转换输出流。可以通过charsetName指定码表
* 可以传递的字节输出流:FileOutputStream,BufferedOutputStream
OutputStreamWrtier类成员方法
write(); 可以输出一个字符,字符数组,字符串。
字符流转字节流的过程
* 先通过OutputStreamWrtier类查询指定的码表,将要输出的内容转换字节
* 然后将转换到的字节交给OutputStream字节输出流输出到目标文件中。
public class OutputStreamWriterDemo { public static void main(String[] args) { writeGBK(); writeUTF8(); } // 使用UTF8方式输出数据 public static void writeUTF8() { try( // 创建字符转换输出流对象 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c.txt"),"UTF-8"); ){ // 调用方法输出数据 osw.write("你好"); }catch(Exception e) { } } // 使用GBK方式输出数据 public static void writeGBK() { try( // 创建字符转换输出流对象 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt")); ){ // 调用方法输出数据 osw.write("你好"); }catch(Exception e) { } } }
InputStreamReader类概述
继承Reader,本质就是一个字符输入流。只能从文本文件中读取数据。
是字节流转字符流的桥梁。
InputStreamReader类构造方法
InputStreamReader(InputStream in);
使用默认码表:GBK 创建字符转换输入流对象
InputStreamReader(InputStream in,String charsetName);
使用指定码表创建字符转换输入流对象。
InputStreamReader类成员方法
read(); 可以读取一个字符,一个字符数组
字节流转字符流的过程
* 先由InputStream的对象去从目标文件中读取数据,读取到的都是字节数
* 然后将读取到的字节交给InputStreamReader的对象去查询指定的码表得到对应字符。
public class InputStreamReaderDemo { public static void main(String[] args) { readGBK(); readUTF8(); } /** * 使用UTF8方法读取文本文件内容 */ private static void readUTF8() { try( // 创建字符转换输入流对象 InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt"),"utf8"); ){ // 定义字符数组 char[] buf = new char[1024]; // 读取内容 int len = isr.read(buf); System.out.println(new String(buf,0,len)); }catch(Exception e) {} } /** * 使用GBK方法读取文本文件内容 */ private static void readGBK() { try( // 创建字符转换输入流对象 InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"),"GBK"); ){ // 定义字符数组 char[] buf = new char[1024]; // 读取内容 int len = isr.read(buf); System.out.println(new String(buf,0,len)); }catch(Exception e) {} } }
9. 缓冲流的使用
一、缓冲流
使用缓冲流后的输入输出流会先存储到缓冲区,等缓冲区满后一次性将缓冲区中的数据写入或取出。
避免程序频繁的和文件直接操作,这样操作有利于提高读写效率。
缓冲流是构建在输入输出流之上的,可以理解为是对基本输入输出流的增强和扩展,但其根本是建立在输入输出流之上的。
1.字节缓冲流
1.1字节缓冲流类
BufferInputStream
BufferOutputStream
1.2.构造方法:
传递参数为输入、输出流对象。
BufferedInputStream(InputStream in)//构造方法,创建一个缓冲流,保存其参数(输入流),供后续使用
BufferedInputStream(InputStream in, int size)//创建一个缓冲流,size为设置缓冲区大小
BufferedOutputStream(OutputStream out)//创建输出缓冲流
BufferedOutputStream(OutputStream out, int size)//size为缓冲区大小
1.3.例子
给输入输出流加上缓冲流后,其基本功能和方法并没有改变,只是改变了读写所采取的策略,基本方法和操作方式没有发生变化。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; public class Test { public static void main(String[] args){ String pathW = "F:\\依风\\Desktop\\writer.txt"; String pathR = "F:\\依风\\Desktop\\reader.txt";//此处的reader.txt中要有数据 File fr = new File(pathR); File fw = new File(pathW); InputStream r = null; OutputStream w = null; InputStream bufferR = null; OutputStream bufferW = null; try { int c = 0; r = new FileInputStream(fr); w = new FileOutputStream(fw); bufferR = new BufferedInputStream(r);//将输入输出流加上缓冲 bufferW = new BufferedOutputStream(w); try { while(-1 != (c = bufferR.read())){//读一个字符 bufferW.write(c); System.out.print((char)c); } bufferW.flush();//将缓冲区剩余数据写入 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { bufferW.close();//关闭资源 bufferR.close(); r.close(); w.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
2.字符缓冲流
2.1字符缓冲流类
BufferedWriter
BufferedReader
2.2构造方法
BufferedWriter(Writer out)
BufferedWriter(Writer out, int sz)
创建字符缓冲流,sz为指定缓冲区大小。
BufferedReader(Reader in)
BufferedReader(Reader in, int sz)
2.3新增方法
BufferedWtriter:void newLine()//写入行分隔符,简单的说就是加上回车换行
BufferedReader:String readLine()//读取一行,以字符串形式返回读取内容。如果已到末尾返回null
有一点需要注意,这里的新增方法在它们的父类(Wtriter,Reader)中并没有,所以需要使用这些方法时不能发生多态。
3.4例子
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; public class Test { public static void main(String[] args){ String pathW = "F:\\依风\\Desktop\\writer.txt"; String pathR = "F:\\依风\\Desktop\\reader.txt";//该文件必须已有数据 /*此时该文件中的数据: *BufferedStream *BufferReader *BufferWriter * */ File fr = new File(pathR); File fw = new File(pathW); Reader r = null; Writer w = null; BufferedReader bufferR = null;//此处使用的BufferedReader、BufferedWtriter中独有的方法 BufferedWriter bufferW = null;//故不能发生多态,所有不能使用Reader或Writer try { String str; r = new FileReader(fr); w = new FileWriter(fw); bufferR = new BufferedReader(r);//将输入输出流加上缓冲 bufferW = new BufferedWriter(w); try { while(null != (str = bufferR.readLine())){//读一行 bufferW.write(str);//将读取的字符串写入 bufferW.newLine();//写入换行分隔符,即回车 System.out.println(str);//输出读取和写入的数据 } bufferW.flush();//将缓冲区剩余数据写入 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { bufferW.close();//关闭资源 bufferR.close(); r.close(); w.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
运行结果: BufferedStream BufferReader BufferWriter
10. 对象流的使用
为了让对象持久化(把对象存储到本地),可以使用java的对象流处理对象,把对象的内容写到本地存储的文件中,也可以从本地文件中读取出来。也就是常说的序列化和反序列化
主要用到了ObjectInputStream(对象输入流) ObjectOutPutStream(对象输出流 )处理对象
使用对象流处理对象进行持久化的顺序是,先创建目标路径,然后创建流通道,之后调用对象流
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; //创建要写入磁盘的类,这个类需要实现接口 Serializable(可系列化的) class Student implements Serializable{ // 在这里保证了serialVersionUID 的唯一性,防止属性变量的临时改变,从而造成写入id与读取id不同 private static final long serialVersionUID = 1L; int id ; //额外需要添加一个属性 String name ; transient String sex; //transient修饰属性,表示暂时的,则这个属性不会被写入磁盘 transient int age; public Student(String name,String sex,int age){ this.name = name; this.sex = sex; this.age = age; } } public class objectIO { /** * @param args * @throws IOException * @throws ClassNotFoundException */ public static void main(String[] args) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub createObj(); readObj(); } //(一)先写入对象 public static void createObj() throws IOException { //1.创建目标路径 File file = new File("C:\\Users\\bg\\Desktop\\objTest.txt"); //2.创建流通道 FileOutputStream fos = new FileOutputStream(file); //3.创建对象输出流 ObjectOutputStream objOP = new ObjectOutputStream(fos); //4.创建类对象,并初始化 Student stu = new Student("玛丽苏", "男", 18); //5.向目标路径文件写入对象 objOP.writeObject(stu); //6.关闭资源 objOP.close(); } //再读取对象 public static void readObj() throws IOException, ClassNotFoundException { File file = new File("C:\\Users\\bg\\Desktop\\objTest.txt"); FileInputStream fis = new FileInputStream(file); ObjectInputStream objIP = new ObjectInputStream(fis); //读取对象数据,需要将对象流强制转换为 要写入对象的类型 Student stu = (Student)objIP.readObject(); System.out.println("\n name:"+stu.name+"\n sex:"+stu.sex+"\n age:"+stu.age); objIP.close(); } }
打印效果
name:玛丽苏 sex:null //后面的这连个属性使用了 transient修饰 age:0
用到方法:writeObject(Object o); //向磁盘写入对象
readObject(); //读取磁盘的对象,注意这里需要强制类型
对象输入输出流的使用注意点:
1.如果想将一个对象写入到磁盘中,那么对象所属的类必须要进行序列化,实现Serializable 接口,Serializable接口没有任何方法 ,是一个标记接口
2.如果对象所属的类的成员变量发生改变,你在读取原来的对象是就会报错,如果想要解决报错,保证serialVersionUID是唯一。
3.如果你不想将某些信息存入到磁盘 就可以同过transient关键字修饰成员变量
4.如果一个类中引用了另外的一个类,那么另外的这个类也要实现Serializable接口。
如果:
11. 内存流的使用
在之前使用过了文件操作流实现了对于文件数据的输入和输出操作,但是如果现在某一种应用需要进行IO操作,可是又不想产生文件的时候,就可以利用内存来实现输入和输出的操作。针对于内存流,java.io包提供了两组操作。
字节内存流:ByteArrayInputStream,ByteArrayOutputStream
public class ByteArrayStreamDemo { public static void main(String[] args) throws IOException { // 写数据 // ByteArrayOutputStream() ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 写数据 for (int x = 0; x < 10; x++) { baos.write(("hello" + x).getBytes()); } // 释放资源 // 通过查看源码我们知道这里什么都没做,所以根本需要close() // baos.close(); // public byte[] toByteArray() byte[] bys = baos.toByteArray(); // 读数据 // ByteArrayInputStream(byte[] buf) ByteArrayInputStream bais = new ByteArrayInputStream(bys); int by = 0; while ((by = bais.read()) != -1) { System.out.print((char) by); } // bais.close(); } }
字符内存流:CharArrayReader,CharArrayWriter
public static void main(String[] args) throws IOException { CharArrayWriter cw=new CharArrayWriter(); cw.write("hello".toCharArray()); char[] a=cw.toCharArray(); System.out.println(a); CharArrayReader cr=new CharArrayReader(a); char[] buf=new char[1024]; int len; while((len=cr.read(buf))!=-1){ System.out.print(new String(buf,0,len)); } }
字符串:StringReader,StringWriter
public static void main(String[] args) throws IOException { String first="hello"; StringWriter sw=new StringWriter(); sw.write("hello"); StringReader sr=new StringReader("hehe"); char[] buf=new char[1024]; int len; while((len=sr.read(buf))!=-1){ System.out.print(new String(buf,0,len)); } }
12. 使用不同的流实现文件内容的拷贝
使用io流进行文件拷贝
需求:在项目的根目录里面创建一个java.txt的文件,然后将这个文件拷贝到file文件夹里面并且重命名为good.txt文件
先以流的方式将java.txt文件读取到内存中,然后再以流的方式将内存中的内容写出到硬盘里面
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 文件拷贝 * 将java.txt文件拷贝到file文件夹中 * */ public class FileCopy { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("java.txt"); fos = new FileOutputStream("file" + File.separator + "good.txt"); byte[] arr = new byte[1024]; int length; while((length = fis.read(arr)) != -1) { fos.write(arr,0,length); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try { fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
使用缓冲流进行文件拷贝
Java中提供了BufferedInputStream和BufferedOutputStream缓冲流用来读取和写出, BufferedInputStream读取时会创建一个长度为8192的byte类型数组,程序一次读取8192个字节数据到数组中 使用缓冲流之后就不用再自定义byte类型数组了。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 使用缓冲流进行文件拷贝 * 将java.txt文件拷贝到file文件夹中 * */ public class BufferFileCopy { public static void main(String[] args) { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { //使用缓冲流装饰一下 bis = new BufferedInputStream(new FileInputStream("java.txt")); bos = new BufferedOutputStream(new FileOutputStream("file" + File.separator + "good.txt")); int temp; while((temp = bis.read()) != -1) { bos.write(temp); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try { bis.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
两者在效率上区别不大,其实java提供的BufferedInputStream和BufferedOutputStream底层使用的也是byte类型的数组,开发中这两种方式都可以使用。
jdk7的新写法
在jdk7中新加入了AutoCloseable接口,IO流中的类都实现了这个接口,这样在读取或者写出操作结束之后,系统会自动close相关资源,开发者不需要再手动close了
更多文件拷贝:https://blog.csdn.net/qq_41821963/article/details/86703184
13. RandomAccessFile类的使用
RandomAccessFile是IO包的类,从Object直接继承而来。只可以对文件进行操作,可以对文件进行读取和写入。
当模式为r时,当文件不存在时会报异常,当模式为rw时,当文件不存在时,会自动创建文件,当文件已经存在时不会对原文件进行
覆盖。
RandomAccessFile有强大的文件读写功能,其内部是大型byte[],可以通过seek(),getFilePointer()等方法操作的指针,方便对数据 进行写入与读取。还可以对基本数据类型进行直接的读和写操作。 RandomAccessFile的绝大多数功能,已经被JDK1.4的nio的“内存映射文件(memory-mapped files)”给取代了,你该考虑一下是不 是用“内存映射文件”来代替RandomAccessFile。
import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; public class RandomAccessFileDemo { public static void main(String[] args) { copyFile(); } public static void copyFile() { try { //读文件 RandomAccessFile r = new RandomAccessFile("E:\\Java_IO\\file1.txt","r"); //写文件 RandomAccessFile w = new RandomAccessFile("E:\\Java_IO\\file1_1.txt","rw"); byte[] bytes = new byte[1024]; int len = -1; while((len=r.read(bytes))!=-1) { w.write(bytes,0,len); } w.close(); r.close(); System.out.println("copy success."); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
14. Properties类的使用
一、Java Properties类
Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支持的配置文件,配置文件中很多变量是经常改变的,这样做也是为了方便用户,让用户能够脱离程序本身去修改相关的变量设置。像Python支持的配置文件是.ini文件,同样,它也有自己读取配置文件的类ConfigParse,方便程序员或用户通过该类的方法来修改.ini配置文件。在Java中,其配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式,文本注释信息可以用"#"来注释。
Properties类继承自Hashtable,如下:
它提供了几个主要的方法:
1. getProperty ( String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。
2. load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。
3. setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。
4. store ( OutputStream out, String comments),以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。
5. clear (),清除所有装载的 键 - 值对。该方法在基类中提供。
二、Hashtable 与HashMap的区别
1、主要:Hashtable线程安全,同步,效率相对低下
HashMap 线程不安全,非同步,效率相对高
2、父类:Hashtable 是 Dictionary HashMap 是 AbstractMap
3、null: Hashtable键与值不能为null
HashMap 键最多一个null,值可以多个null
三、Properties
1、作用:读写资源配置文件
2、键与值只能为字符串
3、方法:
setProperty(String key, String value)
getProperty(String key)
getProperty(String key, String defaultValue)
后缀:.properties
store(OutputStream out, String comments)
store(Writer writer, String comments)
load(InputStream inStream)
load(Reader reader)
.xml
storeToXML(OutputStream os, String comment) :UTF-8字符集
storeToXML(OutputStream os, String comment, String encoding)
loadFromXML(InputStream in)
四、相对路径与绝对路径
1、绝对路径 : 盘符: /
2、相对路径 : 当前项目、工程
五 、类路径加载资源文件
类所在的根路径
1、类.class.getResourceAsStream("/")
2、Thread.currentThread().getContextClassLoader().getResourceAsStream("")
1.为properties对象添加属性和获取值
@Test public void setAndGetProperty() { Properties pro=new Properties(); //设置值 pro.setProperty("driver", "com.mysql.jdbc.Driver"); pro.setProperty("url", "jdbc:mysql///user"); pro.setProperty("user", "root"); pro.setProperty("password", "451535"); //获取值: //1、getProperty(String key)方法 通过键获取值 String str= pro.getProperty("driver"); System.out.println(str); //2、getProperty(String key, String defaultValue)重载方法 //当properties对象中没有所指定的键值时,显示给定的默认值 String str2=pro.getProperty("driver", "没有该值"); String str3=pro.getProperty("haha", "没有该值"); System.out.println(str2); System.out.println(str3); }
运行结果:
com.mysql.jdbc.Driver
com.mysql.jdbc.Driver
没有该值
2.以properties配置文件格式写入到硬盘中的某个文件夹(本例写入到D盘的others文件夹中):
@Test public void storePropertiesToHardFile() throws FileNotFoundException, IOException{ Properties pro=new Properties(); pro.setProperty("driver", "com.mysql.jdbc.Driver"); pro.setProperty("url", "jdbc:mysql///user"); pro.setProperty("user", "root"); pro.setProperty("password", "451535"); //1.通过字节流的形式 //store(OutputStream out, String comments) //outputStream:字节输出流 comments:配置文件说明 pro.store(new FileOutputStream(new File("d:/others/jdbc.properties")), "数据库配置文件"); //2.通过字符流的形式 //store(Writer writer, String comments) //writer:字符输出流 comments:配置文件说明 pro.store(new FileWriter("d:/others/jdbc.properties"), "数据库配置文件"); }
运行后效果:
3.以XML配置文件格式写入到硬盘中的某个文件夹(本例写入到D盘的others文件夹中):
@Test public void storeXMLToHardFile() throws FileNotFoundException, IOException{ Properties pro=new Properties(); pro.setProperty("driver", "com.mysql.jdbc.Driver"); pro.setProperty("url", "jdbc:mysql///user"); pro.setProperty("user", "root"); pro.setProperty("password", "451535"); //1.不指定编码 默认为:UTF-8 //storeToXML(OutputStream os, String comment) //因为XML不是文本文件,所以只能用字节流,为不能用字符流 pro.storeToXML(new FileOutputStream("d:/others/jdbc.xml"), "数据库配置文件"); //1.不指定编码 //storeToXML(OutputStream os, String comment) //因为XML不是文本文件,所以只能用字节流,为不能用字符流 pro.storeToXML(new FileOutputStream("d:/others/jdbc2.xml"), "数据库配置文件", "GBK"); }
运行后效果:
4.以properties和XML配置文件格式写入到应用程序的某个文件夹(本例写入应用程序的classPath类路径下):
public void storeToClassPsth() throws FileNotFoundException, IOException{ Properties pro=new Properties(); pro.setProperty("driver", "com.mysql.jdbc.Driver"); pro.setProperty("url", "jdbc:mysql///user"); pro.setProperty("user", "root"); pro.setProperty("password", "451535"); pro.store(new FileOutputStream("src/jdbc.properties"), "数据库配置文件"); pro.storeToXML(new FileOutputStream("src/jdbc.xml") , "数据库配置文件"); }
5.加载和读取配置文件(以properties文件为例)
public void loadAndReadFile() throws FileNotFoundException, IOException{ Properties pro=new Properties(); //通过字节输入流 //load(InputStream inStream) pro.load(new FileInputStream("src/sql.properties")); //通过类加载器 获取当前类路径 //类路径是指 / bin路径 pro.load(this.getClass().getResourceAsStream("/sql.properties")); pro.load(this.getClass().getClassLoader().getResourceAsStream("sql.properties")); //也可以使用当前上下文的类加载器,不用“/” pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("sql.properties")); //通过字符输入流 //load(Reader reader) pro.load(new FileReader("src/jdbc.properties")); System.out.println(pro.get("driver")); //以上几种加载配置文件的方法都可以使用,此处都列举出来。 }
运行结果:
com.mysql.jdbc.Driver
6.附上通过读取配置文件JDBC连接数据库的代码干货:
public Connection getConnection() throws Exception{ Properties info=new Properties(); info.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties")); String driver=info.getProperty("driver"); String jdbcUrl=info.getProperty("jdbcUrl"); String user=info.getProperty("user"); String password=info .getProperty("password"); Class.forName(driver); Connection connection=DriverManager.getConnection(jdbcUrl,user,password); return connection; }
总结:通过读取配置文件的形式可以实现代码的深层次解耦,在java编程中,配置文件的重要性更是不言而喻。java编程有一条不成文的规定就是:“约定大于配置,配置大于代码”意思就是能用约定的就不去配置,能用配置文件搞定的就不去写代码,真正牛逼的攻城狮(工程师)不是写代码,而是写配置。java的一些开源框架,如:Struts、Struts2、Hibernate、Spring、MyBatis等都大量的使用配置文件,我们在学习和工作中,都应该将“约定大于配置,配置大于代码”的思想运用到项目中,实现代码的解耦,体现出你比别人的高明之处,才能比别人优秀。
15. 装饰者设计模式
什么是装饰者模式:动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。
适用性:当采用类继承的方式会造成类爆炸的情况。如本文的例子中,基本饮料(被装饰者)可能有茶、水、牛奶等等、可以添加的(装饰者)有糖、果肉、珍珠等。
如果通过继承的方式,为每一种类型的奶茶设计一种类会造成类爆炸,同时也不利于后期的扩展(如又添加一种基本饮料豆浆的情况),此时通过装饰者模式可以很好的解决问题。
装饰者模式本质是一种组合的思想(不同于继承的思想),多组合少继承。利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
装饰者模式的主要组成部分:抽象被装饰者、具体被装饰者、抽象装饰者、具体装饰者。(当只有一个具体被装饰者、一个具体装饰者,其对应的抽象类可以省略)。
源码中的应用
-
- 装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如BufferedReader、InputStream、OutputStream
-
在MyBatis框架中的缓存也使用了装饰者模式,比如FifoCache先入先出算法的缓存;LruCache最近
最少使用的缓存;TransactionlCache事务相关的缓存,都是采用装饰者模式。
如果有时间,大家可以去看看源码,下图是源代码位置
装饰者模式要点:
1 多种具体被装饰者(主体类)抽象出一个抽象被装饰类,后面通过多态,动态传递具体对象。
2 抽象装饰类继承抽象被装饰者(保持接口);要求传入被装饰者(使用父类应用、protected修饰);
3 多种具体装饰者抽象出一个抽象装饰类。
4 具体装饰者中,可以添加新方法,可以重写方法;需要使用被装饰者方法的地方用传入的被装饰者引用。
装饰者模式的基本代码:
/** * 被装饰抽象类——基本饮料 * * 作用:当具体的被装饰类有多种时,抽象提出该抽象类,用于后面实现多态。 */ public abstract class BaseDrink { public abstract int calculate();//计算 public abstract void display();//显示 }
/** * 具体的被装饰者类—— Water类 * 继承抽象的被装饰者类,并实现其中的抽象方法。 */ public class Water extends BaseDrink{ @Override public int calculate() { return 3; } @Override public void display() { System.out.println("water"); } }
/** * 具体的被装饰者类—— Tea类 * 继承抽象的被装饰者类,并实现其中的抽象方法。 */ public class Tea extends BaseDrink{ @Override public int calculate() { return 5; } @Override public void display() { System.out.println("Tea"); } }
/** * 抽象装饰者类——Decorator * 要点:1.抽象装饰者类中继承该抽象类以保持接口规范 * 2.包含该抽象类的引用以通过多态的方式对多种被装饰者类进行装饰。 */ public abstract class Decorator extends BaseDrink{ //继承,保持接口 protected BaseDrink bd; //引用,多态 public Decorator(BaseDrink bd) { this.bd = bd; } @Override public int calculate() { return bd.calculate(); } @Override public void display() { bd.display(); } }
/** * 装饰者类 果肉 */ public class FleshDecorator extends Decorator{ public FleshDecorator(BaseDrink bd) { super(bd); } @Override public int calculate() { return super.calculate() + 2; } @Override public void display() { super.display(); System.out.println("+ flesh"); } } /** * 装饰者类 糖 */ public class SugarDecorator extends Decorator{ public SugarDecorator(BaseDrink bd) { super(bd); } @Override public int calculate() { return super.calculate()+1; } @Override public void display() { bd.display(); System.out.println("+ sugar"); } public void addMoreSugar(){ System.out.println("add more sugar"); } }
/** * 测试用例 */ public class TestCase { public static void main(String[] args) { BaseDrink water = new Water(); SugarDecorator sugarWater = new SugarDecorator(water); sugarWater.addMoreSugar(); sugarWater.display(); System.out.println(sugarWater.calculate()); System.out.println("========================="); BaseDrink tea = new Tea(); FleshDecorator fleshTea = new FleshDecorator(tea); fleshTea.display(); System.out.println(fleshTea.calculate()); } }
//结果 F:\deaIC-2019.1.3-jbr11.win\jre64\bin\java.exe -javaagent:F:\deaIC-2019.1.3-jbr11.win\lib\idea_rt.jar=7425:F:\deaIC-2019.1.3-jbr11.win\bin -Dfile.encoding=UTF-8 -classpath E:\eclipse-workspace\decoratorDemo\out\production\decoratorDemo TestCase add more sugar water + sugar ========================= Tea + flesh
参考网址:
File对象常用操作:https://www.cnblogs.com/dreamyoung/p/11112027.html
Java 流(Stream)、文件(File)和IO:https://www.runoob.com/java/java-files-io.html
IO分类和原理:https://www.jianshu.com/p/254ab99f8cc0
文件流: https://www.cnblogs.com/weimore/p/7253212.html
转换流:https://blog.csdn.net/h294590501/article/details/80178369
缓冲流:https://www.cnblogs.com/huang-changfan/p/9880966.html
对象流:https://www.cnblogs.com/bigerf/p/6143911.html
https://www.cnblogs.com/duzhentong/p/7816551.html
内存流:https://blog.csdn.net/qq_21992025/article/details/73024372
https://www.jianshu.com/p/52e60350bdde
文件拷贝:https://www.jianshu.com/p/6551f8e0fb42
RandomAccessFile类的使用:https://www.jianshu.com/p/300d26a58693
Properties类:https://www.cnblogs.com/ou-pc/p/7668079.html
https://blog.csdn.net/yelang0/article/details/76449165
装饰者设计模式:https://www.jianshu.com/p/df81a440ec38
https://www.cnblogs.com/youzoulalala/p/11046607.html