文件IO系列
文件和文件夹都是用File
代表
文件对象
创建一个文件对象
注意:不是创建文件
首先导入 File
类
使用绝对路径或者相对路径创建File对象
1 2 3 4 5
| File f1 = new File("d:/LOLFolder");
File f2 = new File("LOL.exe");
|
1 2
| File f3 = new File(f1, "LOL.exe");
|
文件常用方法
1
| File f = new File("d:/LOLFolder/skin/garen.ski");
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| f.exists();
f.isDirectory();
f.isFile();
f.length();
long time = f.lastModified(); Date d = new Date(time);
f.setLastModified(0);
|
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
| f.list();
File[]fs = f.listFiles();
f.getParent();
f.getParentFile();
f.mkdir();
f.mkdirs();
f.createNewFile();
f.getParentFile().mkdirs();
f.listRoots();
f.delete();
f.deleteOnExit();
|
什么是流
流(Stream)就是一系列的数据
当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,数据库,网络,甚至是其他的程序。
比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
(输入输出是针对Java虚拟机JVM而言的,流入JVM叫输入,反之叫输出)
- 输入流:InputStream
- 输出流:OutputStream
文件输入流
java中通过 FileInputStream()
实现文件输入流。
如下代码,就建立了一个文件输入流,这个流可以用来把数据从硬盘的文件,读取到JVM(内存)。
目前代码只是建立了流,还没有开始读取,真正的读取在下个章节讲解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package stream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class TestStream { public static void main(String[] args) { try { File f = new File("d:/lol.txt"); FileInputStream fis = new FileInputStream(f); } catch (IOException e) { e.printStackTrace(); } } }
|
文件输出流
FileOutputStream
:通过这个输出流,就可以吧数据从java的虚拟机中写入硬盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package stream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; public class TestStream { public static void main(String[] args) { File f =new File("d:/lol.txt"); try { FileOutputStream fos = new FileOutputStream(f); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
|
字节流
字节流即:用于以字节的形式读取和写入数据
InputStream:字节输入流
OutputStream:字节输出流
ASCII码概念
所有的数据存放在计算机中都是以数字的形式存放的。 所以字母就需要转换为数字才能够存放。
比如A就对应的数字65,a对应的数字97. 不同的字母和符号对应不同的数字,就是一张码表。
ASCII是这样的一种码表。 只包含简单的英文字母、符号、数字等。 不包含中文,德文,俄语等复杂的。
以字节流形式读取文件内容
InputStream
是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileInputStream
是InputStream 子类,以 FileInputStream 为例进行文件读取
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
| package IOlearning.stream;
import java.io.File; import java.io.FileInputStream; import java.io.IOException;
public class StreamTest {
public static void main(String[] args) { try { File f = new File("d:/javaTest.txt");
FileInputStream fis = new FileInputStream(f);
byte[] all = new byte[(int) f.length()]; fis.read(all); for (byte b : all) { System.out.print(b + " ");
}
fis.close();
} catch (IOException e) { e.printStackTrace(); }
} }
|
以字节流的形式向文件写入数据
OutputStream
是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileOutputStream
是OutputStream子类,以FileOutputStream 为例向文件写出数据
注: 若文件d:/lol2.txt不存在,写出操作会自动创建该文件。
但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常
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
| package stream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class TestStream { public static void main(String[] args) { try { File f = new File("d:/javaTest2.txt"); byte data[] = { 88, 89 }; FileOutputStream fos = new FileOutputStream(f); fos.write(data); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
总结(利用流读取文件步骤)
4个操作步骤:
-
创建文件对象
-
将文件放入对应的流(输入、输出流)
-
操作流(读取、写入)
-
关闭流(close)
关闭流的方式
在try里关闭
1 2 3 4 5 6 7 8 9 10 11 12 13
| try {
File f = new File("d:/javaTest.txt"); FileInputStream fis = new FileInputStream(f); byte[] all = new byte[(int) f.length()]; fis.read(all);
fis.close();
} catch (IOException e) { e.printStackTrace(); }
|
在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端:
如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。 不推荐使用
在finally里关闭
这是标准的关闭流的方式
-
首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
-
在finally关闭之前,要先判断该引用是否为空
-
关闭的时候,需要再一次进行try catch处理
这是标准的严谨的关闭流的方式,但是看上去很繁琐,所以写不重要的或者测试代码的时候,都会采用上面的有隐患的方式,因为不麻烦🤣
下面是标准方式:
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
| package stream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class TestStream { public static void main(String[] args) { File f = new File("d:/lol.txt"); FileInputStream fis = null; try { fis = new FileInputStream(f); byte[] all = new byte[(int) f.length()]; fis.read(all); for (byte b : all) { System.out.println(b); } } catch (IOException e) { e.printStackTrace(); } finally { if (null != fis) try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
使用try()的方式
把流定义在try()里,当try、catch或者finally结束的时候,会自动关闭。注意区别在try里关闭
这种编写代码的方式叫做 try-with-resources, 这是从JDK7开始支持的技术。
所有的流,都实现了一个接口叫做 AutoCloseable
,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。
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
| package stream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class TestStream { public static void main(String[] args) { File f = new File("d:/lol.txt"); try (FileInputStream fis = new FileInputStream(f)) { byte[] all = new byte[(int) f.length()]; fis.read(all); for (byte b : all) { System.out.println(b); } } catch (IOException e) { e.printStackTrace(); } } }
|
字符流
上面所讲的 InputStream
与 OutputStream
是字节流;
这里还有字符流,即专门用于以字符的形式读写数据:
用字符流读取文件
FileReader
是 Reader子类,以FileReader 为例进行文件读取
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
| package stream; import java.io.File; import java.io.FileReader; import java.io.IOException; public class TestStream { public static void main(String[] args) { File f = new File("d:/lol.txt"); try (FileReader fr = new FileReader(f)) { char[] all = new char[(int) f.length()]; fr.read(all); for (char b : all) { System.out.println(b); } } catch (IOException e) { e.printStackTrace(); } } }
|
用字符流把字符串写入文件
FileWriter
是Writer的子类,以FileWriter 为例把字符串写入到文件
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
| package stream; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class TestStream { public static void main(String[] args) { File f = new File("d:/lol2.txt"); try (FileWriter fr = new FileWriter(f)) { String data="abcdefg1234567890"; char[] cs = data.toCharArray(); fr.write(cs); } catch (IOException e) { e.printStackTrace(); } } }
|
中文问题
编码概念
ASCII 字符集只有256个字符,用 0-255 之间的数字来表示。包括大小写字母、数字以及少数特殊字符:如标点符号、货币符号等。
对于大多数拉丁语言来说,这些字符已经够用。
但是,许多亚洲和东方语言所用的字符远远不止256个字符。有些超过千个。
因此,为了突破 ASCII 码字符数的限制,试图用新的编码方法来针对超过256个字符的语言编写计算机程序
常见编码
工作后经常接触的编码方式有如下几种:
- ISO-8859-1/ASCII: 数字和西欧字母
- GBK/GB2312/BIG5: 中文
- UNICODE: 统一码,万国码
其中
- ISO-8859-1 包含 ASCII
- GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
- UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中
UNICODE和UTF
虽然UNICODE可以存储所有字符,但如果完全按照UNICODE的方式来存储数据,就会有很大的浪费。因为1个Unicode字符就占用4 bytes
倘若一篇文章大部分都是英文字母,那么按照UNICODE的方式进行数据保存就会消耗很多空间
在这种情况下,就出现了UNICODE的各种减肥子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果
UTF-8,UTF-16和UTF-32 针对不同类型的数据有不同的减肥效果,一般说来UTF-8是比较常用的方式
UTF-8,UTF-16和UTF-32 彼此的区别在此不作赘述,有兴趣的可以参考 unicode-百度百科
UTF-8编码方式:数字和字母用一个字节, 汉字用3个字节。
Java采用的是Unicode
写在.java源代码中的汉字,在执行之后,都会变成JVM中的字符。
而这些中文字符采用的编码方式,都是使用UNICODE.
例如: "中"字对应的UNICODE是4E2D,所以在内存中,实际保存的数据就是十六进制的0x4E2D, 也就是十进制的20013。
一个汉字使用不同编码方式的表现
以字符 中 为例,查看其在不同编码方式下的值是多少
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
| package stream; import java.io.UnsupportedEncodingException; public class TestStream { public static void main(String[] args) { String str = "中"; showCode(str); } private static void showCode(String str) { String[] encodes = { "BIG5", "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-32" }; for (String encode : encodes) { showCode(str, encode); } } private static void showCode(String str, String encode) { try { System.out.printf("字符: \"%s\" 的在编码方式%s下的十六进制值是%n", str, encode); byte[] bs = str.getBytes(encode); for (byte b : bs) { int i = b&0xff; System.out.print(Integer.toHexString(i) + "\t"); } System.out.println(); System.out.println(); } catch (UnsupportedEncodingException e) { System.out.printf("UnsupportedEncodingException: %s编码方式无法解析字符%s\n", encode, str); } } }
|
为了能够正确的读取中文内容
-
必须了解文本是以哪种编码方式保存字符的
-
使用字节流读取文本
-
使用对应的编码方式去识别这些数字,得到正确的字符。
如本例,一个文件中的内容是字符"中",编码方式是GBK,那么读出来的数据一定是D6D0。
再使用GBK编码方式识别D6D0,就能正确的得到字符中
注: 在GBK的棋盘上找到的中字后,JVM会自动找到中在UNICODE这个棋盘上对应的数字,并且以UNICODE上的数字保存在内存中。
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
| package stream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class TestStream { public static void main(String[] args) { File f = new File("d:\\project\\j2se\\src\\test.txt"); try (FileInputStream fis = new FileInputStream(f);) { byte[] all = new byte[(int) f.length()]; fis.read(all); String str = new String(all,"GBK"); System.out.println(str); } catch (IOException e) { e.printStackTrace(); } } }
|
用FileReader 字符流正确读取中文
FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成为字符了
而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:
1
| new InputStreamReader(new FileInputStream(f), Charset.forName("UTF-8"));
|
缓存流
以介质是硬盘为例,字节流和字符流的弊端:
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
就好比吃饭,不用缓存就是每吃一口都到锅里去铲。用缓存就是先把饭盛到碗里,碗里的吃完了,再到锅里去铲。
缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作,提高速度。
使用缓存流读取数据
缓存字符输入流 BufferedReader
可以一次读取一行数据,但要注意,缓存流必须建立在一个存在的流的基础上
先准备好文件 d:/lol.txt
,文件内容如下:
garen kill teemo
teemo revive after 1 minutes
teemo try to garen, but killed again
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
| package stream; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class TestStream { public static void main(String[] args) {
File f = new File("d:/lol.txt"); try ( FileReader fr = new FileReader(f); BufferedReader br = new BufferedReader(fr); ) { while (true) { String line = br.readLine(); if (line == null) break; System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }
|
使用缓存流写入数据
之前的 FileOutputStream
与 FileWriter
要写入一串数据时,必须将数据转换为数组,一次只能写入一个字符。
而PrintWriter
缓存字符输出流, 可以一次写入一行数据;
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
| package stream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; public class TestStream { public static void main(String[] args) { File f = new File("d:/lol2.txt"); try ( FileWriter fw = new FileWriter(f); PrintWriter pw = new PrintWriter(fw); ) { pw.println("garen kill teemo"); pw.println("teemo revive after 1 minutes"); pw.println("teemo try to garen, but killed again"); } catch (IOException e) { e.printStackTrace(); } } }
|
flush方法
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush()
方法
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
| package stream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; public class TestStream { public static void main(String[] args) { File f =new File("d:/lol2.txt"); try( FileWriter fr = new FileWriter(f); PrintWriter pw = new PrintWriter(fr); ) { pw.println("garen kill teemo"); pw.flush(); pw.println("teemo revive after 1 minutes"); pw.flush(); pw.println("teemo try to garen, but killed again"); pw.flush(); } catch (IOException e) { e.printStackTrace(); } } }
|
数据流
- DataInputStream 数据输入流
- DataOutputStream 数据输出流
直接读写字符串
使用数据流的writeUTF()
和readUTF()
可以进行数据的格式化顺序读写;
如本例,通过DataOutputStream 向文件顺序写出【布尔值,整数和字符串】。 然后再通过DataInputStream 顺序读入这些数据。
注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException
因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。
读取步骤:
- 创建输入流
FileInputStream()
- 创建数据输入流
DataInputStream()
-
- 读取布尔值:
boolean b= dis.readBoolean();
- 读取整数:
int i = dis.readInt();
- 读取字符串:
String str = dis.readUTF();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| File f =new File("d:/lol.txt"); try ( FileInputStream fis = new FileInputStream(f); DataInputStream dis =new DataInputStream(fis); ){ boolean b= dis.readBoolean(); int i = dis.readInt(); String str = dis.readUTF();
System.out.println("读取到布尔值:"+b); System.out.println("读取到整数:"+i); System.out.println("读取到字符串:"+str);
} catch (IOException e) { e.printStackTrace(); }
|
写入步骤:
- 创建输出流:
FileOutputStream()
- 创建数据输出流:
DataOutputStream()
-
- 写入布尔值true:
dos.writeBoolean(true)
- 写入整数:
dos.writeInt(123)
- 写入字符串:
dos.writeUTF("This is my string")
1 2 3 4 5 6 7 8 9 10 11
| File f =new File("d:/lol.txt"); try ( FileOutputStream fos = new FileOutputStream(f); DataOutputStream dos =new DataOutputStream(fos); ){ dos.writeBoolean(true); dos.writeInt(300); dos.writeUTF("123 this is gareen"); } catch (IOException e) { e.printStackTrace(); }
|
对象流
序列化一个对象
需要用到:
- 对象输入流:
ObjectInputStream
- 对象输出流:
ObjectOutputStream
把一个对象序列化有一个前提是:这个对象的类,必须实现了Serializable接口
1 2 3 4 5 6 7 8 9 10 11
| package charactor; import java.io.Serializable; public class Hero implements Serializable { private static final long serialVersionUID = 1L; public String name; public float hp; }
|
步骤:
- 创建一个Hero对象h,设置其名称为garen。
1 2 3
| Hero h = new Hero(); h.name = "garen"; h.hp = 616;
|
- 把该对象序列化(即写入)到一个文件
garen.lol
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| File f =new File("d:/garen.lol");
try(
FileOutputStream fos = new FileOutputStream(f); ObjectOutputStream oos =new ObjectOutputStream(fos);
) { oos.writeObject(h);
} catch (IOException e) {
e.printStackTrace(); }
|
- 然后再通过序列化把该文件转换为一个Hero对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| File f =new File("d:/garen.lol");
try(
FileInputStream fis = new FileInputStream(f); ObjectInputStream ois =new ObjectInputStream(fis); ){ Hero h2 = (Hero) ois.readObject(); System.out.println(h2.name); System.out.println(h2.hp); }catch (ClassNotFoundException e) {
e.printStackTrace(); }
|
- System.out 是常用的在控制台输出数据的
- System.in 可以从控制台输入数据
1 2 3 4 5 6 7 8 9 10 11 12 13
| try (InputStream is = System.in;) { while (true) { int i = is.read(); System.out.println(i); } } catch (IOException e) { e.printStackTrace(); }
|
Scanner读取字符串
使用System.in.read虽然可以读取数据,但是很不方便;
使用Scanner
就可以逐行读取了
1
| import java.util.Scanner;
|
1 2 3 4 5 6
| Scanner s = new Scanner(System.in);
while(true){ String line = s.nextLine(); System.out.println(line); }
|
Scanner从控制台读取整数
1 2 3 4 5
| Scanner s = new Scanner(System.in); int a = s.nextInt(); System.out.println("第一个整数:" + a); int b = s.nextInt(); System.out.println("第二个整数:" + b);
|