上传操作:IO读取客户端数据写到服务器。
IO也可以读取服务器数据显示到客户端。
我们要实现IO的操作,就必须知道硬盘上文件的表现形式,所以java提供了File类
File类:io包下,文件和目录路径名的抽象表示形式(未必真实存在)
构造方法:(路径名中的\要用\表示)
public File(String pathname) 根据一个路径得到File对象
public File(String parent,String child) 根据一个目录和一个子文件/目录得到File对象
public File(File parent,String child) 根据一个父File对象和一个子文件/目录得到File对象
File类的成员方法:
1.创建功能
public boolean createNewFile() 创建文件 如果存在这样的文件,就不创建(这个方法有异常,错的话会抛出)
public boolean mkdir() 创建文件夹 如果存在这样的文件夹,就不创建了(这个方法没有异常,错的话就是false)
要想在某个目录下创建内容,该目录首先必须存在,所以有了这个方法:
public boolean mkdirs() 创建文件夹 如果父文件夹不存在,会帮你创建出来(创建多级文件夹)
注意:创建文件还是文件夹要搞清楚,方法不要调错了
如果创建文件或者文件夹没有写盘符,那么默认在项目路径下。(项目中就能看到)
2.删除功能
public boolean delete()
java中的删除不走回收站
要删除一个文件夹,注意该文件夹内不能包含文件或者文件夹
3.重命名功能
public boolean renameTo(File dest)
如果路径名相同就是改名。如果路径名不同,就是改名并剪切
路径以盘符开始:绝对路径 c:\a.txt
路径不以盘符开始:相对路径 a.txt
4.判断功能
public boolean isDirectory() 判断是否是目录
public boolean isFile()
public boolean exists()
public boolean canRead()
public boolean canWrite()
public boolean isHidden()
5.基本获取功能
public String getAbsolutePath() 获取绝对路径
public String getPath() 获取相对路径
public String getName() 获取名称
public long length() 获取长度(字节数)
public long lastModified() 获取最后一次的修改时间(毫秒值)
6.高级获取功能
public String[] list() 获取指定目录下的所有文件或者文件夹的名称数组
public File[] listFiles() 获取指定目录下的所有文件或者文件夹的File数组
1.判断E盘下是否有后缀名为.txt的文件,如果有,就输出此文件名称:
A:先获取所有的E盘目录中的文件和文件夹,然后在遍历的时候判断是否是文件,是否是后缀为txt,满足条件就输出(用public File[] listFiles()方法)
B:文件名称过滤器获取E盘目录下的所有数组名称,判断满足条件的元素并直接输出 (用public String[] list()方法)
\2. 批量修改一系列文件的名字:
封装目录;获取该目录下所有文件的File数组;遍历该数组得到每一个File对象;拼接一个新的名称;调用方法重命名即可。
7.文件名称过滤器:FilenameFilter是一个接口,可以写这个接口的匿名实现类
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
IO流用来处理设备之间的数据传输:上传文件和下载文件
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
IO流分类:
1.按照数据流向:
输入流 读入数据 从数据源
输出流 写出数据 到目的地
2.按照数据类型
字节流
字符流(为了方便操作文本数据)
如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。如果你什么都不知道,就用字节流
所以要学四个类:
\1. 字节输入流 InputStream(抽象类) Xxx InputStream(实现子类)
\2. 字节输出流 OutputStream Xxx OutputStream
\3. 字符输入流 Reader Xxx Reader
\4. 字符输出流 Writer Xxx Writer
一般我们在探讨IO流的时候,如果没有明确说明按照哪种分类来说的,是按数据类型即字节流、字符流分的
这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
FileOutputStream的构造方法
FileOutputStream(File file)
FileOutputStream(String name) (一般用这种)
例子:FileOutputStream fos = new FileOutputStream(“fos.txt”);创建字节流对象坐的事情:A:调用系统功能去创建文件B:创建fos对象C:把fos对象指向这个文件 (会有编译异常,抛出即可)
字节流写数据的方法:
public void write(int b) 输出b对应的字符值
public void write(byte[] b) 输出b[]对应的字符值
public void write(byte[] b,int off,int len) 输出b[]对应的从off开始的len长度的字符值
例子:fos.write(“sadfag”.getBytes()); (会有编译异常,抛出即可)
最后要释放资源fos.close();方法:关闭此文件输出流并释放与此流有关的所有系统资源
为什么一定要close()呢:A:让流对象变成垃圾,这样就可以垃圾回收器回收了B:通知系统去释放该文件相关的资源
所以自己输出流操作步骤:1.创建字节输出流对象2.写数据3.释放资源
fos.write(97); 会输出a:97–底层二进制数据–通过记事本打开–97对应的字符值–a
如何实现数据的换行:写入换行符号的话,有的会显示有的不会显示(因为不同的系统针对不同的换行符号是不一样的:windows:\r\n linux:\n Mac:\r 一些常见的高级记事本软件可以识别任意换行符号)
如何实现数据的追加写入(再一次运行就会追加写一次):用构造方法第二个参数是true的情况
public FileOutputStream(String name,boolean append) throws FileNotFoundException 当为true时,追加写入
字节流写数据加入异常处理:throws方法
try…catch…finally…方法:可以把finally中放入close语句(必须在外面初始化赋值null;也可能出现异常导致值为null无法调用close方法,所以加个!=null判断)
字节流读取数据:把数据读取出来显示在控制台
InputStream下的FileInputStream:
FileInputStream的构造方法
FileInputStream(File file)
FileInputStream(String name)
FileInputStream的成员方法
public int read() 一次读取一个字节 如果没有了,返回-1
中文会出问题,因为每次获取一个字节数据,就把该字节数据转换为了字符数据,然后输出到控制台,而汉字是两个字节数据,所以会出问题。
复制文本文件: 数据源:从哪里来 目的地:到哪里去
封装数据源,封装目的地,int by=0; while循环调用Input的read方法并判断不为-1的话,再调用Output的write方法,最后释放资源(谁先谁后都行,我一般先关输出流再关输入流。)
这次汉字转化没有出问题:因为不涉及转换,读一个字节就写一个字节,最后输出的时候自己转换为汉字了。
计算机是什么时候判断两个字节该转换为一个中文的呢:中文的存储分两个字节,第一个字节肯定是负数,第二个字节常见的是负数,也可能是正数,但是无影响。(因为碰到第一个负数就会把后面那个数字一起拼汉字)
复制mp4的时候发现文件太大,复制很慢,所以可以考虑使用一次读取一个字节数组。
public int read(byte[] b) 一次读取一个字节数组 每次可以读取多个数据,提高了操作效率
返回值其实是实际读取的字节个数
数组的长度byte[]一般是1024或者它的倍数
输出的值可以调用String的截取方法,长度为数组的长度
方式一:一次读取一个字节
*public* *static* *void* main(String[] args) *throws* IOException {
FileInputStream fr = *new* FileInputStream(“aaa.txt”);
*int* by = 0;
*while* ((by = fr.read()) != -1) {
System.*out****.print((*char****) by);
}
fr.close();
}
方式二:一次读取一个字节数组
*public* *static* *void* main(String[] args) *throws* IOException {
FileInputStream fr = *new* FileInputStream(“aaa.txt”);
*byte*[] bys = *new* *byte*[5];
*int* len = 0;
*while* ((len = fr.read(bys)) != -1) {
System.*out****.print(*new**** String(bys, 0, len));
}
fr.close();
}
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式),所以专门提供了带缓冲区的字节类,这种类称为缓冲区类(高效类)
字节缓冲输出流 写数据
BufferedOutputStream
字节缓冲输入流 读数据
BufferedInputStream
构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了。
BufferedOutputStream bos =new BufferedOutputStream(new FileOutputStream (“aaa.txt”));
BufferedInputStream bis =new BufferedInputStream(new FileInputStream (“aaa.txt”));
由于字节流操作中文不是特别方便,所以,java就提供了转换流。(也可以用转换char数组来读取中文)
字符流=字节流+编码表。
编码表:由现实世界的字符及其对应的数值组成的一张表
常见编码表
ASCII:美国标准信息交换码 用一个字节的7位可以表示。最高位为符号位,其余为数值位
ISO-8859-1:拉丁码表。欧洲码表 用一个字节的8位表示
GB2312:中国的中文编码表
GBK:中国的中文编码表升级,融合了更多的中文文字符号
GB18030:GBK的取代版本
BIG-5码:通行于台湾、香港地球的一个繁体字编码方案,俗称“大五码”
Unicode 字符集:国际标准码,融合了多种文字。所有的文字都用两个字节来表示,java语言采用的就是unicode
UTF-8:最多用三个字节来表示一个字符 能用一个的就用一个(ASCII兼容)一个表示不了的就用两个,实在不行的用三个字节。(国际化都用这个)
String(byte[] bytes,String charsetName) 通过指定的字符集解码字节数组
byte[] getBytes(String charsetName) 使用指定的字符集把字符串编码为字节数组
编码:把看得懂的变成看不懂的 String – byte[]
解码:把看不懂的变成看得懂的 byte[] –String
只要编码解码格式一致就不会有任何问题,即使中间的文档一点看不懂。
OutputStreamWriter 字符输出流(把字节流转换为字符流)
public OutputStreamWriter(OutputStream out) 根据默认编码把字节流的数据转换为字符流
public OutputStreamWriter(OutputStream out,String charsetName) 根据指定编码把字节流的数据转换为字符流
InputStreamReader 字符输入流
public InputStreamReader(InputStream in) 用默认的编码读取数据
public InputStreamReader(InputStream in,String charsetName) 用制定的编码读取数据
OutputStreamWriter写数据:父类是io.Writer
方法:public void write(int c) 写一个字符
public void write(char[] cbuf) 写一个字符数组
public void write(char[] cbuf,int off,int len) 写一个字符数组的一部分
public void write(String str) 写一个字符串
public void write(String str,int off,int len) 写一个字符串的一部分
字符流写数据,有可能不会出现,因为是字符,而文件中数据存储的基本单位是字节,所以需要刷新缓存区void flush()方法(close方法也能刷新,刷新完并关闭)
flush()和close()的区别:
close()关闭流对象,但是先刷新一次缓冲区,关闭后,该对象不可以继续在使用。
flush()仅仅刷新缓冲区,刷新后,流对象还可继续使用(仅在数据量巨大的时候调用此方法,一般使用close方法就够了,因为每调用一次这个方法就会在内存中多占一点位置)
InputStreamReader读数据
方法:public int read() 一次读取一个字符
public int read(char[] cbuf) 一次读取一个字符数组
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类。
FileWriter 是 OutputStreamWriter的子类
因为OutputStreamWriter= FileOutputStream +编码表(GBK)
所以FileWriter=FileOutputStream +编码表(GBK)
FileReader 是InputStreamReader的子类
字符流为了高效读写,提供了字符缓冲流:
BufferedWriter:字符缓存输出流
BufferedReader:字符缓存输入流
复制图片,视频时不能用字符流。
字符缓存流的特殊方法:
BufferedWriter:public void newLine() 根据系统来决定换行符
BufferedReader:public String readLine() 一次读取一行数据 包含该行内容的字符串,不包含任何终止符,如果已达到流末尾,则返回null
一般写数据,换行,刷新连写(第五种方法)。
LineNumberReader 是BufferedReader的子类,他有2个特有的方法:
public int getLineNumber() 获得当前行号
public void setLineNumber(int lineNumber) 设置行号从这开始
操作基本数据类型:可以读写基本数据类型的数据
DataInputStream(OutputStream out) 数据输入流
DataOutputStream(InputStream in) 数据输出流
内存操作流:用于处理临时存储信息的,程序结束,数据就从内存中消失
操作字节数组:无需关闭流(源码中close()方法中什么都没有是个空方法)
ByteArrayInputStream
ByteArrayOutputStream
操作字符数组
CharArrayReader
CharArrayWrite
操作字符串
StringReader
StringWriter
打印流:
字节打印流 PrintStream
字符打印流 PrintWriter
打印流特点:实现了Buffered的快速
只有写数据的,没有读数据的。只能操作目的地,不能操作数据源。
可以操作任意类型的数据。print println方法
如果启动了自动刷新,能够自动刷新。(构造方法(,true )但是只要println可以自动刷新,print不能自动刷新。此时的println就相当于write();newLine();flush())
该流可以直接操作文本文件。看API,查看流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的。
流:基本流:就是能够直接读写文件的
高级流:在基本流基础上提供了一些其他的功能
标准输入输出流
System类中的两个成员变量:
public static final InputStream in: “标准”输入流 InputStream is=System.in
public static final PrintStream out: “标准”输出流 PrintStream ps=System.out
键盘录入数据:
A:main方法的args接收参数
java HelloWorld hell world java
B:Scanner(JDK5以后的)
Scanner sc=new Scanner(System.in);
String s=sc.nextLine();
int x=sc.nextInt();
C:通过字符缓冲流包装标准输入流实现
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
随机访问流:RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。支持对随机访问文件的读取和写入。
pubic RandomAccessFile(String name , String mode): 第一个参数是文件路径,第二个参数是操作文件的模式
模式有4中,“r” 只读 “rw” 可读可写(常用) “rws” “rw”
该文件指针可以通过getFilePointer方法读取,并通过seek方法设置
合并流:SequenceInputStream类可以将多个输入流串流在一起,合并为一个输入流,因此,该流也被称为合并流
构造方法:SequenceInputStream(InputStream s1, InputStream s2) 将2个文件合并
SequenceInputStream(Enumeration<? extends InputStream> e) 多个文件的集合
序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象–流数据
ObjectOutputStream
反序列化流:把文本文件中的流对象数据或者网络中的流对象还原成对象。流数据–对象
ObjectInputStream
类通过java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。该接口没有任何方法。类似于这种没有方法的接口称为标记接口。
当类实现了序列化接口的时候,这个类会产生一个序列化id值,当你在修改类的时候,这个值就会发生改变,而会产生与序列化时不匹配的错误,需要在运行重新序列化或者反序列化才能匹配。所以要想解决这个问题,就得在实现序列化接口的类上自动产生一个固定的序列化值,再生成这个值后,在对类进行任何改动,它读取以前的数据是没有问题的。
使用transient关键字可以声明不需要序列化的成员变量,输出的是默认值
Properties:属性集合类。是一个可以和IO流相结合使用的集合类
Properties可保存在流中或者从流中加载。属性列表中每个键及其对应值都是一个字符串。
是Hashtable的子类是一个Map集合。
特殊功能
public Object setProperty(String key,String value) 添加元素
public String getProperty(String key) 根据键获取对应值
public Set
Properties和IO流的结合使用
public void load(Reader reader) 把文件中的数据读取到集合中
public void store(Writer writer,String comments) 把集合中的数据存储到文件
这里的集合必须是Properties集合,数据必须是键值对形式
可以利用这个方法进行单机游戏的存储和读取
NIO其实就是新IO的意思,JDK4出现,提高了IO流的操作效率(在内存中):Buffer(缓冲)和Channer(通道)
JDK7的IO改进
Path(接口):与平台无关的路径
Paths(类):有一个静态方法返回一个路径
Files:操作文件的工具类,提供了静态方法供我们使用
public static long copy(Path source,OutputStream out):复制文件
public static Path write(Path path,Iterable<? extends CharSequence> lines, Charset cs, OpenOption… options): 把集合的数据写到文件
try-with-resource语法糖
在java开发中,一些网络链接或者是文件资源都需要程序员去手动调用close方法关闭,比如InputStream、OutputStream和java.sql.Connection。
如果忘关了就可能造成严重的性能后果。而关闭的方法有很多种。比如finalizer、try-catch-finally、try-with-resources等等。
try-with-resources
try-with-resources是jdk1.7引入的语法糖,使得关闭资源操作无需层层嵌套在finally。
finalizer 会引发内存泄漏
垃圾回收器发现对象实现了finalize()方法并把它们添加到java.lang.ref.Finalizer.ReferenceQueue队列中。
然后Finalizer线程 会把ReferenceQueue里面的对象逐个弹出。
不过由于它的优先级比主线程较低,获取到的CPU时间较少,因此它永远也赶不上主线程创建对象的步伐。
所以会引发内存泄漏,所以finalize()并不适合用作普通的清理工作,一般只用于当java中调用非java代码时重写finalize()方法,并在里面调用本地方法的free()等函数。
或者在一下时候有某些用处:
存在一系列对象,对象中有一个状态为false,如果我们已经处理过这个对象,状态会变为true,为了避免有被遗漏而没有处理的对象,就可以使用finalize()方法:
class MyObject{
boolean state = false;
public void deal(){
//...一些处理操作
state = true;
}
@Override
protected void finalize(){
if(!state){
System.out.println("ERROR:" + "对象未处理!");
}
}
//...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
finally的执行顺序
1、finally不是必要条件
也就是说try-catch-finally中,可以只有try-catch,也可以只有try-finally。
2、假设基于try-catch-finally:
第一:代码没有异常
执行顺序:try执行完整->catch不执行->finally执行
第二:代码有异常且catch进行捕获**
执行顺序:try执行部分->跳转catch捕获处理->finally执行
第三:代码有异常且catch不捕获:这种情况没有catch**
执行顺序:try执行部分->finally执行
从上面的执行顺序可以看出,finally语句不管在哪种情况是一定会执行的。基于这个认识,现在我们再来分析。
题外话: 当finally有return时,会直接返回。不会再去返回try或者catch中的返回值,而finally没有return时,try和catch 的return语句并不会马上执行,而是执行完finally代码块之后再返回try和catch里面的值。
try-finally的缺点
同时打开了多个资源,那么将会出现噩梦般的场景:
public class Demo {
public static void main(String[] args) {
BufferedInputStream bin = null;
BufferedOutputStream bout = null;
try {
bin = new BufferedInputStream(new FileInputStream(new File(“test.txt”)));
bout = new BufferedOutputStream(new FileOutputStream(new File(“out.txt”)));
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (bin != null) {
try {
bin.close();
}
catch (IOException e) {
throw e;
}
finally {
if (bout != null) {
try {
bout.close();
}
catch (IOException e) {
throw e;
}
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
我们不仅需要关闭 BufferedInputStream ,还需要保证如果关闭 BufferedInputStream 时出现了异常, BufferedOutputStream 也要能被正确地关闭。所以我们不得不借助finally中嵌套finally大法。可以想到,打开的资源越多,finally中嵌套的将会越深。
主要是这样子写代码是真的丑。
用try-with-resource来改写刚才的例子:
要使用try-with-resource的资源,必须先实现AutoCloseable接口,其中包含了单个返回void的close方法,Java类库与第三方类库中的许多类和接口,现在都实现或扩展了AutoCloseable接口,因此我们现在不必实现了。
1
public class TryWithResource {
public static void main(String[] args) {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File(“test.txt”)));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File(“out.txt”)))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
原理
编译器会自动帮我们补全close(),而且可以避免异常屏蔽
看一下反编译出来的代码:
package com.codersm.trywithresource;
public class TryWithResource {
public TryWithResource() {
}
public static void main(String[] args) {
try {
Connection conn = new Connection();
Throwable var2 = null;
try {
conn.sendData();
} catch (Throwable var12) {
var2 = var12;
throw var12;
} finally {
if (conn != null) {
if (var2 != null) {
try {
conn.close();
} catch (Throwable var11) {
var2.addSuppressed(var11);
}
} else {
conn.close();
}
}
}
} catch (Exception var14) {
var14.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在第15~27行,编译器自动帮我们生成了finally块,并且在里面调用了资源的close方法,所以例子中的close方法会在运行的时候被执行。
异常屏蔽
我们将刚才的代码改回远古时代手动关闭异常的方式,并且在 sendData 和 close 方法中抛出异常:
public class Connection implements AutoCloseable {
public void sendData() throws Exception {
throw new Exception(“send data”);
}
@Override
public void close() throws Exception {
throw new MyException(“close”);
}
}
1
2
3
4
5
6
7
8
9
修改main方法:
public class TryWithResource {
public static void main(String[] args) {
try {
test();
}
catch (Exception e) {
e.printStackTrace();
}
}
private static void test() throws Exception {
Connection conn = null;
try {
conn = new Connection();
conn.sendData();
}
finally {
if (conn != null) {
conn.close();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
运行之后我们发现:
basic.exception.MyException: close
at basic.exception.Connection.close(Connection.java:10)
at basic.exception.TryWithResource.test(TryWithResource.java:82)
at basic.exception.TryWithResource.main(TryWithResource.java:7)
……
1
2
3
4
5
问题来了,由于我们一次只能抛出一个异常,所以在最上层看到的是最后一个抛出的异常——也就是 close 方法抛出的 MyException ,而 sendData 抛出的 Exception 被忽略了。这就是所谓的异常屏蔽。由于异常信息的丢失,异常屏蔽可能会导致某些bug变得极其难以发现,程序员们不得不加班加点地找bug,如此毒瘤,怎能不除!幸好,为了解决这个问题,从Java 1.7开始,大佬们为 Throwable 类新增了 addSuppressed 方法,支持将一个异常附加到另一个异常身上,从而避免异常屏蔽。那么被屏蔽的异常信息会通过怎样的格式输出呢?我们再运行一遍刚才用try-with-resource包裹的main方法:
java.lang.Exception: send data
at basic.exception.Connection.sendData(Connection.java:5)
at basic.exception.TryWithResource.main(TryWithResource.java:14)
……
Suppressed: basic.exception.MyException: close
at basic.exception.Connection.close(Connection.java:10)
at basic.exception.TryWithResource.main(TryWithResource.java:15)
… 5 more
1
2
3
4
5
6
7
8
9
可以看到,异常信息中多了一个 Suppressed 的提示,告诉我们这个异常其实由两个异常组成, MyException 是被Suppressed的异常。可喜可贺!
仔细对比发现,是因为反编译的代码(第21行)多了一个 addSuppressed :var2.addSuppressed(var11);
从而避免了异常屏蔽的问题。
结论
处理必须关闭的资源时,始终要优先考虑使用try-with-resources,而不是try-finally。这样得到的代码将更简洁,清晰,产生的异常也更有价值,这些也是try-finally无法做到的。