目录
一 I/O流
1 I/O流概述
| 定义: | I/O(Input/Output)流,即输入/输出流,是Java中实现输入/输出的基础,它可以方便地实现数据的输入/输出操作 | | ---------- | ------------------------------------------------------------ |
| Java中的I/O流主要定义在java.io包中,该包下定义了很多类,其中有4个类为流的顶级类,分别为InputStream和OutputStream,Reader和Writer。 | | ------------------------------------------------------------ |
2 I/O流分类
根据操作数据单位: | 字节流和字符流 |
---|---|
根据数据流向: | 输入流和输出流 |
注意:
InputStream和OutPutStream是字节流,而Reader和Writer是字符流; |
---|
InputStream和Reader是输入流,而OutputStream和Writer是输出流; |
4个顶级类都是抽象类,并且是所有流类型的父类 |
3 字节流
(1)字节流概述
定义: | 在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的,I/O流中针对字节的输入/输出提供了一系列的流,统称为字节流。 |
---|---|
说明: | 字节流是程序中最常用的流 在JDK中,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自OutputStream。 |
注意:
| InputStream被看成一个输入管道,OutputStream被看成一个输出管道,数据通过InputStream从源设备输入到程序,通过OutputStream从程序输出到目标设备,从而实现数据的传输。 | | ------------------------------------------------------------ |
(2)字节流的继承体系
字节输入流:
字节输出流:
(3)InputStream常用方法
方法声明 | 功能描述 |
---|---|
int read() | 从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数。当没有可用字节时,将返回-1 |
int read(byte[] b) | 从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节的数目 |
int read(byte[] b,int off,int len) | 从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目 |
void close() | 关闭此输入流并释放与该流关联的所有系统资源 |
注意:
前三个read()方法都是用来读数据的,分按字节读取和按字节数组读取。 |
---|
进行I/O流操作时,应该调用close()方法关闭流,从而释放当前I/O流所占的系统资源。 |
(4)FileInputStream
FileInputStream是InputStream的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。 |
---|
从文件读取数据是重复的操作,因此需要通过循环语句来实现数据的持续读取。 |
代码:
//创建字节输入流通道 public static void main(String[] args) throws IOException { FileInputStream in = new FileInputStream("D:\\a.txt"); int content = 0;//定义一个变量,保存读取内容 while((content=in.read())!=-1){//判断是否读取完 System.out.println((char)content);//打印读取内容 } in.close();//释放资源 }
结果:
注意:
| 在读取文件数据时,必须保证文件在相应目录存在并且是可读的,否则会抛出FileNotFoundException。 | | ------------------------------------------------------------ |
(5)OutputStream常用方法
方法声明 | 功能描述 |
---|---|
void write(int b) | 向输出流写入一个字节 |
void write(byte[] b) | 把参数b指定的字节数组的所有字节写到输出流 |
void write(byte[] b,int off,int len) | 将指定byte数组中从偏移量off开始的len个字节写入输出流 |
void flush() | 刷新此输出流并强制写出所有缓冲的输出字节 |
void close() | 关闭此输出流并释放与此流相关的所有系统资源 |
注意:
前三个write()方法都是用来写数据的,分按字节写入和按字节数组写入。 |
---|
flush()方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。 |
close()方法是用来关闭流并释放与当前IO流相关的系统资源。 |
(6)FileOutputStream
| 更多操作FileOutputStream是OutputStream的子类,它是操作文件的字节输出流,专门用于向文件中写入数据。 | | ------------------------------------------------------------ |
代码1:
//创建字节输出流通道 FileOutputStream out = new FileOutputStream("D:\\b.txt"); //写数据 out.write("hello world".getBytes()); //释放资源 out.close();
结果:
注意:
| 通过FileOutputStream向一个已经存在的文件中写入数据,该文件中的数据首先会被清空,再写入新的数据。若希望在已存在的文件内容之后追加新内容,则可使用构造函数FileOutputStream(String fileName, boolean append)来创建文件输出流对象,并把append参数的值设置为true。 | | ------------------------------------------------------------ |
代码2:
//创建字节输出流通道 FileOutputStream out = new FileOutputStream("D:\\b.txt",true); //写数据 out.write("hello world".getBytes()); out.write("天气不错呀!".getBytes()); //释放资源 out.close();
结果:
(7)拷贝图片
I/O流通常都是成对出现的,即输入流和输出流一起使用。 |
---|
例如文件的拷贝就需要通过输入流来读取源文件中的数据,并通过输出流将数据写入新文件。 |
代码:
FileInputStream in = null; FileOutputStream out = null; try{ //和要拷贝的图片建立字节输入流通道 in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\a.png"); //和拷贝后的图片建立字节输出流通道 out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\a_copy.png"); //定义变量,保存每次读取内容 int content = 0; //边读边写 while((content=in.read())!=-1){ out.write(content); } }catch (Exception e){ e.printStackTrace(); }finally { //释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } try { in.close(); } catch (IOException e) { e.printStackTrace(); } }
结果:
注意:
| 注意: | I/O流在进行数据读写操作时会出现异常,为了保证I/O流的close()方法一定执行来释放占用的系统资源,通常会将关闭流的操作写在inally代码块中。 | | ---------- | ------------------------------------------------------------ |
(8)字节流缓冲区
| 假设从北京运送快递到上海,如果有一万件快递,一件一件的运送就必须运输一万次,这样的效率显然非常低。为了减少运输次数,可以先把一批快递装在一个车厢中,这样就可以成批的运送快递,这时的车厢就相当于一个临时缓冲区。 | | ------------------------------------------------------------ |
| 同理:在文件拷贝过程中,通过以字节形式逐个拷贝,效率也非常低。为此,可以定义一个字节数组缓冲区,在拷贝文件时,就可以一次性读取多个字节的数据。 | | ------------------------------------------------------------ |
| 程序中的缓冲区就是一块内存,该内存主要用于存放暂时输入/输出的数据,由于使用缓冲区减少了对文件的操作次数,所以可以提高读写数据的效率 | | ------------------------------------------------------------ |
代码1:
//创建字节输入流通道 FileInputStream in = new FileInputStream("D:\\a.txt"); byte[] buf = new byte[1024];//定义一个变量,保存每次读取内容(缓冲数组) int length = 0;//定义一个变量,保存每次读取的长度 while((length=in.read(buf))!=-1){//判断是否读取完 String content = new String(buf,0,length); System.out.println(content);//打印读取内容 } in.close();//释放资源
结果:
代码2:
FileInputStream in = null; FileOutputStream out = null; try{ //和要拷贝的图片建立字节输入流通道 in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\a.png"); //和拷贝后的图片建立字节输出流通道 out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\a_copy.png"); //定义缓存数组,保存读取内容 byte[] buf = new byte[1024]; //定义变量,保存每次读取的长度 int length = 0; //边读边写 while((length=in.read(buf))!=-1){ out.write(buf,0,length); } }catch (Exception e){ e.printStackTrace(); }finally { //释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } try { in.close(); } catch (IOException e) { e.printStackTrace(); } }
(9)字节缓冲流
除了定义字节缓冲区来提高文件拷贝效率外,IO中还提供了两个字节缓冲流来提高文件拷贝效率:BufferedInputStream和BufferedOutputStream。 |
---|
它们的构造方法中分别接收InputStream和OutputStream类型的参数作为对象,在读写数据时提供缓冲功能。 |
缓冲流示意图:
代码:
FileInputStream in = null; FileOutputStream out = null; BufferedInputStream bufIn = null; BufferedOutputStream bufOut = null; try{ //和要拷贝的图片建立字节输入流通道 in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\a.png"); //创建缓冲输入流 bufIn = new BufferedInputStream(in); //和拷贝后的图片建立字节输出流通道 out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\a_copy.png"); //创建缓存输出流 bufOut = new BufferedOutputStream(out); //定义缓存数组,保存读取内容 byte[] buf = new byte[1024]; //定义变量,保存每次读取的长度 int length = 0; //边读边写 while((length=bufIn.read(buf))!=-1){ bufOut.write(buf,0,length); } }catch (Exception e){ e.printStackTrace(); }finally { //释放资源 try { bufOut.close(); } catch (IOException e) { e.printStackTrace(); } try { bufIn.close(); } catch (IOException e) { e.printStackTrace(); } }
4 字符流
| 说明: | 除了字节流,JDK还提供了用于实现字符操作的字符流,同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。 | | ---------- | ------------------------------------------------------------ |
(1)字符流继承体系
(2)FileReader
| 想从文件中直接读取字符便可以使用字符输入流FileReader,通过此流可以从文件中读取一个或一组字符。 | | ------------------------------------------------------------ |
代码:
//创建输入字符流对象 FileReader fileReader = new FileReader("d:\\a.txt"); int len = 0;//定义变量,保存读取数据 while ((len = fileReader.read()) != -1) {//判断数据是否读完 System.out.print((char)len);//打印数据 } fileReader.close();//是否资源
结果:
(3)FileWriter
| 要向文件中写入字符就需要使用FileWriter类,该类是Writer的一个子类 | | ------------------------------------------------------------ |
代码:
//创建字符输出流对象 FileWriter fileWriter = new FileWriter("d:\\b.txt"); fileWriter.write("你好!");//写数据 fileWriter.close();//释放资源
结果:
注意:
| 使用字符流向文件追加写入数据,需要调用重载的构造方法 | | ---------------------------------------------------- |
代码:
//创建字符输出流对象 FileWriter fileWriter = new FileWriter("d:\\b.txt",true); fileWriter.write("天气不错呀");//写数据 fileWriter.close();//释放资源
结果:
(4)拷贝文档
代码:
//建立输入通道 FileReader reader = new FileReader("d:\\admin.txt"); //建立输出通道 FileWriter writer = new FileWriter("d:\\admin_copy.txt"); int content = 0;//定义变量保存读取内容 while( (content= reader.read())!=-1){//读取数据 writer.write(content);//写数据 } //释放资源 writer.close(); reader.close();
结果:
(5)字符流缓冲区
使用字符流逐个字符的读写文件也需要频繁的操作文件,效率仍非常低。 |
---|
为此,同字节流操作文件一样,也可以使用提供的字符流缓冲区(类似于字节流缓冲区)和字符缓冲流(类似于字节缓冲流)进行读写操作,来提高执行效率。 |
| 字符流缓冲区需要定义一个字符数组作为字符缓冲区,通过操作字符缓冲区来提高文件读写效率。 | | ------------------------------------------------------------ |
代码:
//创建字符输入流对象 FileReader fileReader = new FileReader("d:\\admin.txt"); //定义字符缓冲数组 char[] buf = new char[1024]; int len = 0;//定义变量,保存读取长度 while ((len = fileReader.read(buf)) != -1) {//判断数据是否读完 System.out.print(new String(buf,0,len));//打印数据 } fileReader.close();//是否资源
结果:
代码2:
//建立输入通道 FileReader reader = new FileReader("d:\\admin.txt"); //建立输出通道 FileWriter writer = new FileWriter("d:\\admin_copy.txt"); //定义缓存数组 char[] buf = new char[1024]; int len = 0;//定义变量保存读取长度 while( (len= reader.read(buf))!=-1){//读取数据 writer.write(buf,0,len);//写数据 } //释放资源 writer.close(); reader.close();
(6)字符缓冲流
BufferedReader | String readLine() 读一行文字(不会读取换行)。 |
---|---|
BufferedWriter | void newLine() 写一行行分隔符。 |
注意:
它们的构造方法中分别接收Reader和Writer类型的参数作为对象,在读写数据时提供缓冲功能。 |
---|
BufferedReader(Reader in) |
BufferedWriter(Writer out) |
代码1:
//创建字符输入流对象 FileReader fileReader = new FileReader("d:\\admin.txt"); //创建字符输入缓冲流 BufferedReader bufferedReader = new BufferedReader(fileReader); String content = "";//定义变量,保存读取内容 while ((content=bufferedReader.readLine())!=null) {//判断数据是否读完 System.out.print(content+"\r\n");//打印数据 } bufferedReader.close();//释放资源
代码2:
//建立输入通道 FileReader reader = new FileReader("d:\\admin.txt"); //建立字符输入缓冲流 BufferedReader bufferedReader = new BufferedReader(reader); //建立输出通道 FileWriter writer = new FileWriter("d:\\admin_copy.txt"); //建立字符输出缓冲流 BufferedWriter bufferedWriter = new BufferedWriter(writer); String line = null;//定义变量保存读取内容 while( (line = bufferedReader.readLine())!=null){//读取数据 writer.write(line+"\r\n");//写数据 } //释放资源 bufferedWriter.close(); bufferedReader.close();
5 转换流
在JDK中,提供了两个类用于实现将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter。 |
---|
InputStreamReader是Reader的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符。 |
OutputStreamWriter是Writer的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符。 |
转换流操作文件示意图
代码:
//创建字节输入流 FileInputStream in = new FileInputStream("d:\\admin.txt"); //将字节流转换成字符流 InputStreamReader reader = new InputStreamReader(in); //创建缓冲流 BufferedReader bufferedReader = new BufferedReader(reader); //使用缓冲流读数据 String line = null; while((line=bufferedReader.readLine())!=null){ System.out.print(line+"\r\n");//打印读取内容 } //释放资源 bufferedReader.close();
结果:
二 File类
File类用于封装一个路径,这个路径可以是从系统盘符开始的绝对路径,也可以是相对于当前目录而言的相对路径。 |
---|
封装的路径可以指向一个文件,也可以指向一个目录,在File类中提供了针对这些文件或目录的一些常规操作 |
1File类常用构造方法
方法声明 | 功能描述 |
---|---|
File(String pathname) | 通过指定的一个字符串类型的文件路径来创建一个新的File对象 |
File(String parent,String child) | 根据指定的一个字符串类型的父路径和一个字符串类型的子路径(包括文件名称)创建一个File对象 |
File(File parent,String child) | 根据指定的File类的父路径和字符串类型的子路径(包括文件名称)创建一个File对象 |
例如:
File file1 = new File("C:\\Users\\Administrator\\Desktop\\a.txt"); File file2 = new File("C:\\Users\\Administrator\\Desktop","a.txt"); File file3 = new File(new File("C:\\Users\\Administrator\\Desktop"),"a.txt");
2File类的常用方法
方法声明 | 功能描述 |
---|---|
boolean exists() | 判断File对象对应的文件或目录是否存在,若存在则返回ture,否则返回false |
boolean delete() | 删除File对象对应的文件或目录,若成功删除则返回true,否则返回false |
boolean createNewFile() | 当File对象对应的文件不存在时,该方法将新建一个此File对象所指定的新文件,若创建成功则返回true,否则返回false |
String getName() | 返回File对象表示的文件或文件夹的名称 |
String getPath() | 返回File对象对应的路径 |
String getAbsolutePath() | 返回File对象对应的绝对路径(在Unix/Linux等系统上,如果路径是以正斜线/开始,则这个路径是绝对路径;在Windows等系统上,如果路径是从盘符开始,则这个路径是绝对路径) |
String getParent() | 返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录) |
boolean canRead() | 判断File对象对应的文件或目录是否可读,若可读则返回true,反之返回false |
boolean canWrite() | 判断File对象对应的文件或目录是否可写,若可写则返回true,反之返回false |
boolean isFile() | 判断File对象对应的是否是文件(不是目录),若是文件则返回true,反之返回false |
方法声明 | 功能描述 |
---|---|
boolean isFile() | 判断File对象对应的是否是文件(不是目录),若是文件则返回true,反之返回false |
boolean isDirectory() | 判断File对象对应的是否是目录(不是文件),若是目录则返回true,反之返回false |
boolean isAbsolute() | 判断File对象对应的文件或目录是否是绝对路径 |
long lastModified() | 返回1970年1月1日0时0分0秒到文件最后修改时间的毫秒值 |
long length() | 返回文件内容的长度 |
String[] list() | 列出指定目录的全部内容,只是列出名称 |
String[] list(FilenameFilter filter) | 接收一个FilenameFilter参数,通过该参数可以只列出符合条件的文件 |
File[] listFiles() | 返回一个包含了File对象所有子文件和子目录的File数组 |
例如1:
//判断 File file1 = new File("C:\\Users\\Administrator\\Desktop\\a.txt"); System.out.println("file存在吗?"+file1.exists()); System.out.println("file是文件夹吗?"+file1.isDirectory()); System.out.println("file是文件吗?"+file1.isFile()); //获取 System.out.println("file的名字:"+file1.getName()); System.out.println("file的路径是:"+file1.getPath()); System.out.println("file的父目录是:"+file1.getParent()); System.out.println("file上次修改时间是:"+file1.lastModified());
结果:
例如2:
//创建文件 File file2 = new File("d:\\abc.txt"); if(!file2.exists()){ file2.createNewFile(); } //删除文件(如果是文件夹必须为空才能删除) file2.delete();
例如3:
//获取目录下所有内容 File file = new File("C:\\Users\\Administrator\\Desktop\\a"); if(file.isDirectory()){ String[] list = file.list();//获取的是所有内容的名字 System.out.println(Arrays.toString(list)); File[] files = file.listFiles();//获取的是所有内容的File对象 System.out.println(Arrays.toString(files)); }
结果:
例如4:
软件包 java.io Interface FilenameFilter boolean accept•(File dir, String name)测试指定文件是否应包含在文件列表中。 参数 dir - 找到该文件的目录。 name - 文件的名称。 结果 true当且仅当名称应包含在文件列表中时; 否则为false 。
//创建过滤器 public class MyFileNameFilter implements FilenameFilter { @Override public boolean accept(File dir, String name) { if(name.endsWith("java"))return true; return false; } } File file = new File("C:\\Users\\Administrator\\Desktop\\a"); if(file.isDirectory()){ MyFileNameFilter filter = new MyFileNameFilter(); String[] list = file.list(filter);//获取过滤后的内容 File[] files = file.listFiles(filter);//获取过滤后的内容 System.out.println(Arrays.toString(list)); System.out.println(Arrays.toString(files)); }
结果:
三 对象的序列化
问题: 程序在运行过程中,可能需要将一些数据永久的保存到磁盘上,而数据在Java中都是保存在对象当中的。那么我们要怎样将对象中的数据保存到磁盘上呢? 答: 这时就需要使用Java中的对象序列化。
1 什么是对象的序列化
定义:对象的序列化(Serializable)是指将一个Java对象转换成一个I/O流中字节序列的过程 目的:为了将对象保存到磁盘中,或允许在网络中直接传输对象。 说明: 对象序列化可以使内存中的Java对象转换成与平台无关的二进制流; 既可以将这种二进制流持久地保存在磁盘上,又可以通过网络将这种二进制流传输到另一个网络节点; 其他程序在获得了这种二进制流后,还可以将它恢复成原来的Java对象; 这种将I/O流中的字节序列恢复为Java对象的过程被称之为反序列化(Deserialize)。 注意: 想让某个对象支持序列化,那么这个对象所在的类必须是可序列化的。 在Java中,可序列化的类必须实现Serializable接口
2 ObjectOutputStream对象输出流
public class Person implements Serializable { // 为该类指定一个serialVersionUID变量值 private static final long serialVersionUID = 1L; //声明变量 private int id; private String name; private int age; // 此处省略各属性的getter和setter方法 ... }
//创建字节输出流对象 OutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\person.txt"); //创建对象输出流 ObjectOutputStream objOut = new ObjectOutputStream(out); //输出对象 Person person = new Person(); person.setId(1); person.setName("小明"); person.setAge(11); objOut.writeObject(person);//对象的序列化 //释放资源 objOut.close();
3 ObjectInputStream对象输入流
//创建字节输入流对象 FileInputStream in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\person.txt"); //创建对象输入流 ObjectInputStream objIn = new ObjectInputStream(in); //读对象 Person person = (Person) objIn.readObject();//对象的反序列化 //释放资源 objIn.close(); System.out.println(person);
结果:
注意:
对象的反序列化不会调用构造函数 |
---|
serialVersionUID适用于Java的序列化机制。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就可以进行反序列化,否则就会出现异常。因此,为了在反序列化时确保序列化版本的兼容性,最好在每一个要序列化的类中加入private static final long serialVersionUID的变量值,具体数值可自定义(默认是1L,系统还可以根据类名、接口名、成员方法及属性等生成的一个64位的哈希字段)。这样,某个对象被序列化之后,即使它所对应的类被修改了,该对象也依然可以被正确的反序列化。 |
四 NIO
1 什么是NIO
定义: 从JDK 1.4开始,Java提供了一系列改进的用于处理输入/输出的新功能,这些新功能被称之为NIO(New I/O)。 说明: NIO采用内存映射文件的方式来处理输入/输出,它将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。 在NIO中,使用的是Channel(通道)和Buffer(缓冲器)。 数据总是从通道读入缓冲器,或从缓冲器写入通道。
2 NIO相关包
NIO相关包: java.nio:主要包含各种与Buffer相关的类。 java.nio.channels:主要包含与Channel和Selector(多线程相关选择器)相关的类。 java.nio.channels.spi:主要包含与Channel相关的服务提供者编程接口。 java.nio.charset:主要包含与字符集相关的类。 java.nio.charset.spi:主要包含与字符集相关的服务提供者编程接口。