流的概念(必看)
什么是流
流是一组有序的,有起点和终点的字节集合,是对计算机中数据传输的总称或者抽象,即数据在两个设备间的传输称为流,流的本质是数据传输。
流序列中的数据可以是没有进行加工的原始数据(二进制字节数据),也可以是经过编码的符合某种格式规定的数据,Java中提供了不同的流类对它们进行处理。
比如网上有个 视频——放在服务器上(配置很高的电脑)。现在在线观看电影 ,会进行一点一点加载,这就是电影数据从服务器上正在一点一点流到我们的电脑上。
输入流和输出流都是从jvm角度来讲的,把任何外接数据读到java中,这就是输入流,把任何java中的数据写到外界,都是输出流。
输出流代表:System.out.println
1. 起点:jvm
2. 终点:控制台
输入流代表: new Scanner()
1. 起点:键盘
2. 终点:jvm
示意图
流可以理解成一个管道中传输东西(数据),流有两个端点,起始位置-目的地,一端发送,一端接收。
流的分类
按照流传输方向不同
1. 输入流(InputStream)
2. 输出流(OutputStream)
按照处理数据类型的不同
1. 字节流: 对字节流进行了加工,编码,变成了字符流
2. 字符流
按照流的基本功能不同
1. 节点流(基本流,传输最基本的字节或者字符)
2. 过滤流(对节点流的扩展,传对象,传double,String等等)
输入流(InputStream)
在Java中,程序可以打开一个输入流,输入流的信息源可以位于文件、内存或网络套接字(socket)等地方,信息源的类型可以是包括对象、字符、图像、声音在内的任何类型。一旦打开输入流后,程序就可从输入流串行地读数据。(详细请到流的概念一节)
输出流(OutputStream)
类似地,程序也能通过打开一个输出流并顺序地写入数据来将信息送至目的端。(详细请到流的概念一节)
字节流
概念
传输的数据单位是字节,也意味着字节流能够处理任何一种文件。
字节流的组成
1. 字节输入流 InputStream
2. 字节输出流 OutputStream
字节流 FileInputStream读取文件
基本知识
FileInputStream是字节输入流类。
FileInputStream构造方法
1. FileInputStream(String filename):filename指定文件目录,比如c:/abc/xx.txt
2. FileInputStream(File file): file指定File对象
常用方法
1. close()
2. int read(): 读取一个字节,返回的值是byte值,当没有读取到字节时候返回-1。
3. int read(byte[]b):返回读到数据的长度
4. int read(byte[] bs, int off, int len):返回的是读取到数据长度
读取文件
从指定文件中,读取数据。
读取文件也可以使用int read(byte[] bs, int off, int len)方法,其中off指定数据存放在byte数组的起始位置,len读取多少个数据。
读取文件—讲解
使用read方法读取文件时,会一个一个字节的读取,当使用read()方法读取一次时,只读取到第一个字节,当再使用read()读取文件时,又会读取下一个字节,也就是说,每使用read方法读取文件时,流都会记录读取到的位置,也就是指针指在上次读取到的位置。
示意图
注意事项
非常重要:流使用完后,一定要关闭流。
字节流 FileOutputStream写文件
FileOutputStream字节输出流类
常用构造方法
FileOutputStream(String path)
FileOutputStream(File file)
FileOutputStream(String path, boolean append)
FileOutputStream(File file, boolean append)
常用方法
close()
void write(int v)
void write(byte[] bs)
void write(byte[] bs, int off, int len)
程序示例:写数据到文件中
write(byte[] b, int off, int len)中的b是要发送的byte数组,off是byte数组的起始位置,len要发送的byte数,也就是从byte数组中起始位置开始算,一共发送多少个byte。
write(byte[] b) 中的b是要发送的byte数组。
字符数组转为byte数组后长度是不一样的。
字节流的类关系图
用流复制文件(小文件极简方案)
找到任何文件,图片/视频/文档/PDF等等,将其从一个位置复制到另一个位置。
1. 程序示例: 复制图片
public static void main(String[] args) {
/**
* 把src复制到local
*/
File src = new File("E:/头像/1.jpg"); //存放文件的信息
File local = new File("F:/abc/头像.xixi");
try {
//建立连通管道 InputStream是一个接口
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(local);
//src.length()计算图片的大小
byte[] b = new byte[(int)(src.length())];
//读取数据
in.read(b);
//发送数据
out.write(b);
//关闭流,释放资源,人走关灯
in.close();
out.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这种方法,只适合小文件的传输,不适合大文件的传输,比如你传输一个10G文件,你不可能吧10G全部放在内存中,现在电脑大部分都是8G内存。
注意:一定要关闭流。
用流复制文件(通用全能方案)
以下这种复制文件方法适合大文件,原理是读点发点数据,读几个就byte就发送几个byte,否则文件会损坏。
public static void main(String[] args) {
/**
* 把src复制到local
*/
File src = new File("E:/头像/1.jpg"); //存放文件的信息
File local = new File("F:/abc/" + src.getName());
try {
//建立连通管道 InputStream是一个接口
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(local);
//存放byte的数组,最大存2048字节,这个可以自行更改
byte[] b = new byte[2048];
//方案1
int len = 0;
while(true) {
if((len = in.read(b)) != -1) {
//读取多少个字节就写多少个字节,防止破坏文件
out.write(b, 0, len);
}else {
break;
}
}
//方案2:推荐
for(int len = 0; (len = in.read(b)) != -1;) {
//读取多少个字节就写多少个字节,防止破坏文件
out.write(b, 0, len);
}
//关闭流,释放资源,人走关灯
in.close();
out.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
推荐采用方案2。
断点续传
断点续传:就是传输文件的过程中,有可能出现意外,比如断网等等可能,当再次传输时候,不想重新传,接着上次接着传输。
制造断点文件
传输一部分就停止传输文件,这就是制造断点文件。
1. 主方法中的程序如下
/**
* 把src复制到local
*/
File src = new File("E:/头像/1.jpg"); //存放文件的信息
File local = new File("F:/abc/" + src.getName());
try {
//建立连通管道 InputStream是一个接口
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(local);
//控制传输的大小
long over = 0;
//存放byte的数组,最大存2048字节,这个可以自行更改
byte[] b = new byte[2048];
//传输数据
for(int len = 0; (len = in.read(b)) != -1;) {
//读取多少个字节就写多少个字节,防止破坏文件
out.write(b, 0, len);
//计算每次传输的byte数
over += len;
//如果传输的大小大于源文件的一般就停止传输
if(over > src.length() / 2) {
System.out.println("出现断点");
break;
}
}
//关闭流,释放资源,人走关灯
in.close();
out.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
实现续传功能
将原来没有传输完的文件接着传输完成,设置断点续传核心技术:输出流设置追加,输入流跳过。
示意图:
思路
1. 首先判断文件是否存在,根据目标文件的大小是否等于源文件,如果等于就说明文件已经复制完成,如果目标文件大小小于源文件,说明之前复制的时候发生断点,需要续传,还有一种情况就是文件根本没有复制,也就是所目标文件的大小为0,重新开始传输,因为续传是根据目标文件的大小,判断从源文件的什么位置开始读取输出进行传输,因此续传和重新开始传输程序是一样的,我们可以让在判断为文件已经复制完成的条件下,直接退出程序,使用return。
/**
* 把src复制到local
* 设置断点续传核心技术:输出流设置追加,输入流跳过
*/
File src = new File("E:/头像/1.jpg"); //存放文件的信息
File local = new File("F:/abc/" + src.getName());
//找到源文件起始传输的位置,默认为0,从头开始复制传输数据
long start = 0;
//判断文件是否是断点文件
if(src.length() == local.length()) {
System.out.println("文件已经存在,不需要再次复制");
//退出程序
return;
}else if(src.length() > local.length()) {
System.out.println("断点文件,需要进行续传");
}else if(local.length() == 0) {
System.out.println("文件不存在,需要从头开始进行传输");
}
try {
//
//建立连通管道 InputStream是一个接口
InputStream in = new FileInputStream(src);
//true表示,追加数据到文件中
OutputStream out = new FileOutputStream(local,true);
//设置输入流的起始位置
start = local.length();
in.skip(start);
//存放byte的数组,最大存2048字节,这个可以自行更改
byte[] b = new byte[2048];
//传输数据
for(int len = 0; (len = in.read(b)) != -1;) {
//读取多少个字节就写多少个字节,防止破坏文件
out.write(b, 0, len);
}
//关闭流,释放资源,人走关灯
in.close();
out.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
FileOutputStream(File file, boolean append)方法: append = ture则以前文件不改变,接着追加数据到文件中,如果append = false,则情况文件,开始写入数据。
in.skip(long n)方法是指定从,从输入流文件中的第n个byte位置开始读取数据。
编写文件工具类
如何复制多个文件呢?最佳方案是编写文件工具类,专为文件处理的类,本程序放在Program/实用代码/文件工具类目录下。
/**
* 功能:这个类针对文件的操作,包括文件的复制
* @author 27823
*
*/
public class MyFileUtil {
/**
* copy方法
* InputStream in:输入流对象
* OutputStream out:输出流对象
* InputStream和OutputStream都是接口
* @throws IOException
*/
public static void copy(InputStream in, OutputStream out) throws IOException {
//存放byte的数组,最大存2048字节,这个可以自行更改
byte[] b = new byte[2048];
//传输数据
for(int len = 0; (len = in.read(b)) != -1;) {
//读取多少个字节就写多少个字节,防止破坏文件
out.write(b, 0, len);
}
//关闭流,释放资源,人走关灯
in.close();
out.close();
}
/**
* copy方法重载
* src:源文件对象信息
* target:目标文件对象的信息
* @throws IOException
*/
public static void copy(File src,File target) throws IOException {
//这块的异常不推荐在类的方法中处理
//推荐在main或者其他使用该方法的地方进行处理异常
//建立连通管道 InputStream是一个接口
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
//开始复制文件
copy(in, out);
}
/**
* copy方法重载
* Parameters: String src源文件的目录包括文件名,比如 E:/abc/abc.txt
* Parameters: String target目标文件的目录包括文件名,比如F:/abc/abc.txt
* @throws IOException
*/
public static void copy(String src,String target) throws IOException {
//获取文件对象
File f1 = new File(src);
File f2 = new File(target);
//复制文件
copy(f1,f2);
}
}
主方法
public static void main(String[] args) {
File f1 = new File("E:/头像/1.jpg");
File f2 = new File("F:/abc/1.jpg");
//MyFileUtil类测试
try {
MyFileUtil.copy("E:/头像/1.jpg", "F:/abc/1.jpg");
MyFileUtil.copy(f1, f2);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
字符集
字符(Character)是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。中文文字数目大,而且还分为简体中文和繁体中文两种不同书写规则的文字,而计算机最初是按英语单字节字符设计的,因此,对中文字符进行编码,是中文信息交流的技术基础。
java测试字符集
字符集测试程序:测试编码方式
String s1 = "天涯明月刀";
//将字符串转为byte,采用GBK编码方式
byte[] b1 = s1.getBytes("GBK");
//将字符串转为byte,采用UTF-8编码
byte[] b2 = s1.getBytes("UTF-8");
//输出两个编码方式的byte数组长度
System.out.println("GBK编码方式: " + b1.length); //GBK编码方式: 10
System.out.println("UTF-8编发方式: " + b2.length);//UTF-8编发方式: 15
//将byte数组转为字符串,并且b1的byte数组以UTF-8方式解码
String s2 = new String(b1, "UTF-8");
String s3 = new String(b2, "UTF-8");
//打印两种编码方式的字符串
System.out.println(s2);//?????????
System.out.println(s3);//天涯明月刀
字符流
概述
字符流的组成
1. Reader和Writer抽象类,FileReader和FileWrite是具体的实现,还有其他实现类。
字符流 Reader类
简介
Reader是一个抽象类,它是以字符为单位的输入流的公共父类。
构造函数
1. protected Reader() //创建一个新的字符流 reader,其重要部分将同步其自身的 reader。
2. protected Reader(Object lock) //创建一个新的字符流 reader,其重要部分将同步给定的对象。
抽象类方法
1. abstract void close() //关闭该流并释放与之关联的所有资源。
2. void mark(int readAheadLimit) //标记流中的当前位置。
3. boolean markSupported() //判断此流是否支持 mark() 操作。
4. int read() //读取单个字符。
5. int read(char[] cbuf) //将字符读入数组。
6. abstract int read(char[] cbuf, int off, int len) //将字符读入数组的某一部分。
7. int read(CharBuffer target) //试图将字符读入指定的字符缓冲区。
8. boolean ready() //判断是否准备读取此流。
9. void reset() //重置该流。
10. long skip(long n) //跳过字符。
Reader类图
BufferedReader
1. 从流里面读取文本,通过缓存的方式提高效率,读取的内容包括字符、数组和行。缓存的大小可以指定,也可以用默认的大小,大部分情况下,默认大小就够了。
InputStreamReader
1. 把字节翻译成字符的,可以处理乱码问题。
FileReader
1. 方便读取字符文件的。
FileReader类
FileReader用于以字符为单位读取文本文件,能够以字符流的形式读取文件内容。除了读取的单位不同之外,FileReader与FileInputStream并无太大差异,也就是说,FileReader用于读取文本。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。
构造函数
FileReader(File file)
//在给定从中读取数据的 File 的情况下创建一个新FileReader。
FileReader(FileDescriptor fd)
//在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。
FileReader(String fileName)
//在给定从中读取数据的文件名的情况下创建一个新FileReader。
方法
void close()
//关闭该流并释放与之关联的所有资源。
String getEncoding()
//返回此流使用的字符编码的名称。
int read()
读取单个字符。返回值为读入字符对应的int值(0~65535),如果没有读到一个字符会返回-1
int read(char[] cbuf, int offset, int length)
//将字符读入数组中的某一部分。返回值为读入字符个数
boolean ready()
//判断此流是否已经准备好用于读取。返回值为读入字符个数
读取文本与写入数据程序
一个一个字节的读取文本
往文本中写入内容,使用write方法时将会擦除文本的内容,然后进行写入。
总结
在使用FileReader 对象进行文件输入操作的时,JVM先读取本地文本文,然后将其格式转化为Unicode编码格式进行操作。再用FileWriter进行文本文件输出时则把Unicode编码格式再转换成本地(本地主机上)的编码格式(如ASCII或者GBK等)。
FileReader与FileWriter两个类和FileInputStream和FileOutputStream两个类的操作方法基本相同,只不过前者基于字符,后者基于字节(byte),若操作的文件不是文本文件,则建议使用FileInputStream和FileOutputStream进行文件的输入输出。
当使用完数据流时,一定要使用close方法将其关闭。
InputStreamReader类
InputStreamReader是一个读取流类,可以指定字符集,常用用于文件的传输。
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//异常处理
try {
//获取文件的输入流
InputStream in = new FileInputStream(ff);
//读取输入流:可以指定字符集
InputStreamReader isr = new InputStreamReader(in, "utf-8");
//读取字符缓冲区
char[] c = new char[8];
//读取文件,并且获取读取到的长度
int len = isr.read(c);
//打印读取到的字符
System.out.println(c);
//人走关灯,把资源释放掉
isr.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
字符流 Writer类
OutputStreamWrite
OutputStreamWrite灵活性比FileWrite更好。
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//异常处理
try {
//获取文件的输出流
OutputStream out = new FileOutputStream(ff);
//输出流:可以指定字符集
OutputStreamWriter osw = new OutputStreamWriter(out, "utf-8");
//写文件读取文件
String str = "根据教育部考试中心的安排";
osw.write(str);
//释放资源
osw.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
过滤流(高级流)
过滤流开发步骤
- 创建节点流:节点流只有最基本的读写功能;
- 基于节点流创建过滤流;
- 读/写数据;
- 关闭外层流;
DataOutputStream过滤流
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//因为只有最基本的写功能,因此叫做节点流
//又因为是用来处理字节的流,所以叫字节流
OutputStream out = new FileOutputStream(ff);
//基于节点流的过滤流,属于高级流
DataOutputStream dos = new DataOutputStream(out);
//调用过滤流中的高级方法写数据
//写浮点型数据
dos.writeDouble(3.14159);
//写字符串
dos.writeUTF("Hello world");
//写整数
dos.writeInt(240);
//关闭最外层流即可
dos.close();
写入数据后的文件
写入数据后呈现出乱码问题,这涉及到一个序列化问题,就是在使用write写入数据的时候,存入到byte的数据元素是有序存入的。我们可以验证是否可以正常读取出来,如果可以使用过滤流正常把数据读取出来,就说明过滤流很有价值。
这个文件只是中转数据,过滤流一般用于网络传输,就是一边发送数据一边读取数据。
DataInputStream过滤流
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//因为只有最基本的读写功能,因此叫做节点流
//又因为是用来处理字节的流,所以叫字节流
InputStream in = new FileInputStream(ff);
//基于节点流的过滤流,属于高级流
DataInputStream ins = new DataInputStream(in);
//调用过滤流中的高级方法写数据
//读浮点型数据
double d =ins.readDouble();
//读字符串
String s = ins.readUTF();
//读整数
int i = ins.readInt();
//打印读取到的数据
System.out.println(d);
System.out.println(s);
System.out.println(i);
//关闭最外层流即可
ins.close();
注意:写数据必须与读数据相对应,下图中,如果读取和写入的数据不一一对应,在读取数据时会报错,比如第二次使用writeUTF写入字符串数据,但是你不用readUTF读取字符串,而是使用readInt读取字符串,这肯定会报错的。
BufferdOutputStream过滤流
BufferedOutputStream是缓冲流,java是运行在虚拟机JVM上,而JVM是在内存条中运行了,内存的运行速度非常快,可以达到3G/s;而硬盘的速度能达到80M/s左右。
如果使用DataOutputStream将数据从内存中送到指定文件,由于文件在硬盘中,这就导致一个问题,传输的速率受到硬盘的限制,程序只能等待数据传输完成后,才能往下执行,如果传输大文件这种效率就显得很低。
如果我们使用BufferedOutputStream缓冲流将数据从内存中传到硬盘中,这个类会在内存中创建一个缓冲区,将数据秒传到缓冲区中,然后程序接着往下执行,这时数据会自动一点一点的从缓冲区中传到硬盘中,这钟传输的方法效率很高。
程序示意:使用方法跟DataOutputStream一样
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//因为只有最基本的读写功能,因此叫做节点流
//又因为是用来处理字节的流,所以叫字节流
OutputStream in = new FileOutputStream(ff);
//基于节点流的过滤流,属于高级流,带缓冲区的过滤流
BufferedOutputStream buos = new BufferedOutputStream(in);
//调用过滤流中的高级方法写数据
//写入数据
String str = "hello world";
buos.write(str.getBytes());
//关闭最外层流即可
buos.close();
ObjectStream类
ObjectStream特点
writeObject()写对象
readObject()读对象
可以传对象和读对象,也可以读byte和写byte。
对象序列化
如果想把对象通过ObjectStream类传出去,必须将对象序列化,方法是在类名后面加上implements Serializable即可,Serializable接口不需要实现任何方法,,实现这个接口之后这个类可被序列化,就可以通过流进行传输。
import java.io.*;
/**
* 该类被序列化
* @author 27823
*
*/
public class Hero implements Serializable{
private String name;
private int age;
......
}
如果类中某个属性不想被序列化,那就在该属性前加入transient 关键字,当通过高级流ObjectStream传输对象时候,添加transient关键字的属性将不被传输。
import java.io.*;
/**
* 该类被序列化
* @author 27823
*
*/
public class Hero implements Serializable{
private String name;
transient private int age;
......
}
序列化时注意事项
1. 不要使用追加的方式写对象;
2. 如果一个对象的属性又是一个对象,则要求这个属性对象也实现了Serializable接口,即在类名后面添加implements Serializable。
ObjectInputStream 类
//程序示例:从文件中读取对象
import java.io.*;
/**
* 该类被序列化
*/
public class Hero implements Serializable{
private String name;
private int age;
public Hero(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Test {
public static void main(String[] args) throws IOException {
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//因为只有最基本的读写功能,因此叫做节点流
//又因为是用来处理字节的流,所以叫字节流
InputStream in = new FileInputStream(ff);
//基于节点流的高级流,可以读取对象
ObjectInputStream ois = new ObjectInputStream(in);
//向上转型
Hero h1 = (Hero)ois.readObject();
Hero h2 = (Hero)ois.readObject();
//打印读取到的对象属性
System.out.println(h1.getName() + "\t" + h1.getAge());
System.out.println(h2.getName() + "\t" + h2.getAge());
//关闭最外层流即可
ois.close();
}
}
程序运行结果:
ObjectOutputStream类
通过ObjectOutputStream高级流,传输对象到本地文件中时,打开会看见乱码,根本看不懂,这是正常的,因为这个文件存在的意义就是缓冲接收到的数据,对象等等,我们还需要通过ObjectInputStream高级流,将对象从文件中读取出来,这时我们可以看到读取到的对象。
程序示例:将对象写入到文件中
import java.io.*;
/**
* 该类被序列化
*/
public class Hero implements Serializable{
private String name;
private int age;
public Hero(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Test {
public static void main(String[] args) throws IOException {
//创建两个对象
Hero h1 = new Hero("小米", 24);
Hero h2 = new Hero("老二",21);
//获取一个文件
File ff = new File("E:\\data\\测试文件");
//因为只有最基本的读写功能,因此叫做节点流
//又因为是用来处理字节的流,所以叫字节流
OutputStream out = new FileOutputStream(ff);
//基于节点流的高级流,可以发送对象
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(h1);
oos.writeObject(h2);
//关闭最外层流即可
oos.close();
}
}
程序运行结束后,文件中的内容如下
下一步就是要从文件中把对象读取出来了。
IO流总结
字符流
字符流主要用来处理文本的。
Reader /Write:抽象类
1. 底层也是比特流,只是经过字符编码的处理
2. 可以认为R/W就是字符转换器
FileReader,FileWriter:用于连接文件,通用性不强,因为大多数情况下我们都是通过网络进行传输。
InputStreamReader(in),OutputStreamWriter(out)使用输入输出流初始化一个字符流,in/out已知。
字符过滤流
readLine() 读取一行
writeLine()写一行
各种过滤流都是节点流的扩展。