IO
IO流是输入和输出的接触。
分类
1 输入流,输出流
输入流:只能读数据(**--->内存),不能写数据
输出流:只能写数据(内存 ---> 磁盘),不能读数据
划分原则:程序运行时所在的内存
2 字节流,字符流
字节流和字符流的操作基本一致,唯一的区别是操作的数据单元不同。
字节流——8位字节
字符流——16位字符
3 节点流,处理流
节点流:低级流
处理流:高级流,包装流
4 InputStream 和 Reader
InputStream 和 Reader 是所有输入流的抽象基类,本身不能常见实例来执行输入,但他们是所有输入流的模板
- InputStream
int read() 从输入流中获取单个字节,返回所取得的字节数
int read(byte[] b) 从输入流中最多取读b.length个字节数据,并将其存储在字节数组b中,返回实际取读的字节数
int read(byte[] b,int off,int len) 从输入流中最多取读len个字节的数据,并将其存储在字节数组b中,
放入字节数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际取读的字节数 - Reader
int read(): 取读单个字符
int read(char[] cbuf) 从输入流中最多取读cbuf.length个字符,并将其存储在字符数组cbuf中,返回实际读取的字符数
int read(char[] cbuf,int off,int len) 从输入流中最多取读len个字符的数据,并将其存储在字符数组b中,
放入字符数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际取读的字符数
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
byte[] bbuf = new byte[1024];
int hasRead = 0;
while ((hasRead = fis.read(bbuf)) > 0) {
System.out.println(new String(bbuf, 0, hasRead));
}
fis.close();
}
}
public class FileReaderTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("FileReaderTest.java");
char[] bbuf = new char[32];
int hasRead = 0;
while ((hasRead = fr.read(bbuf)) > 0) {
System.out.print(new String(bbuf, 0, hasRead));
}
fr.close();
}
}
5 OutputStream 和 Writer
- void write(int c) 将制定的字节、字符数出道输出流中
void write(byte[]/char[] buf) 将字节、字符数组输出到指定的输出流中
void write(byte[]/char[] buf,int off,int len) 将字节、字符数组从off位置开始,长度为len的字节
、字符输出到输出流中。
Writer中还可以使用字符串代替字符数组
- void write(String s) 将str中包含的字符输出到指定的输出流中
- void write(String str,int off,int len) 将str字符串中从off位置开始,长度为len的字符输出到指定输出流中
public class FileOutPutStreamTest {
public static void main(String[] args) {
try (
FileInputStream fis =
new FileInputStream("FileOutPutStreamTest.java");
FileOutputStream fos = new FileOutputStream("newFile.txt")) {
byte[] bbuf = new byte[32];
int hasRead = 0;
while ((hasRead = fis.read(bbuf)) > 0) {
fos.write(bbuf, 0, hasRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
```java
public class FileWriterTest {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("pome.txt")) {
fw.write("锦瑟-李商隐\r\n");
fw.write("锦瑟无端五十弦,一弦一柱思华年\r\n");
fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃\r\n");
fw.write("沧海月明珠有泪,蓝田日暖玉生烟\r\n");
fw.write("此情可待成追忆,只是当时已惘然\r\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出:
锦瑟-李商隐
锦瑟无端五十弦,一弦一柱思华年
庄生晓梦迷蝴蝶,望帝春心托杜鹃
沧海月明珠有泪,蓝田日暖玉生烟
此情可待成追忆,只是当时已惘然
6 处理流
处理流可以隐藏底层设备上节点流的差异,对外提供更加方便的输入,输出方法
识别处理流:只要文件流的构造器参数不是一个物理节点而是一个已存在的流,那么这个流一定是处理流。
所有的子节点都是直接以物理IO最为构造器参数的。
public class PrintStreamTest {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) {
ps.println("普通字符串");
ps.println(new PrintStreamTest());
} catch (IOException e) {
e.printStackTrace();
}
}
}
在使用处理刘包装了底层节点流后,关闭资源时,可以之关闭最上层的处理流即可
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
序列化
序列化的目标是将对象保存到磁盘中,或允许在网络中传输对象。
对象序列化机制允许把内容中java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保留在磁盘上
通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这个二进制流(无论是从磁盘还是网络),
都可以讲这种二进制流恢复到原来的java文件。
序列化机制是的对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize) 指将一个Java 对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)
则指从IO流中恢复该java对象。
序列化对象
- 创建一个ObjectOutputStream,这个输出是一个处理流,所以必须建立才其他节点流的基础上
ObjectOutputStream oos = new OjectOutputStream(new FileOutputStream("XXX"));
- 调用ObjectOuputStream对象的writeObject()方法即可输出徐硫化对象
oos.writeObject(***);
样例
public class WriteObject {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
Person person = new Person("孙悟空", 5000);
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出文件中的内容
�� �sr �com.huawei.kemuer.Person�#��JR`Z� �I �ageL �namet �Ljava/lang/String;xp ��t 孙悟空
反序列化步骤
- 创建一个ObjectInputStream 输入流,处理流
ObjectInputStram ois = new ObjectInputStream(new FileInputStream(***));
- 调用ObjectInputStream对象的readObject()方法取读流中的对象,该方法返回一个Object类型的java对象,可以强转
该对象的真实类型
*** p = (***)ois.readObject();
public class ReadObject {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));) {
Person p = (Person) ois.readObject();
System.out.println("名字:" + p.getName() + " 年龄:" + p.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
名字:孙悟空 年龄:5000
当反序列化取读java对象,无需通过构造器来初始化对象
对象引用序列化
如果某个类没有序列化,那么拥有这个类作为成员变脸的类也是不能序列化的
public class WriteTeacher {
public static void main(String[] args) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Teacher.txt"))) {
Person p = new Person("孙悟空", 5000);
Teacher t1 = new Teacher("唐僧", p);
Teacher t2 = new Teacher("菩提老祖", p);
out.writeObject(p);
out.writeObject(t1);
out.writeObject(t2);
out.writeObject(t1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
多次序列化同一个对象时,只有第一次序列化时,才会把该java对象转换成字节序序列并输出。
当序列化一个可变对象时,只有第一次使用writeObject()方法输出是才会将该对象转换成字节序列并输出
当程序再次调用wirteObject()时,程序只是输出前面的序列化编号,及时后面该对象的实例变量值已经被改变
改变的实例变量值也不会被输出。
public class SerializeMutable {
public static void main(String[] args) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mutable.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("mutable.txt"))) {
Person p = new Person("孙悟空", 5000);
out.writeObject(p);
p.setName("猪八戒");
out.writeObject(p);
Person p1 = (Person) in.readObject();
Person p2 = (Person) in.readObject();
System.out.println(p1 == p2);
System.out.println(p2.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
有参数构造器
true
孙悟空
自定义序列化
敏感信息不可以序列化
在敏感信息前加transient关键字,保证字段不被序列化
使用transient关键字修饰变量虽然简单、方便,但被transient修饰的实例变量江北完全隔离在序列化机制之外
这样导致在反序列化恢复java对象时,无法取得该实例变量值。
NIO
为了解决传统IO效率不高的问题而出现的新模式的IO
新IO的主要目的也是用于输入/输出,但新IO使用了不同的方式来处理输入/输出,新IO采用内存映射文件的方式来处理
输入/输出。新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(模拟操作系统的虚拟内存)
,通过这种方式来进行输入/输出比传统的输入/输出要快的多。
java中与新IO有关的包
- java.nio: 主要包含各种与buffer相关的类
- java.nio.channels:主要包含于Channel和Selector相关的类
- java.nio.charset: 主要包含与字符集相关的类
- java.nio.channels.spi:主要包含与Channel相关服务提供者接口编程
- java.nio.charset.spi:主要包含与字符集相关的服务提供者编程接口。
Channel(通道) 和 Buffer(缓冲)是新IO中两个核心对象,Channel是对传统输入/输出系统的模拟,在新IO
系统中所有的数据都需要通过通道传输。Channel与传统的InputStream和OutputStream最大的区别就是他提供了一个
map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则新IO
则是面向块的处理
Buffer
Buffer可以理解成一个容器,他本质是一个数组,发送到channel中的所有对形象都必须放到Buffer中,而Channel中读取的
数据也要先放到Buffer中,此处的Buffer有点类似于竹筒。
Buffer是个抽象类
最常用的子类是ByteBuffer。
Buffer类没有构造器,通过下面的方法来获取一个Buffer对象
- static XXXBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象
在Buffer中有三个比较重要的概念:
容量(Capacity):缓冲区的容量(capacity)表示改Buffer的最大数据容量,最多可以存储多少数据。
不能为负值,创建以后不能改变界限(limit):第一个不应该被读出或者写入缓存区位置索引,也就是说位于limit后的数据及不可以被读,也不可以被写
位置(position):用于志明下一个可以被读出的或者写入的缓存区位置索引(类似于IO流中的记录指针)
0 <= mark <= position <= limit <= capacity
Buffer的主要作用是装入数据然后输出数据
最开始数据的limit为capacity,position为0,装入数据后,position向后移动一些位置
- flip():从Buffer中取出数据做好准备
limit设置为position所在的位置,并将position设置为0 - clear():再次向Buffer中装入数据
position设置为0,将limit设置为capacity
public class BufferTest {
public static void main(String[] args) {
// 创建Buffer
CharBuffer buffer = CharBuffer.allocate(8);
System.out.println("capacity: " + buffer.capacity());
System.out.println("limit: " + buffer.limit());
System.out.println("position: " + buffer.position());
buffer.put('a');
buffer.put('b');
buffer.put('c');
System.out.println("加入三个元素后,position: " + buffer.position());
buffer.flip();
System.out.println("执行flit()后,limit: " + buffer.limit());
System.out.println("position: " + buffer.position());
System.out.println("第一个元素(position=0):" + buffer.get());
System.out.println("取出一个元素后,position: " + buffer.position());
buffer.clear();
System.out.println("执行clear()后,limit: " + buffer.limit());
System.out.println("执行clear()后,position: " + buffer.position());
System.out.println("执行clear()后,缓存区内容并没有被清除,第三个元素为" + buffer.get(2));
System.out.println("执行绝对取读后,position:" + buffer.position());
}
}
输出结果
capacity: 8
limit: 8
position: 0
加入三个元素后,position: 3
执行flit()后,limit: 3
position: 0
第一个元素(position=0):a
取出一个元素后,position: 1
执行clear()后,limit: 8
执行clear()后,position: 0
执行clear()后,缓存区内容并没有被清除,第三个元素为c
执行绝对取读后,position:0
Channel
channel类似于传统流对象,但与传统流对形象有两个主要的区别。
- Channel可以直接将制定文件的部分或全部直接映射成Buffer
- 程序补鞥呢直接访问Channel中的数据,包括取读,写入都不行,Channel只能与Buffer进行交互。
要从Channel中读取数据,先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;
如果要将程序中的数据写入Channel,一样先让程序将数据放入buffer中,在将Buffer里面的数据写入channel
Pipe.SinkChannel Pipe.SourceChannel适用于支持线程之间通信的管道Channel
ServerSocketChannel SocketChannel适用于支持TCP网络通信的Channel
DatagramChannel适用于支持UDP网络通信的Channel
所有的Channel不通过构造器来直接创建,而是通过传统的节点InputStream,
OutPutStream的getChannel()方法
。
Channel中最常用的方法:
map(): 用于将Channel对应的部分或全部数据英社称ByteBuffer
read() 或 write()都有一定的重载形式。用于从Buffer中读取或写入数据。
public class FileChannelTest {
public static void main(String[] args) {
File f = new File("FileChannelTest.java");
try (FileChannel inChannel = new FileInputStream(f).getChannel();
FileChannel outChannel = new FileOutputStream("a.txt").getChannel();) {
// 将FileChannel里面的全部数据英社称ByteBuffer
MappedByteBuffer byteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
// 使用GBK的字符集来创建解码器
Charset charSet = Charset.forName("GBK");
// 直接将Buffer中的数据全部输出
outChannel.write(byteBuffer);
// 再次调用buffer的clear方法,复原limit和position的位置
byteBuffer.clear();
// 创建解码器
CharsetDecoder decoder = charSet.newDecoder();
// 使用解码器将byteBuffer转换成charbuffer
CharBuffer chBuffer = decoder.decode(byteBuffer);
System.out.println(chBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}