在java中,IO是一种阻塞式IO(BIO),基于抽象类,将具体的IO操作放在子线程中进行。
核心有五个类:File、InputStream、OutputStream、Reader、Writer 和一个接口:Serializable
这其中InputStream、OutputStream、Reader、Writer 都是抽象类,要使用的话应该看具体对什么进行操作,比如说对文件操作,就要实例化一个文件字节或字符流,如:InputStream in = new FileInputStream();
一、File类
File类是唯一一个与文件本身操作(创建、删除、取得信息)有关的程序类,他既可以描述文件也可以描述文件夹
产生File对象
public File(String pathname):根据文件的绝对路径来产生file对象
public File(URI uri):根据网络产生file对象
1、常用操作方法
1)创建新文件:
public boolean createNewFile() throws IOException
2)判断文件是否存在
public boolean exists()
3)删除文件
public boolean delete()
文件分隔符:
File.separator :不同的操作系统有不同的分割符,比如Windows下是“\”,而Linux下是"/",为了程序的移植性更好,引入了 File.separator ,将需要写分隔符的地方换成 File.separator ,就能在不同的操作系统下产生不同的分隔符。
2、目录操作
1)取得父路径的File对象
public File getParentFile()
2)取得父路径的目录
public String getParent()
3)创建多级父路径(一次性创建多级不存在的父路径)
public boolean mkdirs()
public class Test {
public static void main(String[] args) {
File file = new File("C:"+File.separator+"Users"
+File.separator+"Eve"+File.separator+
"Desktop"+File.separator+
"java"+File.separator+"javaIO.java");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}else {
System.out.println("文件已经存在");
}
}
}
这段代码作用是在桌面创建一个java文件夹,在java文件夹中创建一个名为javaIO.java的文件。如果文件存在就输出文件已经存在。这段代码先进行对父路径的的判断,如果父路径不存在一定要使用mkdirs()创建父路径,否则createNewFile不会创建父路径,程序就会报错,提示找不到指定路径。
3、取得文件信息
1)判断File对象是否是文件
public boolean isFile()
2)判断File对象是否是文件夹
public boolean isDirectory()
3)取得文件大小---单位是字节
public long length()
4)取得上次修改时间
public long lastModified()
5)列出一个目录的全部组成
public File[] listFiles()
4、遍历指定目录下所有的文件(包括子目录)
了解了这些我们可以写一段代码来数一数电脑中有多少文件,当然这个时间是非常长的,我们可以选择一个文件夹来数一数有多少文件。大体思路是选择一个目录,判断他是目录还是文件夹,是文件计数器就+1,是目录的话就取得这个目录的全部组成,再通过递归来不断深入直到遍历完所有文件。
public class Test{
static int count = 0;
public static void main(String[] args) {
File file = new File("D:\\");
new Thread(()->{
long start = System.currentTimeMillis();
findAllFile(file);
long end = System.currentTimeMillis();
System.out.println("共计"+(end - start)+"毫秒");
System.out.println(count);
}).start();
}
public static void findAllFile(File file){
if(file.isFile()){
System.out.println(file);
count++;
}else {
if(file.exists() && file.isDirectory()){
File[] files = file.listFiles();
if (files!=null) {
for (File file1 :
files) {
findAllFile(file1);
}
}
}
}
}
}
有些人写这样的代码的时候可能会遇到空指针异常,原因在于有些系统的文件我们无权访问,所以在获取目录的全部组成的时候要判断一下取得的files是不是空的。
File[] files = file.listFiles();
if (files!=null) {
for (File file1 :
files) {
findAllFile(file1);
}
}
二、字节与字符流
java.io包中流分为两类:输入流与输出流
字节(byte)流是原生操作,无需转换,可以处理文本文件、图像、音乐、视频等资源,如InputStream、OutputStream
字节(char)流是字节流经过处理后的操作,只用于处理中文文本,如Reader、Writer
流模型的操作流程
1)取得终端对象
2)根据终端对象取得输入输出流
3)根据输入输出流进行数据读取与写入
4)关闭流(IO属于资源处理,所有的资源处理(IO操作,数据库操作,网络操作)在使用后一定要关闭)
2.1、字节输出流(OutputStream)
核心方法
Write()方法
public void write(byte b[]):将给定的字节数组全部输出
public void write(byte b[], int off, int len) throws IOException:将给定的字节数组以off位置开始输出len长度后停止输出
public abstract void write(int b) throws IOException: 输出单个字节
使用OutputStream输出数据时,若指定的文件不存在,FileOutputStream会自动创建文件(不包含创建目录),使用OutputStream输出内容时,默认是文件内容的覆盖操作.若要进行文件内容的追加,使用如下构造方法public FileOutputStream(String name, boolean append)
2.2、字节输入流(InputStream)
核心方法
public int read(byte b[]) throws IOException:将读取的内容放入字节数组中
这个方法有一个int类型的返回值,返回值代表的含义如下:
I.返回b.length
表明未读取的数据大于存放的缓冲区大小,返回字节数组大小
II.返回大于0的整数,此整数小于b.length
表明未读取的数据小于存放的缓冲区大小,返回剩余数据大小
III.返回-1 --- 终止标记
此时数据已经读取完毕
这个很好理解,假设把byte[] b当做一个勺子,把要读取的东西当做一碗饭,read方法就是我用勺子去碗里盛饭的过程,我盛了一勺饭,但是碗里还有很多饭,就返回我一勺取得饭的数量,即b.length,当我继续盛直到碗里剩下的饭不足一勺了,这时返回的是碗里剩的饭的数量,我把碗里的饭盛完了,就返回-1。
public class Test {
public static void main(String[] args) throws Exception{
//1.取得终端对象
File file = new File("C:"+File.separator+"Users"+File.separator+"Eve"+File.separator
+"Desktop"+File.separator+"test.txt");
//2.取得指定文件的输出流
OutputStream out = new FileOutputStream(file,true);
String str = "字节输出流";
//3.进行数据输出
out.write(str.getBytes());
//4.关闭流
out.close();
InputStream in = new FileInputStream(file);
byte[] b = new byte[1024];
while (in.read(b) != -1){
System.out.print(new String(b));
}
in.close();
}
}
这里我设置字节数组的大小是1024,大一点没有关系,但要是设置太小,比如说1,就会出现乱码的问题,这是由于数据丢失造成的,因为在UTF-8编码中,一个汉字不可能是一个字节可以存下的,由于UTF-8是变长的,一个汉字可能占3个或4个字节。
看一下输出,当字节数组大小是1024时,
当字节数组大小是1时
当字节数组大小是2时
当字节数组大小是3时
可见这几个汉字都是3个字节。
2.3、字符输出流Writer---适用于处理中文文本
核心方法
public void write(String str) throws IOException
字符流可以直接支持字符串的输出
字符流若未关闭,数据在缓冲区存放,不会输出到目标终端.要想将数据输出,要么将输出流关闭,要么使用flush()强制刷新缓冲区
2.4、字符输入流Reader
public int read(char cbuf[]) throws IOException
字符输入输出流使用方式与字节输入输出流的使用大同小异,只不过字符输入输出流支持字符串的输出,不用转换成字节数组,对中文的输入输出方便了一下。
三、转换流(字节流--->字符流)
字符流的具体子类大都是通过转换流将字节流转换为字符流(FileWriter基础转换流)
OutputStreamWriter(字节输出流--->字符输出流)
InputStreamReader(字节输入流--->字符输入流)
转换流只是一个中间的媒介,具体使用较少,比如我可以这样使用:
Writer writer = new OutputStreamWriter(new FileOutputStream(file));
与
Writer writer = new FileWriter(file);
的效果是一样的
四、内存流(以内存为终端的输入输出流)
字节内存流
ByteArrayInputStream,ByteArrayOutputStream
字符内存流
CharArrayReader,CharArrayWriter
将指定的字节数组的内容放到内存中
public ByteArrayInputStream(byte buf[])
public ByteArrayOutputStream()
通过内存流实现大小写转换
public class Test {
public static void main(String[] args) throws Exception{
String str = "HELLO WORLD";
InputStream in = new ByteArrayInputStream(str.getBytes());
OutputStream out = new ByteArrayOutputStream();
int temp = 0;
while ((temp = in.read())!=-1){
out.write(Character.toLowerCase(temp));
}
System.out.println(out);
}
}
五、打印流和装饰设计模式
字节打印流:PrintStream
字符打印流:PrintWriter
打印流是针对输出流的强化版本,不需要考虑OutputStream的write方法只能接受byte数组或int类型的数据,打印流几乎可以支持所有的数据类型。
PrintStream printStream = new PrintStream(file);
printStream.print("这是打印流");
printStream.close();
比如我们可以直接给通过PrintStream类的print方法进行写入一个字符串。
我们看一下print方法可以传入的数据类型
打印流的设计属于装饰设计模式---基于抽象类
特点:核心依然是某个类(OutputStream提供的writer())的功能。但是为了得到更好的操作效果,让其支持的功能更多一些,使用装饰类(PrintStream)
优点:很容易更换装饰类来达到不同的操作效果
缺点:由于装饰设计模式的引入,造成类结构的复杂