一、概念
流:数据流是一组有顺序的,有起点和终点的字节集合,是对输入和输出的抽象
流的划分
-
数据传输方法:输入流,输出流;参照物为内存,数据从内存到磁盘为输出,从磁盘到内存为输入。
-
处理的数据单元:字节流和字符流
-
节点流和处理流:节点流指的是向指定数据源进行数据处理,处理则是从流中进行数据的操作
体系结构
抽象基类
InputStream(字节输入)
OutputStream(字节输出)
Writer(字符输出)
Reader(字符输入)
缓冲流:缓冲流通过定义缓冲区(内存中),一次性读取大量数据存入缓冲区,解决内存和磁盘传输效率低下的问题。(个人理解,如有补充,小编跪谢)
二、入门案例
1.使用字节流及其缓冲实现文件复制
package FileSty;
import java.io.*;
public class CopyAndTime {
public static void main(String[] args) {
String src = "E:/pic/34.jpg";
String desc = "E:/pic/pic1/34.jpg";
//
// new CopyFileByte().countTime(src, desc);// 共耗时2988毫秒
// new CopyFileBuffer().countTime(src, desc);// 共耗时27毫秒
// 读取一个字节数组
// new CopyFileByte().countTime(src, desc);//共耗时6毫秒
new CopyFileBuffer().countTime(src, desc);// 共耗时3毫秒
}
}
abstract class CopyFile{
abstract void copyFile(String src, String desc);
public void countTime(String src, String desc){
long start = System.currentTimeMillis();
copyFile(src, desc);
long end = System.currentTimeMillis();
System.out.println("共耗时"+(end-start)+"毫秒");
}
}
class CopyFileByte extends CopyFile{
@Override
void copyFile(String src, String desc) {
// 判断文件是否存在
File srcFile = new File(src);
if (!srcFile.exists())
return;
// 建立输入输出流进行数据读写
FileInputStream fi =null;
FileOutputStream fo = null;
try{
fi = new FileInputStream(srcFile);
fo = new FileOutputStream(desc);
byte [] buf = new byte[1024];
int len;
while ((len = fi.read(buf)) != -1){
fo.write(buf, 0, len);
}
}catch (FileNotFoundException e){
System.out.println("文件不存在");
}catch(IOException e){
e.printStackTrace();
}
finally {
try{
if (fi != null)
fi.close();
if(fo != null)
fo.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
class CopyFileBuffer extends CopyFile{
@Override
void copyFile(String src, String desc) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(new FileOutputStream(desc));
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf)) != -1 ){
bos.write(buf, 0, len);
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try {
bos.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
一些说明:理论上,字节流(1个字节8个二进制位)可以用于读取任何文件,但部分会出现编码问题,多用于读取图片、视频、音频等文件。
2.字符流实现文本文件的拷贝
// 此处异常已抛出
BufferedReader bis = new BufferedReader(new FileReader("E:\\fileTest\\test3.txt"));
BufferedWriter bos = new BufferedWriter(new FileWriter("E:\\fileTest\\test4.txt"));
char[] chs = new char[1024];
String line;
int lineNum = 1;
StringBuilder sb ;
// 逐行读取
while ((line = bis.readLine()) != null){
// 添加行号
sb = new StringBuilder(lineNum++ + "").append(line);
// 逐行写入,并换行
// WruteLine(),ReadLine()为BufferedWriter扩展的方法
bos.write(sb.toString());
bos.newLine();
}
bis.close();
bos.close();
3. 字节流读取指定编码文件
数据源(本地ide的编码为utf8)
使用字节流读取
public class Test {
public static void main(String[] args) throws IOException {
String src = "E:\\fileTest\\Test\\test.txt";
FileInputStream fi = new FileInputStream(src);
int len;
// 准备一个足够大字节数组,不会出现编码问题
byte [] by = new byte[1024];
while ((len = fi.read(by)) != -1){
System.out.println(new String(by, 0, len));
}
}
}
不出意外,乱码了
改用字符流读取
public class Test {
public static void main(String[] args) throws IOException {
String src = "E:\\fileTest\\Test\\test.txt";
FileReader fi = new FileReader(src);
int len;
// 准备一个足够大字节数组,不会出现编码问题
char [] by = new char[1024];
while ((len = fi.read(by)) != -1){
System.out.println(new String(new String(by, 0, len).getBytes("gbk")));
// 这里的乱码可能是我理解不够,代码有问题
}
}
}
依然读取失败
使用转换流读取
public class Test {
public static void main(String[] args) throws IOException {
String src = "E:\\fileTest\\Test\\test.txt";
FileInputStream fi = new FileInputStream(src);
InputStreamReader isr = new InputStreamReader(fi, "gbk");
int len;
// 准备一个足够大字节数组,不会出现编码问题
char [] by = new char[1024];
while ((len = isr.read(by)) != -1){
System.out.println(new String(by, 0, len));
}
}
}
成功读取到文件内容 ,可认为,转换为可以将字节流读取的字节序列按照指定的编码规则转换为字符
4.序列化和反序列化的使用
package WriterAndReader;
import java.io.*;
@SuppressWarnings("all")
public class Seri {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 序列化:将数据和它的数据类型转换为字节序列
// 反序列化:将某个序列化产生的字节序列恢复
// 序列化
Demo demo = new Demo(1, "阿欢", 37.0);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\fileTest\\Demo.bin"));
oos.writeObject(demo);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\fileTest\\Demo.bin"));
System.out.println(ois.readObject());
}
}
// 一个对像要序列化,则这个类必须实现 Serializable 或 Externalizable
// 一般实现Serializable接口,不需要进行方法实现
class Demo implements Serializable{
Integer id;
String name;
// 不需要序列化的属性可以通过transient进行标识
transient Double temperature;
public Demo(Integer id, String name, Double temperature) {
this.id = id;
this.name = name;
this.temperature = temperature;
}
@Override
public String toString() {
return "Demo{" +
"id=" + id +
", name='" + name + '\'' +
", temperature=" + temperature +
'}';
}
}
运行结果
5.结合Propertis使用
Properties是HashMap的子类,存储键值对,支持从输入流中加载配置文件到Propertie集合,也可用于将Properties集合中的键值对写入配置文件,写入格式为String T = String V,默认键和值为String类型,key重复时相当于更新配置文件。
6.bio、nio、aio
BIO:线程发起IO请求到IO结束,线程一直阻塞,直到操作完成。
NIO:线程发起IO请求,立即返回,期间可以做其他事;当内核有可用IO资源时,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。
AIO:线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。