流
根据流的方向性,可以分为输入流和输出流。
以java为例,则输入流是由文件“流向”程序,输出流是程序“流向”文件。
这里是以文件为例,java程序还可以讲数据写入到网络、内存等中。
流的分类
java中的IO流可以根据很多不同的角度进行划分,最常见的是以数据的流向和数据的类型来划分
根据数据的流向分为:输入流和输出流
-
输入流 :把数据从其他设备上读取到程序中的流
-
输出流 :把数据从程序中写出到其他设备上的流
根据数据的类型分为:字节流和字符流
- 字节流 :以字节为单位(byte),读写数据的流
- 字符流 :以字符为单位(char),读写数据的流
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 | 字节输出流 |
字符流 | 字符输入流 | 字符输出流 |
注意,字节指的是byte,字符指的的是char
一个流具备最起码的三个特点
- 是输入还是输出
- 是字节还是字符
- 流的目的地
字节流
即以字节传输的流。(字节:计算机最基本的二进制存储单位)
java.io.InputStream
是所有字节输入流的父抽象类
InputStream
中最核心的三个read方法:(其中.skip()方法可以让我们先跳过指定的字节数,再去读取)
//每次读取一个字节,返回值是本次读取的字节值
public abstract int read() throws IOException;
//每次读取多个字节,并存放到指定数组中,返回值是本次一共读取了多少个字节(字节数)
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/*读取同传数组参数方法,区别在于,可以指定从数组的第几个位置开始存放,len为最多存放多少个字节
(即调用这个方法的时候,一次只读从len个字节。比如数组长度有900,len为300,每次只从其他设备中读取300字节。)*/
public int read(byte b[], int off, int len) throws IOException
{ if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
java.io.OutputStream
是所有字节输出流的父抽象类
OutputStream
中最核心的三个write方法
//写出去一个字节值
public abstract void write(int b) throws IOException;
//把一个自己数组中的值全部写出去
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//写出字节数组,指定开始位置,以及写出的字节数量
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
例子
@Test
public void testOutputStream() throws Exception {
//单字符
FileOutputStream d = new FileOutputStream("d.txt",true);
d.write("还可以".getBytes());
d.flush();
d.close();
}
@Test
public void testOutputStream() throws Exception {
//整个数组
FileOutputStream d = new FileOutputStream("d.txt",true);
FileInputStream b = new FileInputStream("b.txt");
byte[] bytes= new byte[900];
int length=0;
while((length=b.read(bytes))!=-1) {
d.write(bytes);
}
d.flush();
d.close();
}
@Test
public void testOutputStream() throws Exception {
//一次只输出300字节
FileOutputStream d = new FileOutputStream("d.txt",true);
FileInputStream b = new FileInputStream("b.txt");
byte[] bytes= new byte[900];
b.read(bytes);
d.write(bytes,0,300);
d.flush();
d.close();
}
补充:(基本所有流的使用都是这个套路)
在代码中,使用流操作数据的基本步骤是:
1.声明流
2.创建流
3.使用流
4.关闭流
数组、文件、管道
无论是在字节流还是字符流中,这三样分别都是
- ByteArray 数组输入输出流 (字符是CharArray)
CharArrayWriter中的toCharArray方法,可以将写入到输出对象中的数据返回
- File 文件输入输出流
- Pided 管道(记得管道对接(输入.connect.输出))-管道本质是一个线程将数据写入管道中,另一个线程再从管道中获取数据
字符流
- Reader 输出
- Write 输入
俩者的read方法或者write方法与 字节流用法相同
Reader 的read方法中 除了数组以外,还可以传入字符串参数,也就是可以用字符串接收数据
相比字节流,在面对文本传输时,字符流的传输方式效率将高于字节流的传输方式
数据流
java.io.DataOutputStream
可以将指定类型的数据转换为字节并写出去。
public void testDataOutputStream() throws Exception {
DataOutputStream dOutput = new DataOutputStream(new FileOutputStream("f.txt",true));
dOutput.writeLong(1000L);
DataInputStream dInput = new DataInputStream(new FileInputStream("f.txt"));
System.out.println(dInput.readLong());
}
数据流不能直接操作字节,所以
DataOutputStream
必须包裹一个字节流,增强这个字节流的功能,一次可以写出去一个具体类型的数据
缓冲流
java.io.BufferedInputSteam
,负责给字节输入流提供缓冲功能
java.io.BufferedOutputSteam
,负责给字节输出流提供缓冲功能
使用缓冲功能之后比原先直接使用字节输入输出 更快,效率更高
代码
@Test
public void TestBufferedOutput() throws Exception {
BufferedInputStream buffIn = new BufferedInputStream(
new FileInputStream("背影.txt"));
BufferedOutputStream buffOut = new BufferedOutputStream(
new FileOutputStream("背影copy.txt"));
//毫秒为单位
long start= System.currentTimeMillis();
int length=0;
while((length = buffIn.read())!=-1) {
buffOut.write(length);
}
buffOut.flush();
long end =System.currentTimeMillis();
System.out.println("使用缓冲流完成读写操作的时间为:"+(end-start)+"毫秒");
buffIn.close();
buffOut.close();
}
输出结果:
相比普通的直接使用:
普通使用代码:
@Test
public void TestBufferedOutput() throws Exception {
FileInputStream buffIn =
new FileInputStream("背影.txt");
FileOutputStream buffOut =
new FileOutputStream("背影copy.txt");
//毫秒为单位
long start= System.currentTimeMillis();
int length=0;
while((length = buffIn.read())!=-1) {
buffOut.write(length);
}
buffOut.flush();
long end =System.currentTimeMillis();
System.out.println("使用缓冲流完成读写操作的时间为:"+(end-start)+"毫秒");
buffIn.close();
buffOut.close();
}
拷贝结果:
可以看出来使用了缓冲区功能快了很多,内容越多,越明显
- 在
BufferedReader
中有一个特殊方法 - readLine() 读取文件的一行,相对的PrintWriter 中的println,一次写一行,写完之后自动换行(windows系统还会加回车)
BufferedReader
PrintWriter
利用这俩者 输入,输出数据
代码
public void TestSpecial() throws Exception {
BufferedReader reader = new BufferedReader(new FileReader("背影.txt"));
PrintWriter writer = new PrintWriter(new FileWriter("背影copy2.txt"));
//这里的读,判断是否读完是用String 当读到null的时候就读取结束
String flag =null;
while((flag = reader.readLine())!=null) {
writer.println(flag);
}
writer.flush();
writer.close();
reader.close();
}
BufferedReader
也拥有skip()
的方法,跳过几个字符,再进行读取
如果想要读取一行
.skip(输入对象.readLIne().length+2);
跳过多少个字符,跳过行的长度 加上 \n和\r 换行和回车
转换流
转换流可以在将字节流转换为字符流的同时,并指定转换的字符集
InputStreamReader
字节输入流转换为字符输入流
OutputSteamWriter
字节输出流转换为字符输出流
outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file),"UTF-8");
- 使用方法和输入流输出流一样 三个read 三个write
- 转换之后同样可以被包裹使用
对象流
较为重要
java提供了一种对象序列化的机制,可以将对象和字节序列之间进行转换:
序列化和反序列化
- 序列化:程序中可以用一个字节序列来表示一个对象,该字节序列中包含了对象的类型、对象中的数据等。如果这个字节序列写出到文件中,就相当于在文件中持久保存了这个对象的信息
- 反序列化: 相反的过程,从文件中将这个字节序列读取回来,在内存中重新生成这个对象,对象的类型,属性数据等和原来保持一致。(注意这个对象的内存地址可能与原来的不一样)
完成对象的序列化和反序列化就需要用到对象流了
对象流中有一个小小的要求
只有对象实现了 Serializable 接口才可以进行序列化和反序列化(但是不需要实现这个接口的抽象,因为这个接口中没有定义抽象)
ObjectOutputStream
将对象转换为字节队列,并输出到内存、文件、网络等其他地方。
具体输出到什么位置,需要看ObjecctOutputStream
包裹的是什么字节流
ObjectInputStream
,从某一个地方读取出对象的字节序列,并生成对应的对象。
注意,具体是从输什么地方读取字节,要看 ObjectInputStream
“包裹”的是哪一个节点流
关键字transient
ava中的关键字transient
,可以修饰类中的属性,它的让对象在进行序列化的时候,忽略掉这个被修饰的属性。
常用在一些敏感属性的修饰,例如对象中的password属性,我们并不想将这个敏感属性的值进行序列化保存,那么就可以使用transient
来对他进行修饰
序列化版本号
Serializable 接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
该版本号的目的,是用于验证序列化的对象和对应类是否版本匹配
在我们将一个对象进行序列化之后,又对这个对象的属性进行了修改,然后再去将这个对象反序列化,此时会报错。因为在反序列化的时候会验证反序列化的字节序列版本是否与对象匹配。而对象属性变化了,其序列号也产生了变化。
如果我们手动指定这个版本号之后,那么这个类的序列化版本号就固定下来了:
public class Student implements Serializable {
//属性的名字、类型、修饰符是固定的,值可以随意给一个
private static final long serialVersionUID = 1L;
String name;
}
这个时候重复上述的操作 就不会报错了
随机访问流
java.io.RandomAccessFile
是JavaAPI中提供的对文件进行随机访问的流
一般是使用"rw"方法 在构造函数的第二个参数