目录
流介绍:Java中所有的数据都是使用流读写的,流是一组有顺序、有方向,有起点和终点的字节集合,通过使用流我们可以完成设备之间的数据传输。
1、流体系及实现类
流的体系
流的实现类
2、流的划分
按照方向划分:
输入流:从各种输入设备(磁盘、网卡、键盘...)将数据读取到当前程序
输出流:从当前程序将数据写入输出设备(磁盘、网卡、屏幕)
按照数据传输单元划分
字节流:以字节为单位的数据传输流, InputStream和OutputStream
字符流:以字符为单位的数据传输流,Reader、Writer
按照功能划分
节点流:用于直接操作目标设备的流
过滤流(高级流):对一个已存在的流进行包装,以提供更强大灵活的读写功能
3、File类介绍
File类主要是对文件或者目录的抽象表示
文件可以理解为保存数据的一种方式
文件一共有两部分组成,属性(文件名、大小、类型)和内容(文件中存储的内容)
File类的构造函数
构造函数 | |
---|---|
File (String pathName) | 通过路径字符串获取file实例 |
File(String parent, String child) | 第一个参数:父路径字符串 第二个参数:文件名字符串 |
File(File parent, String child) | 第一个参数:父路径File对象 第二个参数:文件名字符串 |
调用示例
String path = "/Users/*****/test1.txt";
//通过一个字符串路径创建file实例
File file = new File(path);
String parentPathName = "/Users/*****/test";
String name = "test1.txt";
//通过父路径字符串和文件名字符串创建file实例
File file1 = new File(parentPathName, name);
File parentFileName = new File(parentPathName);
//通过父路径的file对象和文件名字符串创建file实例
File file2 = new File(parentFileName, name);
绝对路径和相对路径
绝对路径:带盘符开始的路径, 比如:windows系统:C:\ Linux: /
相对路径:不带盘符的路径 '.'当前路径 '..':父路径
路径分割符:
Linux:‘/’ 例如:/Users/*/Desktop/IO/test1.txt
Windows系统:‘\’ 例如:C:\test\test1.text
4、字节流
1、InputStream类
1、定义
InputStream是字节输入流,也就是从存储数据的设备把数据输入到程序中。也是字节输入流基类,不能被实例化,需要通过他的子类、FileInputStream类来实例化。
inputstream声明如下:
public abstract class InputStream implements Closeable
InputStream是一个抽象类,是字节输入流的顶层抽象,所有的字节输入流的类都是其子类, 实现了Closeable接口,该接口中提供close方法,流使用结束后需要显性关闭。
2、涉及的主要方法
该方法是每次读取一个字节,返回的类型是int、结束的条件就是read的返回值为-1
public int read() throws IOException {
return read0();
}
该方法时每次读取byte数组大小的字节,返回类型为int。结束的条件就是read的返回值为-1
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
该方法时每次byte数组大小的数据,从指定的off位置开始,读取的长度为len
结束的条件就是read的返回值为-1
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
文件输入流
FileInputStream:文件输入流,从存储数据的设备输入到程序文件,是InputStream的具体实现类。
public class FileInputStream extends InputStream
构造函数:
从构造函数中可知当传入的文件不存在是,运行时会抛出FileNotFoundExeception异常
FileInputStream(String path) throws FileNotFoundException
FileInputStream(File file) throws FileNotFoundException
读取操作的实现如下:
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
底层调用的是read0或者readBytes的native方法
读取操作注意点:
1、如何判断数据是否读取结束 ,通过-1表示读取结束
2、读取全部文件 循环读取数据(关注每次读取数据的有效个数)
3、方法的使用
//获取文件路径
String path = "C:\\Users\\****\IO\\java.txt";
//开启流
InputStream inputStream = null;
try {
//流的路径
inputStream = new FileInputStream(path);
/*
* int read() 读取一个字节返回类型是int
* int read = inputStream.read();
System.out.println((char)read);
* */
// int read(byte[] bytes) ; // 读取的字节保存在数组中
byte[] bytes = new byte[2];
int read = -1;
while((read = inputStream.read(bytes) )!= -1){
System.out.print(new String(bytes,0,read));
}
int read(int byte[], int begin,int len) 获取从指定位置开始,获取指定长度的字节
byte[] bytes = new byte[10];
int read = -1;
while((read = inputStream.read(bytes,2,5)) != -1){
System.out.println(read);
System.out.print(new String(bytes,2,read));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2、OutputStream类
OutputStream是字节输出的基类
public abstract class OutputStream implements Closeable, Flushable
核心方法:
文件输出流
FileOutputStream:将数据从程序写入目标存储设备:文件
构造函数:
当文件不存在时,会创建出指定文件,当路径不存在时,会抛出FileNotFoundException异常。append默认是false,表示写入的数据会覆盖文件原本内容,如果append为true,可以进行内容追加,将新写入的内容追加到文件的后面
FileOutputStream(String name) throws FileNotFoundException
FileOutputStream(String name, boolean append) throws FileNotFoundException
FileOutputStream(File file) throws FileNotFoundException
FileOutputStream(File file, boolean append) throws FileNotFoundException
方法调用:
String path = "/Users/苏亚/Desktop/IO/Java.txt";
FileOutputStream outputStream=null;
try {
outputStream = new FileOutputStream(path);
//写一个字节
/**
* void write(int b) throws IOException
* 每次写一个字节
*/
outputStream.write('d');
/**
* void write(byte b[])
* 批量写:byte数组内容写入到底层
*
*/
byte[] bytes = {'a', 'b', 'c'};
outputStream.write("hello".getBytes());
/**
* void write(byte b[], int off, int len) throws IOException
* 批量写
* 第一个参数:数据源 byte数组
* 第二个参数:偏移量 从byte数组指定偏移量写
* 第三个参数:长度,从偏移量开始写的长度
*/
byte[] bytes1 = "hello tulun".getBytes();
outputStream.write(bytes1,6,5);
//将数据刷入磁盘
outputStream.flush();
4、字符流
Reader:字符输入流
Reader是IO库提供到的一个输入流基类
和InputStream的区别是:InputStream是一个字节流,操作的是以byte为单位读取;Reader是一个字符流,操作的是以char为单位读取
Reader的继承关系如下:
public abstract class Reader
implements Readable, Closeable
FileReader类
该类是Reader的实现类,其主要功能是从磁盘文件中读取数据
读取方法:
public int read() throws IOException
public int read(char cbuf[]) throws IOException
public int read(char cbuf[], int off, int len) throws IOException
public int read(java.nio.CharBuffer target) throws IOException
读取操作:
//读取数据
int read = fd.read();
System.out.println((char) read);
/**
* int read(char cbuf[]) throws IOException
* 批量读取操作
* 读取的数据放入char数组中
* 返回结果表示读取数据的有效个数
*/
char[] chars = new char[12];
int read1 = fd.read(chars);
System.out.println(new String(chars,0,read1));
/**
* int read(char cbuf[], int offset, int length)
* 批量读取
*/
fd.read();
Writer:字符输出流
字符输出流, Writer操作的是字符,以char为单位进行操作
OutputStream操作的是字节,以byte为单位操作
Writer操作带有编码装换器的OutputStream
Reader操作带有编码转换器的InputStream
Writer的继承关系如下:
public abstract class Writer implements Appendable, Closeable, Flushable
核心方法
FileWriter类
该类是Writer的实现类主要功能是向磁盘文件中写入数据。
文件的写操作
构造函数:
构造函数注意: 1、如果文件不存在可以自动创建,如果目录不存在,则抛出异常
2、如果需要将写入的数据以追加的形式存储,需要传递append参数为true
方法介绍:
FileWriter fw = null;
try {
fw = new FileWriter(path,true);
/**
* void write(int c)
* 每次写入一个字符的ASCII码
*/
fw.write('a');
/**
* void write(char cbuf[])
* 批量写入
* 将char数组内容写入
*/
char[] chars = {'a', 'b', 'c'};
fw.write(chars);
/**
* void write(char cbuf[], int off, int len)
* 批量写入char数组
*/
fw.write(chars,0,3);
/**
* void write(String str)
* 写入字符串
*/
fw.write("hello");
/**
* void write(String str, int off, int len)
* 写入部分字符串
* off指定字符串的偏移量 len表示写入的长度
*/
fw.write("hello",0,5);
fw.append(" tulun");
fw.flush();
5、转换流
将字节流和字符流进行相互转换
OutputStreamWriter:将字节输出流转换为字符输出流
InputStreamReader:将字节输入流转换为字符输入流
public class OutputStreamWriter extends Writer
public OutputStreamWriter(OutputStream out) {
super(out);
try {
se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
public class InputStreamReader extends Reader
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
由源码分析可知OutputStreamWriter是继承Writer这个类,InputStreamReader继承Reader这个类,主要的目的就是把字节流转换成字符流的形式进行数据的读和写操作。
Reder和InputStream的关系
普通的Reader实际上是基于InputStream改造的,因为Reader需要从INputStream中读取字节流(byte),然后根据编码设置,在转化为字符流(char),在底层实现上持有一个转换器(StreamDecoder)
使用方式如下:
//字节流
//原始流
FileInputStream inputStream = new FileInputStream(path);
//高级流,通过封装原始流提供高级特征
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
6、缓冲流
缓冲罐流的作用就是磁盘的I/O次数减少,提高IO读写速度,也就是把数据读取或者写入时就会先把数据先保存在缓冲流中,到达一定的数据量后或者读取/写入数据已经结束后在IO操作。
缓冲流划分:字节缓冲流、字符缓冲流
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter
继承关系
public class BufferedInputStream extends FilterInputStream
public class BufferedOutputStream extends FilterOutputStream
public class BufferedReader extends Reader
public class BufferedWriter extends Writer
以字符缓冲流为例介绍
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
inputStream = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BufferedReader和BufferedWriter缓冲流
字符输出流:
String readLine() 分行读取 读取结束为null
字符输入流:
void newLine() 根据当前系统,写入一个换行符
7、对象流
在介绍对象流之前首先了解序列化和反序列化的概念
序列化和发序列化
序列化:将对象转化为字节流的过程,可以将流进行保存到磁盘或者是发送到网络中(rpc)
反序列化:将字节流转化为对象的过程
序列化的特点:
1、在Java中,只要一个类实现了Serializable接口,那么就可以支持序列化和反序列化
2、通过ObjectInputStream和ObjectOutputStream对对象进行序列化和发序列化
3、transient关键字用于控制变量的序列化,在变量前添加该关键字,可以阻止该字段被序列化到文件中,在反序列化后,transient关键字修饰的变量给定的就是初始化,对象类型是null,int类型是0
4、虚拟机是否允许反序列化,不仅仅取决于类路径和代码是否一致,还要看两个类的序列化Id是否一致
什么是serialVersionUID?
称之为版本标识符,使用对象的哈希码序列化会标记在对象上
两种生成方式,一种是默认的1L、另一种是通过类名、接口名、成员变量及属性等生成的64位的哈希字段,UID的生成的默认值是依赖于Java编译器的,对于同一个类,不同的编译器生成的结果UID可能是不同。idea中的具体操作设置如下
版本号的作用:
1、确保不同的版本有不同的UID
2、在类中新增或者修改属性,不设置UID,会抛出异常
对象流的处理使用ObjectInputStream和ObjectOutputStream类
以ArrayList为例介绍序列化和反序列化,在ArrayList中定义了writeObject和readObject方法
在序列化的过程中,如果被定义的类中定义类writeObject,虚拟机会试图来调用类中writeObject方法来进行序列化,如果没有这个存在,调用默认的ObjectOutputStream中defaultWriteObject方法来序列化
反序列化的类似
对ArrayList集合进行序列化和反序列代码如下:
public static void objectReader() {
String path = "/Users/gongdezhe/Desktop/IO/test1.txt";
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path));
ArrayList list = (ArrayList) inputStream.readObject();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void objectWrite() {
String path = "/Users/gongdezhe/Desktop/IO/test1.txt";
ArrayList <Integer> list = new ArrayList <>(100);
list.add(11);
list.add(22);
list.add(33);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(path));
outputStream.writeObject(list);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
源码实现思路
在ArrayList中显性实现了WriteObject和ReadObject方法。在对ArrayList的对象进行序列化和反序列化过程中,会自动调用到ArrayList中定义的WriteObject和ReadObject方法,在ArrayList中存储数据的属性定义为ElementData属性,该属性是通过transient关键字修饰
该关键字的目的是不让修改的属性参与序列化,而ArrayList中数据就存储在elementData中,如果不参与序列化,则ArrayList中的数据则无法持久化这就需要进行特殊处理。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
8、RandomAccessFile类
通过使用这个类我们可以不像之前的输入输出流一样,一种流只能是输入或者输出流,而这个类就可以通过指定模式来进行相应的操作,比如说,通过这个类创建的对象就进行读写操作,也可以是只读的形式,这样更能方便的使用。
RandomAccessFile类的构造函数
RandomAccessFile(String name, String mode)
RandomAccessFile(File file, String mode)
第一个参数:指定路径可以是String或者File类型、第二个参数:mode来指定访问模式
mode有4种访问模式:
“R”:以只读方式打开,调用任何write方法都会抛错
"RW":打开方便读取和写入
"RWS":打开进行读写操作,对文件内容和元数据的每次更新都会写入到基础存储设备
"RWD":打开进行读写操作,对文件内容的每次更新都会写入到基础存储设备
RandomAccessFile是IO体系中功能最丰富的文件访问类,即可以读取文件内容,也可以向文件中写入内容
另外:程序可以直接调到文件的任意位置进行读取操作
该类与之前学习的输入输出流相比有一些特殊方法:
long getFilePointer() //返回当前文件中数据的位置
void seek(long pos) //将文件记录指针定位到指定文件中的数据位置pos
其他的一些方法和我们之前学过的输入输出流的一些方法一样,在这里就不进行介绍了