IO: 输入输出流Input Output
输入(Input)指的是:可以让程序从外部系统获得数据(核心含义是“读”,读取外部数据)。 常见的应用:
-
读取硬盘上的文件内容到程序。
-
读取网络上某个位置内容到程序。例如:浏览器中输入网址后,打开该网址对应的网页内容;下载网络上某个网址的文件。
-
读取数据库系统的数据到程序。
-
读取某些硬件系统数据到程序。
输出(Output)指的是:程序输出数据给外部系统从而可以操作外部系统(核心含义是“写”,将数据写出到外部系统)。
-
将数据写到硬盘中。
-
将数据写到数据库系统中。
-
将数据写到某些硬件系统中。
需要注意的是:
- 这里输入输出全部都是相对于程序而言的,input是指程序的输入,output是指程序的输出;
- 流是一种抽象(abstract),动态(dynamic)的概念,是一连串连续动态的数据集合。
数据源data source,提供数据的原始媒介。常见的数据源有:数据库、文件、其他程序、内存、网络连接、IO设备等。
数据源分为:源设备、目标设备。
-
源设备:为程序提供数据,一般对应输入流。
-
目标设备:程序数据的目的地,一般对应输出流。
IO核心类
类 | 说明 |
---|---|
File | 文件类 |
InputStream | 字节输入流 |
OutputStream | 字节输出流 |
Reader | 字符输入流 |
Writer | 字符输出流 |
Closeable | 关闭流接口 |
Flushable | 刷新流接口 |
Serializable | 序列化接口 |
输入流 :InputStream(字节)、Reader(字符)读进程序;
输出流: OutputStream(字节)、Writer(字符)程序输出。
节点流: 可以直接从数据源或目的地读写数据
处理流: 又叫包装流,不直接连接到数据源或目的地,是其他流的封装。主要目的是简化操作,提高性能。
节点流和处理流的关系:
- 节点流处于IO操作的第一线,所有操作必须通过它们进行;
- 处理流可以对其他流进行处理(提高效率或操作灵活性)
字节流: 按照字节读取数据(InputStream、OutputStream)
字符流: 按照字符读取数据(Reader、Writer),由于存在不同的文件编码方式(UTF-8、Unicode、GBK)不同,所以存在对字符进行高效操作的字符流对象。底层还是基于字节流进行操作,自动搜寻指定的码表。
File类
因为Java程序不能直接跟硬盘进行数据交互,Java程序只能先到Java虚拟机JVM,再到计算机操作系统,再有计算机操作系统来操作硬盘。而File类代表的是程序与硬盘建立的一个联系,所以File类表示文件和文件夹的路径,或文件/文件夹不存在,所以在程序实例化一个File类的一个对象时不一定会存在的。
下面跟着老师的思路看一下File类的API文档,打开JDK8的API,找到File类,
- 首先看这个类的继承体系,我们看File类的继承体系如下图:
可以看到File类也是包含于java.lang包,是IO接口的类,继承了Object类实现了Serializable,Comparable接口 - 看这个类的说明:有时间通读这个类的所有介绍,没有时间就看前面的简短的介绍:文件和目录路径名的抽象表示。注意,File类的对象不代表文件本身,文件是存在硬盘上的,File类的对象只是代表文件(夹)的路径,或者这个文件不存在。
- 向下看,File类的常量:
简要看一下这些常量,pathSeparator,与系统相关的路径分隔符,就是“E:(A)PostgraduateFlies\JavaLearing”这里面的 “\”,注意这是Windows系统下的路径分隔符,不同系统的路径分隔符可能不同。
- 然后再看构造器,可以看到File类的构造器如下:
当然,可能存在没有构造器的情况。下面分别说明:
- 有构造器:可以直接使用new进行实例化
- 没有构造器:并不是说真的没有构造器,而是这个类的构造器被private修饰了,所以又分为两种情况:
- 这个类是一个工具类,一般工具类里面的方法都是静态方法,不需要它的对象就能够使用它的方法,可以直接使用“类名+.”来调用它的方法,比如:Collections类的方法:Collections.shuffle(),Collections.sort(),Math类等等;
- 有构造方法,但是不能人为控制,是由某个静态方法返回这个对象,比如:Runtime类。
- 看类的方法,方法是被什么修饰符修饰(是用对象调用还是用类名调用),方法名和功能(见名知意),方法的形参及返回值,读源码。
路径的表示形式
下面看一个程序实例:
package myio;
import java.io.File;
/**测试Windows平台下的转义字符
*
* @author 发达的范
* @date 2020/11/27 17:34
*/
public class PathDemo01 {
public static void main(String[] args) {
//在Windows资源管理器下显示的路径的分隔符是“\”,粘贴到IDEA中自动变成“\\”,“\\”是可以被Linux环境识别的
String path = "E:\\(A)PostgraduateFlies\\JavaLearing\\JavaProjects\\TestingProjects\\src";
System.out.println(path);
System.out.println(File.separator);
//建议使用这种拼接路径名称的转义字符“/”,因为既可以在Windows环境下识别,又可以在Linux环境下识别
String path1="E:/(A)PostgraduateFlies/JavaLearing/JavaProjects/TestingProjects/src";
System.out.println(path1);
//这种方式不常用,因为比较麻烦,但是会见到,这两种方式在两种环境下都不会出问题
String path2 = "E:" + File.separator + "(A)PostgraduateFlies" + File.separator + "JavaLearing" + File.separator
+ "JavaProjects" + File.separator + "TestingProjects" + File.separator + "src";
System.out.println(path2);
}
}
运行结果:
下面看File类的几种实例化的形式:
package myio;
import java.io.File;
/**
* 测试File类的构造器
*
* @author 发达的范
* @date 2020/11/27 19:27
*/
public class FileDemo01 {
public static void main(String[] args) {
//第一种实例化File类的方法
String path = "E:/(A)PostgraduateFlies/JavaLearing/JavaPrinting/IO.vsdx";
File file = new File(path);
System.out.println(file.length());//35159bit=34.33KB
String path1 = "E:/(A)PostgraduateFlies/JavaLearing/JavaProjects";
File file1 = new File(path1);
//4096byte,这里打印的并不是这个文件夹内部所有文件的大小,而是硬盘存储这个文件夹路径的大小
System.out.println(file1.length());
//第二种实例化File类的方法
File file2 = new File("E:/(A)PostgraduateFlies/JavaLearing/JavaPrinting", "IO.vsdx");
File file3 = new File("E:/(A)PostgraduateFlies/JavaLearing", "JavaPrinting/IO.vsdx");
System.out.println(file2.length());
System.out.println(file3.length());
//第三种实例化File类的方法
File file4 = new File(new File("E:/(A)PostgraduateFlies/JavaLearing/JavaPrinting"), "IO.vsdx");
System.out.println(file4.length());
}
}
运行结果:
根据JDK8的API测试几种实例化File的方法,可以看到第一种方法是直接new了一个文件路径,第二种方法使用的是把文件路径拆分之后再进行实例化,对应的构造器如下:
这里的父类文件路径和子类文件路径没有绝对的区分(至少目前来说是这样的),只要能够完整且没有错误的拼接起来就可以创建成功;第三种方法使用与第二种方法类似,只不过使用的是一个File对象,即:
需要注意的是:File类的length()方法返回的是对象的大小,但是如果对象是文件,返回的是它的实际占用空间,单位是bit(注意bit比特不是字节,byte是字节,一个比特bit占8个字节byte);如果对象是文件夹,则返回值是这个文件夹路径的大小,经过实际测试,无论文件夹有多少层,只要是个文件夹,返回值就是4096,单位应该是KB(可能是磁盘最小扇区的大小)。
相对路径与绝对路径
直接看程序:
package myio;
import java.io.File;
/**测试File类的相对路径和绝对路径
* 前面加盘符是绝对路径
* 不加盘符是相对路径,相对的是工程所在文件夹
*
* @author 发达的范
* @date 2020/11/27 19:27
*/
public class FileDemo02 {
public static void main(String[] args) {
String path = "E:/(A)PostgraduateFlies/JavaLearing/JavaPrinting/IO.vsdx";//绝对路径
File file = new File(path);
System.out.println(file.getAbsolutePath());
System.out.println(file);
System.out.println(System.getProperty("user.dir"));//当前工程所在文件夹
file = new File("IO.vsdx");//不指定盘符,是相对路径
System.out.println(file.getAbsolutePath());//虽然这个文件不存在,但是仍可以正常输出
}
}
运行结果
从运行结果可以看到,绝对路径的输出就是原本指定的带盘符的路径,而相对路径输出的是把当前工程所在文件夹与相对路径的进行拼接然后形成相对路径,其实就是构建一个不存在的路径,File类的对象只是一个路径表示形式,可以表示不存在的文件路径。
问题来了,为什么要区别绝对路径和相对路径?
因为在实际项目中,往往是以工程所在文件夹为出发点来构建的文件路径,很少在服务器上指定绝对路径。
测试使用File类的常用方法
package myio;
import java.io.File;
/**测试File类的常用方法
* getName():获取文件名
* getPath():获取文件路径,给的是绝对路径获取到的就是绝对路径,给的是相对路径获取到的就是相对路径
* getAbsolutePath():获取绝对路径
* getParent():获取父路径,其实就是上一级路径,如果没有上一级路径则返回空
*
* @author 发达的范
* @date 2020/11/29 20:33
*/
public class FileDemo03 {
public static void main(String[] args) {
File file1 = new File("E:/(A)PostgraduateFlies/JavaLearning/JavaPrinting/IO.vsdx");
File file2 = new File("IO.vsdx");
System.out.println("文件1名:"+file1.getName());
System.out.println("文件2名:"+file2.getName());
System.out.println("文件1路径:"+file1.getPath());
System.out.println("文件2路径:"+file2.getPath());
System.out.println("绝对路径1:"+file1.getAbsolutePath());
System.out.println("绝对路径2:"+file2.getAbsolutePath());
System.out.println("父路径1:"+file1.getParent());
System.out.println("父路径2:"+file2.getParent());
}
}
运行结果:
package myio;
import java.io.File;
/**测试File类的常用方法
*文件状态:
* 1.是否存在:exists()
* 2.如果存在(如果文件不存在,那么下面两个的返回值都是false)
* 是不是文件:isFile()
* 是不是文件夹:isDirectory()
*
* @author 发达的范
* @date 2020/11/27 20:43
*/
public class FileDemo04 {
public static void main(String[] args) {
File file1 = new File("E:/(A)PostgraduateFlies/JavaLearning/JavaPrinting/IO.vsdx");
File file2 = new File("IO.vsdx");
File file3 = new File("001.jpg");
System.out.println("file1是否存在:"+file1.exists());
System.out.println("file2是否存在:"+file2.exists());
//判断时自动在当前工程所在文件夹寻找是否存在该文件(注意这里的路径实质上就是此文件的绝对路径)
System.out.println("file3是否存在:"+file3.exists());
System.out.println("file1是不是文件:"+file1.isFile());
System.out.println("file2是不是文件:"+file2.isFile());//这个文件不存在,所以也就不能判别它是不是文件
System.out.println("file3是不是文件:"+file3.isFile());
System.out.println("file3的绝对路径:"+file3.getAbsolutePath());
System.out.println("file1是不是文件夹:"+file1.isDirectory());
System.out.println("file2是不是文件夹:"+file2.isDirectory());
}
}
运行结果:
注意:,file1是一个绝对路径且存在的文件,此工程中没有文件:“IO.vsdx”,有文件:“001.jpg”。
上面的程序不难理解,我简要说明一下有些地方:
- 指定file的绝对路径之后,三个方法(exists(),isFile(),isDirectory())就很显然了;
- 工程中没有文件:“IO.vsdx”,所以isFile(),isDirectory()方法的返回值直接都是false;
- 此工程中有文件:“001.jpg”,注意:是在这个工程中,不是在src文件夹中,然后获取他的绝对路径(也即是完整路径)如上。
package myio;
import java.io.File;
import java.io.IOException;
/**测试createNewFile()方法:创建文件(不是文件夹)
* 如果不存在,就创建,如果已经存在就返回false
*
* @author 发达的范
* @date 2020/11/29 21:41
*/
public class FileDemo05 {
public static void main(String[] args) {
File file1 = new File("E:/(A)PostgraduateFlies/JavaLearning/JavaPrinting/IO.txt");
boolean newFile = false;
try {//这里的两处try...catch语句可以使用throws抛给父类,但是我偏不想用,就是想try try
newFile = file1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("file1是否创建成功:" + newFile);
File file2 = new File("E:/(A)PostgraduateFlies/JavaLearning/JavaPrinting/Java001");
try {
newFile = file2.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("file2是否创建成功:" + newFile);
boolean delete = file2.delete();
System.out.println("file2是否删除成功:" + delete);
}
}
运行结果:
注意:
-
第一次运行file1.createNewFile();语句的时候返回值是true,因为原来不存在这个文件,当这个文件已经创建成功之后,再次点击运行,它的返回值就变成了false,这就是createNewFile()方法的功能。
-
这里为了测试使用createNewFile()方法创建的是文件而不是文件夹,可以看到运行完 file2.createNewFile();之后,相应的文件夹里面出现了这个文件:
可见,它是一个没有后缀的文件,并不是文件夹;
-
delete()删除已经存在的文件,如果这个文件不存在直接返回false。
补充:
package myio;
import java.io.File;
import java.io.IOException;
/**
*
* @author 发达的范
* @date 2020/11/29 21:41
*/
public class FileDemo05 {
public static void main(String[] args) throws IOException {
File file = new File("con1");
boolean newFile=file.createNewFile();
System.out.println(newFile);
}
}
运行结果:
并且相应的文件夹里面也多了这个文件。
但是把文件名改成File file = new File(“con”);之后,运行发现是不能创建成功的,这是为什么?
因为con是Windows的操作系统的设备名,被禁止自己创建与系统设备名相同名称的对象。