IO流简介
一、IO流概念
1. IO流:
要把数据持久化存储,就需要把内存中的数据存储到内存以外的其他持久化设备(硬盘、光盘、U盘等)上。当需要把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操作。
当把持久设备上的数据读取到内存中的这个动作称为输入(读)Input操作。因此我们把这种输入和输出动作称为IO操作,Java对数据的操作是通过流的方式,IO流用来处理设备之间的数据传输:上传文件和下载文件。
2. Stream(流):
流是一个抽象、动态的概念,是一连串连续动态的数据集合。
流是一组有序的数据序列,根据操作的类型,可分为输入流和输出流两种。为进行数据的输入/输出操作,Java中把不同的输入/输出源(键盘、文件、网络连接,压缩包,其他数据源等)抽象表述为“流”(Stream)Stream是从起源(source)到接收(sink)的有序数据,java.io包中提供了很多类和接口来实现输入/输出功能。所有输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类;而所有输出流都是抽象类OutputStream(字节输出流)或抽象类Writer(字符输出流)的子类。
对于输入流而言,数据源就像水箱,流就像水管中流动着的水流,程序就是我们的最终用户。我们通过流将数据源的数据输送到程序当中。
对于输出流而言,目标数据源就是目的地,我们通过流将程序中的数据输送到目的地数据源中。
3. 输入流:
用于读文件的流,InputStream类是字节输入流的抽象类,是所有字节输入流的父类。Reader类是字符输入流的抽象类,是所有字符输入流的父类。
4. 输出流:
用于写文件的流,OutputStream类是字节输出流的抽象类,是所有字节输出流的父类。Writer类是字符输出流的抽象类,是所有字符输出流的父类。
二、IO流的分类
1. 根据处理 数据类型/数据单位 的不同分为:字节流和字符流
-
字节流:以字节为单位读写文件,操作二进制文件,当传输的资源文件有中文时,就会出现乱码。
-
字符流:以字符为单位读写文件;操作文本文件。
2. 根据数据流向不同分为:输入流和输出流
-
此输入输出是相对于我们写的代码程序而言。
-
输入流:从别的地方(本地文件,网络上的资源等)获取资源 输入到 我们的程序中
-
从我们的程序中 输出到别的地方(本地文件), 将一个字符串保存到本地文件中,就需要使用输出流。
3. 根据功能不同分为:节点流和处理流
-
节点流:可以直接从或向一个特定的地方(节点)读写数据,例:FileInputStream、FileReader、DataInputStream等。
-
处理流:不直接连接到数据源或目的地,是处理流的流,是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据的读写,例:BufferedReader、BufferedInputStream等。处理流的构造方法总是要带一个其他的流对象做参数,一个流对象经过其他流的多次包装。处理流也叫包装流。
4. 4个基本的抽象流类型,所有的流都继承这四个
输入流 | 输出流 | |
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
- InputStream:字节输入流:
- OutputStream:字节输出流:
- Reader:字符输入流:
- 字符输出流:
所有的流:
常用的几个:
怎么选择流:
对于上面的一堆各种流,对于初学者可能在用的时候不知道选择哪个,什么时候用哪个更合适,下面的步骤可以帮助你选择合适的流:
-
首先自己要知道是选择输入流还是输出流,如果想从程序写东西到别的地方,就选择输出流,反之选择输入流。
-
然后考虑传输的数据,是选择使用字节流传输还是字符流(有中文就选择字符流了)
-
在前面的两步就可以选出一个合适的节点流了,比如字节输入流InputStream,如果想要在此基础上增强功能,那么就在处理流中选择一个合适的即可。
三、IO流特性
-
先进先出:最先写入输出流的数据最先被输入流读取到。
-
顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile可以通过流指针从文件的任意位置进行存取(输入输出)操作)
-
只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
四、IO流常用到的五类一接口
在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。
-
File(文件特征与管理):File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
-
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
-
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
-
Reader(文件格式操作):抽象类,基于字符的输入操作。
-
Writer(文件格式操作):抽象类,基于字符的输出操作。
-
Serializable(序列化):接口,是启用其序列化功能的接口,实现Serializable接口的类是可序列化的,Serializable接口是一个空接口,是一个标识接口。
五、File类
- 构造方法:
-
public File(String pathname):传递路径名:可以写到文件夹,可以写到一个文件。
-
public File(String parent,String child):传递字符串父路径,传递字符串子路径;好处:单独的操作父路径和子路径。
-
public File(File parent,String child):传递路径,传递file路径父路径,字符串子路径;好处:父路径是File类型,父路径可以直接调用File类方法。
- File类常用的成员方法:
创建功能:
-
public boolean createNewFile():在指定路径创建文件,如果文件已经存在,则不创建,返回false;输出流:对象一旦创建,如果文件存在,则会覆盖
-
public boolean mkdir():创建一级文件夹(目录)
-
public boolean mkdirs():创建多级文件夹
public class Demo01File {
public static void main(String[] args) throws IOException{
File file = new File("e:\\a");
boolean res = file.mkdir();//创建文件夹即目录
System.out.println(res);
File file2 = new File("e:\\a.text");
boolean res2 = file2.createNewFile();//创建的是文件
boolean mkdir = file2.mkdir();
System.out.println(res2);
File file3 = new File("e:\\a\\b\\c");
boolean res3 = file3.mkdirs();//创建的是多级目录
System.out.println(res3);
}
}
删除功能:public boolean delete():删除失败返回false。如果文件正在使用,则删除不了返回false。
public class Demo02File {
public static void main(String[] args) throws IOException{
//创建a目录和a.txt文件
File file = new File("e:\\a.txt");
if(file.exists()) {
file.delete();//删除目录的时候,如果目录旗下有东西,不会删除
}else {
file.mkdir();
}
File file2 = new File(file,"\\a.txt");
if(file2.exists()) {
file2.delete();
}else {
file2.createNewFile();
}
}
}
重命名功能:public boolean renameTo(File dest)
判断功能:
-
public boolean isDirectory():是否是一个目录,如果不存在,则始终为false。
-
public boolean isFile():是否是一个文件,如果不存在,则始终为false。
-
public boolean exists():判断文件是否存在。
-
public boolean isHidden():是否是一个隐藏的文件或是否是隐藏的目录。
基本获取功能:
-
public String getAbsolutePath():获取绝对路径。
-
public String getPath():获取路径。
-
public String getName():获取文件或文件夹的名称,不包含上级路径。
-
public long length():获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
-
public long lastModified():获取最后一次被修改的时间。
高级获取功能:
-
public String[] list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
-
public String[] list(FilenameFilter filter):返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
-
File[] listFiles() :返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
-
public File[] listFiles(FilenameFilter filter):返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
public class Demo03File {
public static void main(String[] args) {
File file = new File("d:\\MySQL");
// String[] list = file.list();
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".exe");
}
});
for(String s :list) {
System.out.println(s);
}
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f);
}
}
}
路径的分类:
-
绝对路径:就是从根目录开始一直到该目录搜索的全程的路径(带盘符)。
-
相对路径:相对于当前目录的路径,相对的是当前的项目(不带盘符)。
-
绝对路径是不变的,而相对路径是随用户工作目录的变化而变化。
-
抽象路径:用户界面和操作系统使用与系统相关的路径名字符串来命名文件和目录。此类呈现分层路径名的一个抽象的、与系统无关的视图。
-
注:当指定一个文件路径的时候,如果采用的是相对路径,默认的目录为项目的根目录
例题:递归打印所有子目录中的文件路径:
public class Demo04File {
public static void getFileAll(File file) {
File[] list = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
for (File s : list) {
if(s.isDirectory()) {//判断是否是目录
getFileAll(s);
}else {
System.out.println(s);
}
}
}
public static void main(String[] args) {
File file = new File("e://java");
Demo04File.getFileAll(file);
}
}
六、文件输入输出流:
- FileOutputSream(文件输出流)
构造方法:
-
FileOutputStream(File file):创建FileOutputStream流以写入数据到File对象所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象)。
-
FileOutputStream(String name):创建FileOutputStream流以写入数据到指定路径所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象)。
-
FileOutputStream(String name,boolean append):创建FileOutputStream流以写入数据到指定路径所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象), 如果第二个参数为true,则字节将写入文件的末尾而不是开头。
-
FileOutputStream(File file,boolean append):创建FileOutputStream流以写入数据到File对象表示的文件。 如果第二个参数为true,则字节将写入文件的末尾而不是开头。 创建一个新的FileDescriptor对象来表示此文件连接。其抛异常的规则与第一个构造函数一致。
字节流写数据的方式:
-
public void write(int b):写入一个字符。
-
public void write(byte[] b):写入一个字符数组。
-
public void write(byte[] b,int off,int len):写入字符数组的一部分。
-
public void write(String str):写入一个字符串。
-
public void write(String str,int off,int len):写入一个字符串的一部分。
-
flush():刷新流,还可以继续写数据。
-
close():关闭流,释放资源,但是在关闭之前会先刷新流,一旦关闭就不能在写数据。
字节流写数据常见问题:
- 数据写成功后,为什么要close()?
java建立在c++之上 c++中new出来的对du象都需要通过delet来释放 而java虚拟机的垃圾回收器回完成了这个工作,但是回收器只能清理内存中的东西 使用IO流关联到了内存与硬盘的链接 虚拟机没法搞定这个 只能继续依赖c++ 查看源码会发现有个close()的native方法 调用close时 就通过该方法使用了c++功能来关闭硬盘与内存的链接。
需要自己close的东西,一般都是用了虚拟机之外的资源,例如端口,显存,文件等,虚拟机无法通过垃圾回收释放这些资源,只能你显式调用close方法来释放。
许多情况下,如果在一些比较频繁的操作中,不对流进行关闭,很容易出现输入输出流经超越了JVM的边界,所以有时可能无法回收资源。
所以流操作的时候凡是跨出虚拟机边界的资源都要求程序员自己关闭,不要指望垃圾回收。
你读一个文件,忘记关闭了流,你在操作系统里对这个文件的写,删除等操作就会报错,告诉你这个文件被某个进程占用。
- 如何实现数据的换行?
-
windows:\r\n
-
linux:\n
-
Mac:\r
- 如何实现数据的追加写入?
用构造方法带第二个参数是true的情况即可 ,创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
FileOutputStream fos = new FileOutputStream("fos3.txt", true);
- FileInputStream(文件输入流)
构造方法:
-
FileOutputStream(String name)创建文件输出流以指定的名称写入文件。
-
FileOutputStream(File file)创建文件输出流以写入由指定的 File对象表示的文件。
FileInputStream成员方法:
-
public int read():一次读一个字符数据。
-
public int read(byte[] b):一次读一个字符数据数组。
例题:复制文件:
public class Demo08 {
public static void main(String[] args) {
//输出流:把内存中的东西写入文件中
//输入流:把磁盘上某个文件中的内容读到内存中
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream("e:\\第一天Java入门.xmid");
os = new FileOutputStream("e:\\a.xmind");
byte[] b = new byte[1024];
int len = 0;
int i =is.read();//一个字节一个字节的读,读取到的数据放入i中,汉字可能出现问题
while((len=is.read(b))!=-1) {//每次读取到的是一个字节数组,读取到的数据放到数组中,返回的是读取到的内容长度
String s = new String(b,0,len);
System.out.println(s);
os.write(b,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}