一、I/O流基本类的继承关系及简单应用
I/O流按不同的属性可以分为不同的类型:
- 输入流(InputStream、Reader);输出流(OutputStream、Writer)
- 用于处理字节的输入输出流(InputStream、OutputStream);用于处理字符的输入输出流(Reader、Writer)
InputStream、OutputStream、Reader和Writer都属于抽象基类,不能直接new对象操纵I/O流。因此,我们必须使用其实现类进行操纵:
1. InputStream常用的实现类:FileInputStream 和 ByteArrayInputStream
public static void main(String[] args) {
//创建源文件
File src = new File("D:/Java/abc.txt");
//选择流
InputStream is = null;
try {
is = new FileInputStream(src);
//操作
byte[] flush = new byte[1024];//缓冲容器
int len = -1;//返回值为-1表示读完了
while ( (len = is.read(flush)) != -1 ) {
//字节数组-->字符串(解码)
String str = new String(flush,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//-------------------------------------------------
public static void main(String[] args) {
//创建源:字节数组不要太大
byte[] src = "talk is cheap show me the code".getBytes();
//选择流
InputStream is = null;
try {
//操作
is = new ByteArrayInputStream(src);
int len = -1;
byte[] flush = new byte[5];
while((len = is.read(flush)) != -1) {
String str = new String(flush,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. OutputStream常用的实现类: FileOutputStream 和 ByteArrayOutputStream
public static void main(String[] args) {
//1、创建源
File src = new File("dest.txt");
//2、选择流
OutputStream os = null;
try {
os = new FileOutputStream(src,true);//追加
//3、操作
String str = "hhhhh";
byte[] datas = str.getBytes();
os.write(datas, 0, datas.length);
os.flush();//刷新
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(null != os) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//--------------------------------------------
public static void main(String[] args) {
byte[] dest = null;
//2、选择流
ByteArrayOutputStream bos = null;
try {
bos = new ByteArrayOutputStream();
//3、操作
String str = "show me the code";
byte[] datas = str.getBytes();//编码
bos.write(datas, 0, datas.length);
bos.flush();
//获取数据
dest = bos.toByteArray();
System.out.println(new String(dest,0,dest.length));
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(null != bos) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. Reader常用实现类: FileReader
public static void main(String[] args) {
File src = new File("abc.txt");
Reader reader = null;
try {
reader = new FileReader(src);
int len = -1;
char[] flush = new char[1024];
while((len = reader.read(flush)) != -1) {
String str = new String(flush,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
4. Writer常用实现类: FileWriter
public static void main(String[] args) {
File src = new File("abc.txt");
Writer writer = null;
try {
writer = new FileWriter(src,true);
String msg = "jjjjj";
//第一种方式,转为字符数组进行写
char[] datas = msg.toCharArray();
writer.write(datas, 0, datas.length);
//第二种方式,直接写
writer.write(msg);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
二、加入缓冲流(装饰器)
I/O操作中为加快速度,加入了缓冲流的概念(其本质为I/O流的装饰器,需要以普通的I/O流为修饰对象,实际上操纵数据的还是第一部分讲到的四种基本I/O操作),I/O中的缓冲流有:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
1.不带缓冲的流的工作原理:
- 它读取到一个字节/字符,就向用户指定的路径写出去,读一个写一个,所以就慢了。
2.带缓冲的流的工作原理:
- 读取到一个字节/字符,先不输出,等凑足了缓冲的最大容量后一次性写出去,从而提高了工作效率
- 优点:减少对硬盘的读取次数,降低对硬盘的损耗,提升输入输出速度。
//以BufferedInputStream为例,其余类似
public static void main(String[] args) {
//创建源
File src = new File("abc.txt");
//选择流
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(src));
//操作,一个字节的读取
byte[] flush = new byte[1024];
int len = -1;
while ( (len = is.read(flush)) != -1 ) {
String str = new String(flush,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、转换流:连接字节流与字符流的桥梁
InputStreamReader和OutputStreamWriter是字符和字节的桥梁,也可称之为字符转换流。原理:字节流+编码。
FileReader和FileWriter作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接使用子类完成操作,简化代码。一旦要指定其他编码时,不能使用子类,必须使用字符转换流。否则容易出现乱码的问题!!!
Reader r = new InputStreamReader(System.in,"UTF-8");//指定CharSet防止乱码
- 这里以操作网络流为例,实现下载文件源码 >网络爬虫的基本操作(爬取百度首页源码),既使用了缓冲流,也实现了转换流
public static void main(String[] args){
//try with不用释放资源
try(BufferedReader read =
new BufferedReader(
new InputStreamReader(
new URL("http://www.baidu.com").openStream(),"UTF-8"));
BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("baidu.html")));
){
String line = null;
while((line = read.readLine()) != null) {
writer.write(line);
writer.newLine();
}
writer.flush();
}catch (Exception e) {
System.out.println("操作异常");
}
}
四、数据流:DataInputStream 和 DataOutputStream
- DataInputStreams是InputStream的子类。
- DataOutputStreams是OutputStream的子类。
以DataOutputStreams为例进行讲解(DataInputStreams类似):此类继承自FillterOutputStream类,继承了close和write方法;同时实现DataOutput接口,在DataOutput接口定义了一系列写入各种数据的方法。DataOutputStreams是对普通流的功能的一个扩展,可以更加方便地读取int,long,字符等类型数据 。
- 功能: 实现八种基本类型数据的输入/输出。 同时,也可实现字符串的输入/输出。
- 特点: 八种基本类型的数据在输入/输出时,会保持类型不变。(适合在网络上实现基本类型数据和字符串的传递)
- 数据流属于处理流,它必须套接在节点流上使用。(与BufferedOutputStream地位类似,使用方法类似)
- 注意: 数据流在读取与存储时的顺序要一致。否则,读取数据会失真。
public static void main(String[] args) throws IOException {
//写出
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
dos.writeUTF("啊哈哈");
dos.writeInt(66);
dos.writeBoolean(true);
dos.writeChar('a');
dos.flush();
byte[] datas = baos.toByteArray();
//----------------------------------
//按写出顺序读入
ByteArrayInputStream bais = new ByteArrayInputStream(datas);
DataInputStream dis = new DataInputStream(new BufferedInputStream(bais));
String s = dis.readUTF();
int age = dis.readInt();
boolean flag = dis.readBoolean();
char ch = dis.readChar();
System.out.println(s + age + flag + ch);
}
五、对象流:ObjectInputStream 和 OjbectOutputSteam
用于存储和读取对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
原理:对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
- 序列化(Serialize):用ObjectOutputStream类将一个Java对象写入IO流中(将一个特定的数据结构转换为一组字节的过程)
- 反序列化(Deserialize):用ObjectInputStream类从IO流中恢复该Java对象(将一组字节转换为特定的数据结构的过程 )
- 持久化:将数据写入硬盘长久保存的过程称之为持久化
- 注意:只能将支持 java.io.Serializable 接口的对象写入流中,因此对象必须实现Serializable接口
1.对象类Employee
public class Employee implements Serializable{
private int id;
private transient String ename;//transient:该数据不许要序列化
private double salary;
public Employee(int id, String ename, double salary) {
super();
this.id = id;
this.ename = ename;
this.salary = salary;
}
@Override
public String toString() {
return super.toString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
2.写出(序列化)与读取(反序列化)
public static void main(String[] args) throws IOException, ClassNotFoundException {
//写出--》序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(baos));
oos.writeObject(new Date());
oos.writeObject(new Employee(11, "dada", 44.2));
oos.flush();
//-------------------------------
//反序列化
byte[] datas = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(datas);
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(bais));
//数据还原
Object date = ois.readObject();
Object emp = ois.readObject();
if(date instanceof Date) {
Date dateObj = (Date)date;
System.out.println(dateObj);
}
if(emp instanceof Employee) {
Employee empObj = (Employee)emp;
//name不能输出,因为未序列化
System.out.println(empObj.getId() + "-" + empObj.getEname());
}
}
六、使用I/O流实现简单的文件copy操作,创建工具类FileUtils
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FileUtils {
/**
* try .. with .. resource,释放资源 jdk9之后
* 直接在try(is;os)释放资源
* 实现文件copy
* @param is:输入
* @param os:输出
*/
public static void copy(InputStream is, OutputStream os) {
try {
//操作
int len = -1;
byte[] flush = new byte[1024];
while((len = is.read(flush)) != -1) {
os.write(flush,0,len);
}
//编码
} catch (IOException e) {
e.printStackTrace();
}finally {
//先打开的后关闭,分别关闭
close(is,os);
}
}
/**
* 传入可变参数,来几个closeable接几个
* 关闭IO流
* @param ios io流组
*/
public static void close(Closeable... ios) {
for(Closeable io : ios) {
try {
if(null != io) {
io.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}