流
部分内容来自这里
问:Java中有几种类型的流?
在Java使用流的机制进行数据的传送,从文件到内存是输入流,从内存到文件是输出流。
Java中有字节流和字符流。字节流继承于InputStream/OutputStream,字符流继承于Reader/Writer。
在java.io包中还有许多其他的流,低层流与高层流,高层流主要是为了提高性能和使用方便。
图 1
问:字符流与字节流
要把一片二进制数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式即为IO流,对应的抽象类为OutputStream和InputStream,它们都是针对字节进行操作的。
在应用中,经常要将完全是字符的一段文本输出去或读进来,直接用字节流可以吗?计算机中的一切最终都是以二进制的字节形式存在。对于“中国”这些字符,首先需要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。
底层设备永远只接受字节数据,有时候要将字符串写到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,它直接接受字符串,内部将字符转换成字节,再写入底层设备,这为我们向IO设备写入或读取字符串提供了一点点方便。
在将字符转换为字节时,需要注意编码问题:因为字符串转换成字节数组,其实是将每个字符转换成某种编码的字节形式,读取也是反之的道理。
例1:字节流和字符流的使用
String str="Hello World!";
字节流:
FileOutputStream fos=new FileOutputStream("content.txt");
fos.write(str.getBytes("UTF-8"));
fos.close();
FileInputStream fis = new FileInputStream("content.txt");
byte[] buf = new byte[1024];
int len =fis.read(buf);
String myStr = new String(buf, 0, len, "UTF-8");
System.out.println(myStr);
字符流:
FileWriter fw =new FileWriter("content.txt");
fw.write(str);
fw.close();
FileReader fr =new FileReader("content.txt");
char[] buf = new char[1024];
int len =fr.read(buf);
String myStr = new String(buf, 0, len);
System.out.println(myStr);
例2:编程实现文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Main {
// 工具类中的方法都是静态方式访问的,因此将构造器设为私有不允许创建对象
private Main() {
throw new AssertionError();
}
public static void main(String[] args) {
fileCopy("file1.txt", "file2.txt");
//fileCopyNIO("file1.txt", "file2.txt");
}
public static void fileCopy(String source, String target) {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(source);
out = new FileOutputStream(target);
byte[] buffer = new byte[4096];
int bytesToRead;
while ((bytesToRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} catch (IOException e) {
} finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void fileCopyNIO(String source, String target) {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(source);
out = new FileOutputStream(target);
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
while (inChannel.read(buffer) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
}
}
}
问:节点流与处理流
流可以分为节点流和处理流:
- 节点流可以从一个特定的数据源(如文件、内存等)读写数据;
图 1
- 而处理流则是连接在已存在的节点流或处理流之上,通过对数据的处理为程序提供更强大的读写功能。
图 2
常用的节点流
图 1
常用的处理流
图 2
节点流向处理流转换的实例:
FileInputStream(System.in) -> InputSteamReader -> BufferReader
OutputSteam(System.out) -> PrintStream
FileReader -> BufferedReader
FileWriter -> PrintWriter或bufferWriter
例1:处理流的使用
String str="Hello World!";
PrintWriter pw =new PrintWriter("content.txt", "utf-8");
pw.write(str);
pw.close();
BufferedReader br =new BufferedReader(new InputStreamReader(new FileInputStream("content.txt"), "UTF-8"));
String myStr =br.readLine();
br.close();
System.out.println(myStr);
例2:写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数
/**
* 统计给定文件中给定字符串的出现次数
*
* @param filename 文件名
* @param word 字符串
* @return 字符串在文件中出现的次数
*/
public static int countWordInFile(String filename, String word) {
BufferedReader br = null;
int counter = 0;
try {
br = new BufferedReader(new FileReader(filename));
String line = null;
while ((line = br.readLine()) != null) {
int index = -1;
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;
line = line.substring(index + word.length());
}
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
if(br!=null)
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return counter;
}
问:IO与NIO
问:序列化
序列化是为了解决在对对象流进行读写操作时所引发的问题(如果不进行序列化可能会存在数据乱序的问题)。序列化就是一种用来处理对象流的机制,所谓对象流就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
Java序列化是指将对象转换为字节序列的过程,而反序列化则是将字节序列转换成目标对象的过程。
我们都知道,在进行浏览器访问的时候,我们看到的文本、图片、音频、视频等都是通过二进制序列进行传输的,那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络、IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信。
问:序列化的实现
方式一
(1) 定义类实现Serializable接口,并生成一个版本号
import java.io.Serializable;
public class People implements Serializable{
private static final long serialVersionUID = 1942699735300957387L;
private transient String id;
private String name;
private String age;
public People(String id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public People(){
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Student{id='" + id + "\', name='" + name + "\', age='" + age + "\'}";
}
}
(2) 使用ObjectOutputStream类中的writeObject()方法将对象进行序列化;使用ObjectInputStream类中的readObject()方法反序列化得到对象
public static void main(String[] args){
File file = new File("D:/test.txt");
People people = new People("12", "齐天大圣", "500");
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(people);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
People p = (People) ois.readObject();
System.out.println(p.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注意:
- Serializable接口中并没有提供任何方法,它只是一个标识性接口,标识这个类的对象需要被序列化,javac编译时会进行特殊处理;然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject()方法从流中读取对象。
- 序列化版本号serialVersionUID的作用是区分我们所编写的类的版本,用于判断反序列化时类的版本是否一致,如果不一致会出现版本不一致异常;
- 关键字transient用来忽略我们不希望进行序列化的变量,在被反序列化后,transient变量的值被设为初始值,如int型的是0,引用型变量的值是null。
方式二
(1) 定义类实现Externalizable接口,实现接口中的writeExternal()和readExternal()方法实现对象的序列化。Externaliable接口是Serializable的子接口,有着和Serializable接口同样的功能。
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class People implements Externalizable{
private transient String id;
private String name;
private String age;
public People(String id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public People(){
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Student{id='" + id + "\', name='" + name + "\', age='" + age + "\'}";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(id);
out.writeObject(name);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = (String) in .readObject();
name = (String) in.readObject();
age = (String) in.readObject();
}
}
(2)
public static void main(String[] args){
File file = new File("D:/test.txt");
People people = new People("12", "齐天大圣", "500");
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
people.writeExternal(oos);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
People p = new People();
p.readExternal(ois);
System.out.println(p.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
问:transient关键字的作用?如何将transient修饰符修饰的变量序列化?
当使用transient关键字修饰一个变量时,这个变量将不会参与序列化过程,也就是说它不会在网络操作时被传输,也不会在本地被存储下来,这对于保护一些敏感字段(如密码等)非常有帮助。
- 变量被transient关键字修饰后,反序列化后不能获取到该变量的值;
- transient只能修饰变量,不能修饰方法。修饰我们自定义类的变量时,这个类一定要实现Serializable接口;
- 由于static修饰的变量是属于类的,而序列化是针对对象的,因此static修饰的变量不能被序列化。
例1:当一个类实现了Serializable接口,这个对象的所有字段就可以被自动序列化。在持久化对象时,我们可能不想让一些特殊字段随着网络传输过去,或者在本地被序列化缓存起来,这时我们就可以在这些字段前加上transient关键字修饰,被transient修饰的变量的值将不会包括在序列化的表示中,也就不会被保存下来。这个字段的生命周期仅存在于调用者的内存中。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class UserBean implements Serializable {
private static final long serialVersionUID = 856780694939330811L;
private String userName;
private transient String password; // 此字段不需要被序列化
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
public class Main {
public static void main(String[] args) {
// 序列化
try {
UserBean bean = new UserBean();
bean.setUserName("name");
bean.setPassword("password");
System.out.println("序列化前--->userName:" + bean.getUserName() + ",password:" + bean.getPassword());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:/userbean.txt"));
oos.writeObject(bean);// 将对象序列化缓存到本地
oos.flush();
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:/userbean.txt"));
UserBean bean = (UserBean) ois.readObject();
ois.close();
System.out.println("反序列化后获取出的数据--->userName:" + bean.getUserName() + ",password:" + bean.getPassword());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//输出:
序列化前--->userName:name,password:password
反序列化后获取出的数据--->userName:name,password:null
注意:如果给password加上static关键字修饰,反序列化后依然能够获取到值,但是这个时候的值是JVM内存中对应的static的值,因为static修饰后,它属于类不属于对象,存放在一块单独的区域,直接通过对象也是可以获取到这个值的。
我们可以通过实现Externalizable接口,实现writeExternal()和readExternal()方法,然后再自定义序列化对象。