JAVA IO全面总结
简单描述:对于初学者来学,Java IO相对来说较为复杂,因为里面使用的父类与子类较多,所以我们要坚定一个学习规则:抽象类中定义的抽象方法会根据实例化子类的不同,也会完成不同的方法
如果要进行所有的文件以及文件内容的开发操作,应该使用java.io
包完成,而且整个java.io包实际上就是五个类和一个接口:
(插上一嘴多线程中是几个接口一个类)
接下来的知识,我们应该熟读加背诵给安排上🐶🐱 🐤 🐮
- 五个类:File ,InputStream,OutStream,Reader,Writer
- 一个接口:Serializable
我们再次加深多态性的概念以及特点,而对象多态类中最为核心的概念是:如果抽象类或者接口中的抽象方法被子类覆写了,那么实例化该对象,所调用的方法一定是被覆写的方法,即方法的名称以父类声明为标准,而具体的实现需要子类完成
1.0文件操作类:File
在这java.io包中,File类是唯一个的与文件本身操作有关的类,所谓的文件本身是指,文件的创建,删除,重命名,取得文件大小,和修改日期.
我们先研究File类的构造方法有哪些
方法 | 类型 | 描述 |
---|---|---|
public File(File parent, String child) | 构造 | 给出要操作文件的父路径和子文件名称 |
public File(String pathname) | 构造 | 给定一个要操作文件的完整路径 |
注意:第二种方法常用与安卓开发中,因为安卓常将许多文件临时存储到手机SD卡中
1.1使用File操作文件的方法
几个简单文件操作方法,创建文件,删除文件,判断文件路径是否存在
1.1.1 createNewFile()
public boolean createNewFile()
throws IOException
这里我们需要注意的是什么时候会抛出异常
- 如果目录不能访问
- 如果文件重名或者,文件名称错误
public class FileDemo {
public static void main(String[] args) throws IOException {
// 我们需要在F盘操作文件,资源管理器下访问文件可以直接使用
// F:\demo.txt,对就是":\"单横进行访问
// 但是在File对象参数里面需要对"\"进行转义
File file = new File("F:\\demo1.txt");
if (!file.exists()){
file.createNewFile();
System.out.println("创建该文件"+file.getName());
}
}
}
1.1.1.2 关于路径分隔符
我们知道在windos里路径分隔符常用\
,但是在linux下面使用/
,所有在File里面直接给出一个字段``
我们可以观察File里面有四个字段,它都表示啥意思那?
File file = new File("F:"+File.separatorChar+"demo1.txt");
字段 | 类型 | 描述 |
---|---|---|
public static final char separatorChar | char | 在 UNIX 系统上,此字段的值为 ‘/’;在 Microsoft Windows 系统上,它为 ‘\’。 |
public static final String separator | String | 同上 |
public static final char pathSeparatorChar | char | 在 UNIX 系统上,此字段为 ‘:’;在 Microsoft Windows 系统上,它为 ‘;’。 |
public static final String pathSeparator | String | 同上 |
1.1.1.3创建多级和单级目录
- 对于文件的路径操作,File的方法里提供了
getParentFile
,它判断路径是否存在,而且它返回File对象,可以直接再调用exits()
方法判断路径是否存在 - 对于创建文件的路径,FIle的方法提供了
mkdir
和mkdirs
两个方法,什么区别哪?一个只能创建一级目录,一个可以把你提供的所有都不存在的路径都给安排了
说了这么多,不如凑一凑
方法 | 类型 | 描述 |
---|---|---|
public String getParent() | String | 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。 |
public File getParentFile() | File | 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。 |
public boolean mkdir() | boolean | 创建此抽象路径名指定的目录。 |
public boolean mkdirs() | boolean | 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。注意,此操作失败时也可能已经成功地创建了一部分必需的父目录。 |
public class FileDemo2 {
public static void main(String[] args){
File file = new File("F:"+File.separator+"Test1"+File.separator+"test2"+File.separator+"test3"+File.separator+"test");
if (!file.getParentFile().exists()){
file.mkdirs();
System.out.println("创建:"+file.getParent()+"下的文件"+file.getName());
}else {
System.out.println("文件以及存在");
}
}
}
1.1.14 File文件常用类
我们常用的文件操作方法有哪些?很好回答的文件,我们需要查看文件名称,我们要分辨是文件还是文件夹,我们要看看文件夹有没有隐藏,看文件修改日期,我们有时候看文件字节大小,给文件重命名,取得文件夹下所有文件
方法 | 类型 | 描述 |
---|---|---|
public String getName() | 普通 | 返回由此抽象路径名表示的文件或目录的名称 |
public boolean isDirectory() | 普通 | 测试此抽象路径名表示的文件是否是一个目录。 |
public boolean isFile() | 普通 | 测试此抽象路径名表示的文件是否是一个标准文件。 |
public boolean isHidden() | 普通 | 测试此抽象路径名指定的文件是否是一个隐藏文件 |
public long lastModified() | 普通 | 返回此抽象路径名表示的文件最后一次被修改的时间。 |
public long length() | 普通 | 返回由此抽象路径名表示的文件的长度。如果此路径名表示一个目录,则返回值是不确定的。 |
public boolean renameTo(File dest) | 普通 | 重新命名此抽象路径名表示的文件。 |
public File[] listFiles() | 普通 | 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 |
1.2字节流和字符流
上面我们复习File类,File里虽然可以操作文件,但是并不是操作文件内容,如果要进行内容的操作,只能通过两种途径完成字节流和字符流
假如我们要对一个文件进行输入,输出操作,一般会按照如下的步骤进行
- 通过File类定义一个要操作文件的路径(如果不是对文件进行操作则可以忽略)
- 通过字节流或者字符流的子对象对父类对象进行实例化
- 进行数据的读(输入),写(输出)操作
- 数据流属于资源资源操作,资源操作必须关闭
- 字节流(JDK1.0):InputStream, OutputStream
- 字符流(JDK1.1):Reader ,Writer
1.2.1字节输出流OutputStream(重点)
OutputStream是一个专门进行字节数据输出的一个类,这个类定义如下
public abstract class OutputStream extends Object implements Closeable, Flushable
{
//整个IO里面就是类和抽象类
}
All Implemented Interfaces:
Closeable
, Flushable
, AutoCloseable
首先我们发现OutoutStream类实现了两个接口:Closeable
和Flushable
两个接口
我们先看Closeable
接口
Closeable
public interface Closeable extends AutoCloseableA Closeable
是可以关闭的数据的源或目的地。 调用close方法来释放对象持有的资源(如打开的文件)。
从以下版本开始:
1.5
这里出现了一个很厉害的地方,接口继承接口
public interface Closeable extends AutoCloseable
{
public void close() throws IOException;
}
我们打开Closeable继承的接口AutoCloseable的定义,jdk1.7出现的自动关闭机制
Interface AutoCloseable
public interface AutoCloseable{
void close() throws 异常
}
还有一个
Flushable
接口
public interface Flushable
A
Flushable
是可以刷新的数据的目的地。 调用flush方法将任何缓冲输出写入底层流。
从以下版本开始:
1.5
Interface Flushable
public interface Flushable
{ void flush() throws IOException
}
清空处理
1.2.1.1 OutputStream方法
OutputStream类里面一共提供提供三个输出方法
输出单个字节
public abstract void write(int b) throws IOException
将指定的字节写入此输出流。 write的一般合同是将一个字节写入输出流。 要写入的字节是参数b的八个低位
。 b的24个高位
被忽略。 bit一个字节,int八个字节
OutputStream的OutputStream必须为此方法提供一个实现。
输出全部字节数组
public void write(byte[] b) throws IOException
将b.length字节从指定的字节数组写入此输出流。 write(b)的一般合约是应该具有与电话write(b, 0, b.length)完全相同的效果。
输出部分字节数组
public void write(byte[]b,int off,int len) throws IOException
write的方法OutputStream调用写出在每个字节中的一个参数的写入方法。 鼓励子类覆盖此方法并提供更有效的实现。
OutputStream是一个抽象类,按照抽象类的使用规则,需要定义抽象类的子类,而如果是执行文件操作OutputStream下有一个子类叫FileOutputStream子类,子类要为抽象类进行对象的实例化,我们再调用的方法以父类中定义的方法为主,而具体的实现是实例化这个父类的子类完成,所有我么对应子类最关注的也就是子类的构造方法了
构造方法是
第一个:
public FileOutputStream(File file) throws FileNotFoundException
// 创建文件输出流以写入由指定的File对象表示的文件。 创建一个新的FileDescriptor对象来表示此文件连接
//参数 file - 要打开的文件进行写入。
第二个:
public FileOutputStream(File file, boolean append) throws FileNotFoundException
/*创建文件输出流以写入由指定的File对象表示的文件。 如果第二个参数是true ,则字节将被写入文件的末尾而不是开头。 创建一个新的FileDescriptor对象来表示此文件连接。
参数
file - 要打开的文件写入。
append - 如果 true ,则字节将被写入文件的末尾而不是开头
*/
我们利用FileOutputStream对一个文件进行写操作
public class FileOutputStreamDemo {
public static void main(String[] args) throws Exception{
// 1.定义输出文件的路径
File file = new File("F:"+File.separator+"demo.txt");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
// 2.通过子对象对父类进行实例化
OutputStream output = new FileOutputStream(file,true);
// 3.进行文件内容的输入
String str="\n唐诗三百首";
for (int i = 0; i <1000 ; i++) {
byte [] data=str.getBytes(); //将字符串变成字节数组
output.write(data);
}
System.out.println("写入成功");
// 4.资源操作一定要关闭
output.close();
}
}
1.2.2 字节输入流:InputStream
如果需要对数据进行读取操作,可以利用InputStream类实现功能,此类定义为
这个抽象类是表示输入字节流的所有类的超类。
需要定义InputStream
子类的应用InputStream
必须始终提供一种返回输入的下一个字节的方法。从以下版本开始:
JDK1.0
public abstract class InputStream extends Object implements Closeable
{
}
虽然继承了Closeable接口但几乎不用,自用自己定义的方法
1.2.2.1 InputStream主要方法
在InputStream类里面,定义了3个读取数据的操作
方法 | 类型 | 描述 |
---|---|---|
public abstract int read() throws IOException | abstract int | 从输入流读取数据的下一个字节。 值字节被返回作为int 范围0 至255 。 |
public int read(byte[] b) throws IOException | int | 从输入流读取一些字节数,并将它们存储到缓冲区b 。 实际读取的字节数作为整数返回。结果 读取到缓冲区的总字节数,或者如果没有更多的数据,因为已经到达流的末尾,则是 -1 。 |
public int read(byte[] b, int off, int len) throws IOException | 从输入流读取len 字节的数据到一个字节数组。 尝试读取多达len 个字节,但可以读取较小的数字。 实际读取的字节数作为整数返回。 |
- public abstract int read() throws IOException
从输入流读取数据的下一个字节。 值字节被返回作为int
范围0
至255
。
**返回值:**返回读取的数据长度,如果读取到了结尾返回-1
- public int read(byte[] b) throws IOException
从输入流读取一些字节数,并将它们存储到缓冲区b 。 实际读取的字节数作为整数返回。结果 读取到缓冲区的总字节数,或者如果没有更多的数据,因为已经到达流的末尾,则是 -1 。
**返回值:**返回读取的数据长度,如果读取到了结尾返回-1
- public int read(byte[] b, int off, int len) throws IOException
从输入流读取len
字节的数据到一个字节数组。 尝试读取多达len
个字节,但可以读取较小的数字。 实际读取的字节数作为整数返回。
package com.rango.InputStream;
import java.io.*;
/**
* @program: java_IO复习
* @description: 测试InputStream中的一些方法, 进行文件输入操作
* @author: JiaYadong
**/
public class InputStreamDemo {
public static void main(String[] args) throws Exception {
// 定义读取的文件路径
File file = new File("F:"+File.separator+"demo.txt");
// 2.使用InputStream进行读取
InputStream stream = new FileInputStream(file);
// 3.将数据保存到一个数组中
int foot=0;
byte[] bytes = new byte[1000010];
int temp=0;
while((temp=stream.read())!=-1){
bytes[foot++]=(byte)temp; //保存返回的单个字节
}
// 4.关闭输入流
stream.close();
// 输出数据
System.out.println("该文本一共有: "+foot+" 个字");
System.out.println(new String(bytes,0,foot));
}
}
1.2.3 字符输出流Writer
OutputStream类作为字节数据输出,其实字符数据也可以使用Writer类完成
public abstract class Writer
extends Object
implementsAppendable
,Closeable
,Flushable
用于写入字符流的抽象类。 子类必须实现的唯一方法是write(char [],int,int)
,flush()和close()。 然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者。
从以下版本开始:
JDK1.1
Closeable和Flushable我们已经不陌生的,我们需要看一看Appendable的定义
Appendable
追加功能
public interface Appendable
{
Appendable append(CharSequence csq) throws IOException;
}
三个方法:
Appendable append(CharSequence csq) throws IOException;
/*将指定的字符序列追加到此Appendable 。
根据哪个类实现字符序列csq ,可能不会附加整个序列。 例如,如果csq是CharBuffer,则附加的子序列由缓冲区的位置和限制来定义。
参数
csq - 要追加的字符序列。 如果csq是null ,则四个字符"null"附加到该附录*/
Appendable append(CharSequence csq, int start, int end) throws IOException
/*
将指定的字符序列的子序列附加到此Appendable 。
形式的这种方法的调用时out.append(csq, start, end) csq不是null,行为以完全相同的方式调用
out.append(csq.subSequence(start, end))
*/
Appendable append(char c) throws IOException
/*
将指定的字符附加到此 Appendable 。
参数
c - 要追加的角色
*/
1.2.3.1 字节输出流中的主要几个方法
public void write(int c) throws IOException
写一个字符 要写入的字符包含在给定整数值的16个低位中; 16个高位被忽略。
旨在支持高效单字符输出的子类应该覆盖此方法。
public void write(char[] cbuf) throws IOException
写入一个字符数组。 参数 :cbuf - 要写入的字符数组
public abstract void write(char[] cbuf, int off, int len) throws IOException
写入字符数组的一部分。 参数
cbuf - cbuf数组
off - 从中开始编写字符的偏移量
len - 要写入的 len数
public void write(String str) throws IOException
写一个字符串的一部分。
参数
str - 字符串
off - 开始编写字符的偏移量
len - 要写入的 len数
我们需要注意的是Writer只是一个抽象类,我们需要使用它只能用他的子类,现在我们是对文件进行操作,所有可以使用FileWriter进行使用
public class FileWriterTest {
public static void main(String[] args) throws IOException {
Writer writer=null;
File file = new File("F:"+File.separator+"demo.txt");
try {
writer = new FileWriter(file,true);
String data="\r\n不要被打败,坚持做自己";
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("写入成功!");
if (writer!=null){
writer.close();
}
}
}
}
1.2.4字符输入流Reader
public abstract class Reader extends Object implements Readable, Closeable
/*
用于读取字符流的抽象类。 子类必须实现的唯一方法是read(char [],int,int)和close()。 然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者。
从以下版本开始:
JDK1.1
*/
我们再字符输入流Reader中发现一个新的接口Readable
,我们查看其定义
public interface ReadableA Readable是一个字符源。 Readable的字符通过CharBuffer提供给读取方法的呼叫者 。
从以下版本开始:
1.5
不怎么考虑
1.2.4.1字节输出流中的主要几个方法
在Reader里面也提供了一系列的read()方法,
- 读取内容到字符数组public int read(CharBuffer target) throws IOException
表示正在读取的数据长度,如果已经读取到结尾返回-1;
为Reader类实例化的可以使用FileReader子类完成,
public class FileReaderTest {
public static void main(String[] args){
Reader reader=null;
File file = new File("F:"+File.separator+"demo.txt");
try{
reader= new FileReader(file);
// 申请数组
char[] chars = new char[1024];
int len=reader.read(chars);
System.out.println("输入字符数组中的数据"+new String(chars,0,len));
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3字节流与字符流的区别
给出两个流我们应该用哪个?有什么区别
字节流和字符流最大的区别:字节流直接与终端进行数据交换,而字符流需要将数据经过缓冲区(理解为内存)处理之后,再由缓存区操作终端(如文件),这属于间接操作,属于间接操作,按照这种方式,如果字节流最后不关闭,也可以将内容直接输出,但是字符流最后不关闭,则缓冲区的内容不会流出来,当然,可以有用户自己使用Flush函数进行操作
1.4转换流
以上我们知道字符流会有一个缓冲区,在不用关闭流的情况下,需要手动关闭流文件,但是有一个不能忽略的问题,字符输出流可以直接操作字符,所有有时候我们会使用字符流和字节流的使用
在java.io里面提供了两个子类,我们观察两个类
public class InputStreamReader extends Reader
InputStreamReader是从字节流到字符流的桥:它读取字节,并使用指定的charset将其解码为字符 。 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
每个调用InputStreamReader的read()方法之一可能会导致从底层字节输入流读取一个或多个字节。 为了使字节有效地转换为字符,可以从底层流读取比满足当前读取操作所需的更多字节。
//构造方法,就是字节流对象,返回字符流对象
public InputStreamReader(InputStream in)
由于这部分内容使用很少,所有我们直接观察类的继承结构
我们知道文件保存在磁盘上,磁盘上能够保存文件形式都是以字节的方式保存,而我们在字符操作的时候也是对字节数据读取的,不过是这个过程被电脑在缓冲区操作了
总结:
- 如果要进行转换 唯一可能出现的形式就是处理中文
- 两个转换类都是字符流的子类,属于字符流字节流共同桥梁
2.0文件拷贝小实验
模拟Dos系统的文件拷贝命令.有初始化参数输入源文件,和拷贝文件的路径, 接着执行文件拷贝
**思路:**对文件复制要使用字节流,因为文件有可能是图片.对应这样的操作有两种实现方式
- 一次性读取,再一次性输出
- 边读边输,一点一点进行复制
要知道我们的内存空间没有这么大,一下子读取这么多文件会时间会很慢
所有使用边读边输出的方式还是比较好的
设置main(String args[])
package com.rango.Test;
import java.io.*;
/**
* @program: java_IO复习
* @description: 模拟Dos文件对进行拷贝
* @author: JiaYadong
* @create: 2020-08-08 12:16
**/
public class TestCopy {
public static void main(String[] args) throws IOException {
if (args.length!=2){
System.out.println(args.length);
// 长度不是二.命令有误
System.out.println("执行命令出错!");
System.exit(1);
}
// 为了测试程序运行时间
long start = System.currentTimeMillis();
// 1.要读取的文件路口,已经在类中配置完毕
File infile = new File(args[0]);//复制文件
File outfile = new File(args[1]);//输出路径
InputStream inputStream = new FileInputStream(infile);
OutputStream outputStream = new FileOutputStream(outfile);
int temp=0;
byte[] bytes = new byte[1024];
// 将输入流输入到字节数组中
while ((temp=inputStream.read(bytes))!=-1){ //有内容
outputStream.write(bytes,0,temp);
}
long end = System.currentTimeMillis();
System.out.println("拷贝完成花费时间: "+(end-start)+" 毫秒");
inputStream.close();
outputStream.close();
}
}
3.0字符编码
4.0 内存流
当我们学习了AJAX+XML(JSON)应用才会牵扯到此部分
可以使用内存流操作IO操作
什么时候会出现内存流的使用情况?
在每个操作中必须使用IO操作,但用不希望有一些临时文件产生,那我们不能使用文件操作流了,应该使用内存操作流了
针对io操作内存里提供了两种操作
- 字节内存流:内存输入流(ByteArrayInputStream),内存输出流(ByteArrayOutputStream)
- 字符内存流:内存输入流(CharArrayReader),内存输出流(CharArrayWriter)
public class Demo1 {
public static void main(String[] args) throws IOException {
String str="Hello world.";
// 将数据输出到内存中去
InputStream bIn = new ByteArrayInputStream(str.getBytes());
// 从内存中读取数据
OutputStream bOut = new ByteArrayOutputStream();
int temp=0;
while ((temp=bIn.read())!=-1){
bOut.write((char)Character.toUpperCase(temp));
// 从内存中输出
}
// 可以直接读数据
String s = bOut.toString();
bOut.close();
bIn.close();
System.out.println(s);
}
}
我们没有用到文件操作,但可以直接把数据读取出来
4.1一个电影合并代码
package edu.youzg.about_io.about_file.core.Test;
import java.io.*;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream in1 = new FileInputStream("妇联 上.mp4");
FileInputStream in2 = new FileInputStream("妇联 下.mp4");
FileOutputStream out = new FileOutputStream("妇联 玩整版.mp4");
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
//创建一个集合
ArrayList<FileInputStream> list = new ArrayList<>();
list.add(in1);
list.add(in2);
int len=0;
byte[] bytes = new byte[1024 * 8];
for (FileInputStream in : list) {
while ((len=in.read(bytes))!=-1){
byteOut.write(bytes,0,len);
byteOut.flush();
}
in.close();
}
//取出两部电影的字节数据
byte[] allBytes = byteOut.toByteArray();
//将两部电影的字节数据,写到硬盘上
ByteArrayInputStream byteIn = new ByteArrayInputStream(allBytes);
int len2 = 0;
byte[] bytes2 = new byte[1024 * 8];
while ((len2 = byteIn.read(bytes)) != -1) {
out.write(bytes, 0, len2);
out.flush();
}
//释放资源
out.close();
}
}
5.0 打印流
我们会学习打印流的实现原理
打印流操作类的使用
打印流属于整个java开发中,非常重要的概念
5.1 PrintStream字节打印流和PrintWriter字符打印流
5.2代理模式(未完成)
未完成
5.3装饰设计模式(未完成)
未完成
将一个功能不足的操作类,通过包装,形成更好用的工具类
6.0 System类
在PrintStream类中有许多的println方法,下面是System类的一些定义,有3个与IO有关的常量
System类的3个常量,有两个都是PrintStream类的实例对象
System类常用三个变量
常量 | 类型 | 描述 |
---|---|---|
public static final PrintStream err | 常量 | “标准”错误输出流。 此流已经打开并准备好接受输出数据。 |
public static final PrintStream out | 常量 | “标准”输出流。 |
public static final InputStream in | “标准”输入流。 |
6.1 System.in的输入
系统的输出是将所有信息输出到指定的输出设备(显示器上).System.out本身属于PrintStream对象,而PrintStream是OutputStream的子类
public class Demo1 {
public static void main(String[] args) throws IOException {
InputStream in = System.in;
byte[] bytes = new byte[1024];
System.out.println("请输入数据:");
int len=in.read(bytes);
// 等待用户输入,程序进入阻塞状态
System.out.println("您一共输入:"+len+"个字符");
System.out.println("您输入的数据是"+new String(bytes,0,len));
}
}