File
- File类是指 java.io.File类,是Java用来抽象表示文件和文件目录路径的形式,本身与平台无关
- 值得注意的是,File类能够新建、新建、删除、重命名文件和目录,却File 不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
File常用构造器
public File(String pathname)
:pathname为路径创建File对象
pathname可以为绝对路径或者相对路径,如果 pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
绝对路径:是一个固定的路径,从盘符开始
相对路径:是相对于某个位置开始public File(String parent,String child)
:以parent为父路径,child为子路径创建File对象public File(File parent,String child)
:根据一个父File对象和子文件路径创建File对象
路径分隔符
- 路径中的每级目录之间用一个路径分隔符隔开
- 路径分隔符和系统有关:windows和DOS系统默认使用“\”来表示 , UNIX和URL使用“/”来表示 (Java程序支持跨平台运行,因此路径分隔符要慎用)
- 为了解决路径分隔符与系统息息相关导致的隐患,File类提供了一个常量
public static final String separator
,根据操作系统,动态的提供分隔符,不过会比较麻烦
File file1 = new File("d:\\javaio\\info.txt");
//使用separator
File file2 = new File("d:" + File.separator + "javaio" + File.separator + "info.txt");
File常用方法
获取相关信息的方法(前两个用的多,其他稍作了解):
public String getAbsolutePath()
:获取绝对路径
public String getParent()
:获取上层文件目录路径。若无,返回null
public String getPath()
:获取路径
public String getName()
:获取名称
public long length()
:获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified()
:获取最后一次的修改时间,毫秒值
public String[] list()
:获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles()
:获取指定目录下的所有文件或者文件目录的File数组
重命名方法:
public boolean renameTo(File dest)
:把文件重命名为指定的文件路径
示例:
//将file1的文件复制并命名到file2目录下的hi.txt
//必须保证file目录下的文件是不存在的,否则方法返回失败
//因为renameTo方法无法复制重命名在覆盖掉原来的同名文件
//目录无所谓存不存在,但是如果有目录,目录下一定不能有file2的文件
File file1 = new File("hello.txt");
File file2 = new File("D:\\javaio\\hi.txt");
boolean renameRel = file1.renameTo(file2);
判断方法(前三个常用,其他稍作了解)
public boolean isDirectory()
:判断是否是文件目录
public boolean isFile()
:判断是否是文件
public boolean exists()
:判断是否存在
public boolean canRead()
:判断是否可读
public boolean canWrite()
:判断是否可写
public boolean isHidden()
:判断是否隐藏
创建功能方法
public boolean delete()
:删除文件或者文件夹 (注意,Java中删除不走回收站滴)
还有一件事:要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
File创建的对象本身的文件或文件夹可以不存在,而存在的文件或文件夹和不存在的文件或文件夹区别就是File类属性的值,File创建的对象对应的文件或文件夹不存在,则对象中存储的是默认值,如果存在,File对象就包含了文件或文件夹本身的一些属性,供方法调用
I/O
- I/O即是Input/Output,是一种实用的技术,处理设备之间的数据传输,比如读/写文件,网络通信等
- Java程序中,对于数据的输入/输出操作以“流(stream)” 的 方式进行
- java.io包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据
输入(Input):一般以程序作为参照,读取外部设备(硬盘、U盘)的数据到程序(内存)中去,即是将输入流的数据读取到程序中去
输出(Output):一般以程序作为参照,程序将数据输出到外部设备(硬盘、U盘),即是将程序的数据写出到输出流中去
流的分类
按数据单位分为:字节流(8bit)、字符流(16bit)
按流向分为:输入流、输出流
按角色分为:节点流、处理流
Java提供的流的体系非常庞大,我们需要学习其中常用的一些流
以下图是流的体系,可以见到,非常庞大
流的抽象基类
InputStream & Reader
InputStream
和 Reader
是所有输入流的基类
OutputStream & Writer
OutputStream
和 Writer
是所有输出流的基类
按数据类型来分:InputStream
和OutputStream
是字节流,Reader
和Writer
是字符流
InputStream(典型实现:FileInputStream)
int read()
从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值 -1int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已 经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取 的字节数。int read(byte[] b, int off, int len)
将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取 的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于 文件末尾而没有可用的字节,则返回值public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源,一定要记得关闭
Reader(典型实现:FileReader)
int read()
读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1int read(char[] cbuf)
将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源,一定要记得关闭
OutputStream(典型实现:FileOutputStream)
void write(int b)
将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。void write(byte[] b,int off,int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流public void flush() throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源,一定要记得关闭
Writer(典型实现:FileWriter)
- void write(int c)
写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码 void write(char[] cbuf)
写入字符数组void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符void write(String str)
写入字符串void write(String str,int off,int len)
写入字符串的某一部分void flush()
刷新该流的缓冲,则立即将它们写入预期目标public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源,一定要记得关闭
节点流(文件流)
FileReader
和FileWriter
用来读/写文件,使用父类Reader
和Writer
的方法
读取文件FileReader
FileReader fr = null;
try {
fr = new FileReader(new File(d:\\test.txt))
//FileReader按数据类型来分属于字符流,所以用char
char[] buf = new char[1024];
int len;
while(len = read(buf) != -1){
System.out.print(new String(buf,0,len));
}
}catch(IOException e){
System.out.println(("read-Exception :" + e.getMessage());
}finally{
if(fr != null){
try {
fr.close()
}catch (IOException e) {
System.out.println("close-Exception :" + e.getMessage());
}
}
}
读取文件FileInputStream
FileInputStream fs = null;
try {
fs = new FileInputStream(new File(d:\\test.txt))
//FileInputStream按数据类型来分属于字节流,所以用byte
byte[] buf = new byte[1024];
int len;
while((len = read(buf)) != -1){
System.out.print(new String(buf,0,len));
}
}catch(IOException e){
System.out.println(("read-Exception :" + e.getMessage());
}finally{
if(fr != null){
try {
fr.close()
}catch (IOException e) {
System.out.println("close-Exception :" + e.getMessage());
}
}
}
写入文件FileWriter
FileWriter fw = null;
try{
fw = new FileWriter(new File("d:\\text1.txt"));
fw.write("helloworld");
}catch(IOException e){
e.printStackTrace();
}finally{
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
写入文件FileOutputStream
FileOutputStream fw = null;
try{
fw = new FileOutputStream(new File("d:\\text1.txt"));
fw.write("helloworld");
}catch(IOException e){
e.printStackTrace();
}finally{
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意点
- 定义文件路径时,注意:可以用“/”或者“\”。(根据系统不同而定)
- 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文 件将被覆盖。
- 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容
- 在读取文件时,必须保证该文件已存在,否则报异常
- 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
- 字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文 本文件
- 抛出异常时最好使用
try-catch-finally
,防止程序因为异常停止,导致流没有关闭
处理流
这里我们介绍以下处理流之中的缓冲流,为什么叫处理流,缓冲流时提供给I/O节点流一个缓冲区的,可以看作将节点流包裹在内的一个流,对节点流做优化处理,如下图
为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类 时,会创建一个内部缓冲区数组,缺省(默认)使用8192个字节(8Kb)的缓冲区
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
BufferedInputStream
和BufferedOutputStream
BufferedReader
和BufferedWriter
通过上面节点流的知识,我们大概知道缓冲流和文件流是一一对应的
对于缓冲流的认识:
- 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
- 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中 读取下一个8192个字节数组
- 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush()可以强制将缓冲区的内容全部写入输出流
- 关闭流的顺序和打开流的顺序相反(即关闭流从下往上关闭)。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
- flush()方法的使用:手动将buffer(缓冲区)中内容写入文件
- 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷 新缓冲区,关闭后不能再写出
缓冲流处理节点流图示:
接下来以一个综合的题目来认识缓冲流
题目:使用节点流:FileInputStream、FileOutputStream和缓冲流: BufferedInputStream、BufferedOutputStream实现非文本文件(图片/视频)。使用节点流:FileReader、FileWriter和缓冲流:BufferedReader、BufferedWriter实现文本文件的复制。
package com.atguigu.java;
import org.junit.Test;
import java.io.*;
/**
* 处理流之一:缓冲流的使用
*
* 1.缓冲流:
* BufferedInputStream
* BufferedOutputStream
* BufferedReader
* BufferedWriter
*
* 2.作用:提供流的读取、写入的速度提高读写速度的原因:内部提供了一个缓冲区
* 3. 处理流,就是“套接”在已有的流的基础上。
*
*/
public class BufferedTest {
/*
实现非文本文件的复制(字节流)
*/
@Test
public void copyNoTextFile(String srcFile, String destFile) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//这里我用的是IDEA,创建模块的方式,在junit的Test方法下,相对路径是从Model开始的,而不是工程目录
//而Eclipse在junit的Test方法下,无论是一个工程还是一个模块,相对路径都是从工程目录下开始的
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
//刷新缓冲区的作用是将buffer的内容手动写到文件
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。所以我们只需关闭外层处理流
// fos.close();
// fis.close();
}
}
/*
*实现文本文件复制的方法(字符流)
*/
public void copyTextFile(String srcPath,String destPath){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File(srcPath)));
bw = new BufferedWriter(new FileWriter(new File(destPath)));
//读写操作
//方式一:使用char[]数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
// bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//非文本文件复制
@Test
public void testcopyNoTextFile(){
String srcPath = "d:\\view\\视频.avi";
String destPath = "d:\\view\\视频-copy.avi";
copyNoTextFile(srcPath,destPath);
}
//文本文件的复制
@Test
public void testcopyTextFile(){
String srcPath = "d:\\text\\java.txt";
String destPath = "d:\\txt\\java-copy.txt"
copyTextFile(srcPath, destPath);
}
转换流(处理流的一种)
Java API提供了两个转换流
- InputStreamReader:将InputStream转换为Reader
- OutputStreamWriter:将Writer转换为OutputStream
字节流中的数据都是字符时,转成字符流操作更高效
很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能
解码:将 字节流
转换为 字符流
编码:将 字符流
转换为 字节流
通俗理解就是,解码就是解开密码,解开我们看不懂的东西,字节我们看不懂,字符我们看得懂,那么解码就是将字节流转换为字符流。编码就是反过来
InputStreamReader
- 实现将
字节的输入流
按指定字符集转换为字符的输入流
- 需要和InputStream“套接”
构造器:
- public InputStreamReader(InputStream in)
- public InputSreamReader(InputStream in,String charsetName) :charsetName是被转换文件存储的字符编码,如果被转换文件的编码格式为UTF-8,那么charsetName就要设置为UTF-8,即用UTF-8的编码方式将文件转换,如果不一致会导致转换出来的文件出现乱码(默认值为系统默认编码,开发环境下默认值为开发工具指定的编码,例如IDEA设定默认编码为UTF-8)
OutputStreamWriter
- 实现将
字符的输出流
按指定字符集转换为字节的输出流
- 需要和OutputStream“套接”
构造器:
- public OutputStreamWriter(OutputStream out)
- public OutputSreamWriter(OutputStream out,String charsetName):charsetName是转换后文件存储的字符编码
可以用下图来理解
接下来用例题来使用转换流来实现文件的读入和写出
package com.atguigu.java;
import org.junit.Test;
import java.io.*;
/**
* 处理流之二:转换流的使用
* 1.转换流:属于字符流
* InputStreamReader:将一个字节的输入流转换为字符的输入流
* OutputStreamWriter:将一个字符的输出流转换为字节的输出流
*
* 4.字符集
* ASCII:美国标准信息交换码。
* 用一个字节的7位可以表示。
* ISO8859-1:拉丁码表。欧洲码表
* 用一个字节的8位表示。
* GB2312:中国的中文编码表。最多两个字节编码所有字符
* GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
* Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
* UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
*
*/
public class InputStreamReaderTest {
//综合使用InputStreamReader和OutputStreamWriter
@Test
public void test2() throws Exception {
try {
//1.造文件、造流
File file1 = new File("Java_utf-8.txt");
File file2 = new File("Java_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
//2.读写过程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.关闭资源
if(isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
对象流
ObjectInputStream 和 和OjbectOutputSteam
- 对象流和数据流(DataInputStream、DataOutputStream)在存储和读取基本数据类型上功能是一样的,不过对象流能够存储和读取对象,而数据流不能存储和读取对象
- 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来
序列化:用ObjectOutputStream
类保存
基本类型数据或对象的机制 (持久化起来,应用于网络传输)
反序列化:用ObjectInputStream
类读取
基本类型数据或对象的机制
通俗来讲,序列化即是把对象从内存中保存到数据源中(硬盘,数据库) ,而反序列化就是将持久化的数据重新读取到内存中
- ObjectOutputStream和ObjectInputStream不能序列化
static
和transient
修 饰的成员变量
对象序列化的方式
对象序列化的方式主要有两个,实现以下类
Serializable
(常用)Externalizable
我们比较常用的是实现Serializable类,以下将一些关于实现Serializable类需要注意的细节
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量
private static final long serialVersionUID = 12039585L;
(12039585L可以随便写一个*******L)
建议在每个需要序列化的类中都要写上这个静态变量,这个静态变量可以理解为Java对序列化对象的一个标识符。虽然没有写的话,Java会隐式的自动创建,但是有一个细节需要注意,用Java自动创建隐式标识符的类对象在序列化后,如果你对这个自动创建隐式标识符的类进行了修改(修改成员变量,添加新内容等),此时,Java也会对这个自动创建的隐式标识符进行修改,在反序列化的时候会因为找不到原先的标识符而反序列化失败报错
强调:如果某个类需要序列化,必须保证其内部所有属性也是可序列化的(基本数据类型和String、Map等都是实现了序列化的,需要注意的是在类的内部引用别的类,那么别的类也是必须可序列化的)
序列化的机制
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在硬盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
使用对象流序列化和反序列化自定义对象
首先自定义一个实体类
import java.io.Serializable;
/**
* Person需要满足如下的要求,方可序列化
* 1.需要实现接口:Serializable
* 2.当前类提供一个全局常量:serialVersionUID
* 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性
* 也必须是可序列化的。(默认情况下,基本数据类型可序列化)
*/
public class Person implements Serializable{
public static final long serialVersionUID = 475463534532L;
private String name;
private int age;
private int id;
public Person(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
}
对Person序列化和反序列化
package com.atguigu.java;
import org.junit.Test;
import java.io.*;
public class ObjectInputOutputStreamTest {
/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
//将对象的数据保存在object.dat文件中
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new Person("张学良",23,1001);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream来实现
*/
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Person p = (Person) ois.readObject();
System.out.println(p);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}