目录
一、IO流
IO流是指用来处理设备之间的数据传输的流
1、IO流的分类:
流向:
输入流 读取数据,从硬盘中读取数据到Java程序中
输出流 写出数据,从Java程序中输出数据到硬盘中
数据类型:
字节流
字节输入流 读取数据 InputStream
字节输出流 写出数据 OutputStream
字符流
字符输入流 读取数据 Reader
字符输出流 写出数据 Writer
2、字节输入流
InputStream,这是一个抽象类,其中有一个子类FileInputStream
FileInputStream的构造方法
FileInputStream(File file):
通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的File对象file命名。
FileInputStream(String string):
通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。
字节输入流读取数据的两种方式:
FileInputStream的成员方法
public int read():从该输入流读取一个字节的数据
public int read(byte[] b):从该输入流读取最多b.length个字节的数据为字节数组
代码举例:read()第一种方式,一次读取一个字节
import java.io.FileInputStream;
public class Demo1 {
public static void main(String[] args) throws Exception{
// File file = new File("f.txt");
// FileInputStream fis = new FileInputStream(file);
// 当封装一个不存在的文件,报错:系统找不到指定的文件。
FileInputStream fis = new FileInputStream("d.txt");
int i=0;
// 一次读取一个字节,返回的是ASCII码值
while((i= fis.read())!=-1){
System.out.print((char)i);//强制类型转换
}
fis.close();
}
}
输出结果:d.txt中的内容
read(byte[] b) 第二种方式,一次读取一个字节数组
import java.io.FileInputStream;
public class Demo2 {
public static void main(String[] args) throws Exception{
FileInputStream fis = new FileInputStream("a.txt");
byte[] bytes = new byte[1024];
// 定义一个字节数组,将实际获取到的字节存储到数组中,返回的是,数组实际读取到的字节数
int length=0;
// 一次读取一个字节数组
while ((length= fis.read(bytes))!=-1){
System.out.println(new String(bytes, 0, length));
// 获取数组中从0到实际读取的字节数的字节,不然数组中的默认值也会被获取到
}
fis.close();
}
}
输出结果:a.txt中的内容,由于一次获取的字节数组长度远大于文件中存储内容的字节数,所以实际在执行过程中,while循环只执行了一次,直接将文件中的所有内容存储到一个数组中,然后输出了这一个数组中实际获取的数据。
举例:
需求:将当前项目下的a.txt的内容复制到b.txt中
代码实现:
import java.io.FileInputStream;
import java.io.FileOutputStream;
/*
需求:将当前项目下的a.txt的内容复制到b.txt中
*/
public class Demo3 {
public static void main(String[] args) throws Exception {
// 创建字节输入输出流对象
FileInputStream fis = new FileInputStream("a.txt");
FileOutputStream fos = new FileOutputStream("b.txt");
int length=0;
byte[] bytes = new byte[1024];
while((length=fis.read(bytes))!=-1){
fos.write(bytes,0,length);
}
fos.close();
fis.close();
}
}
输出结果:b.txt中的内容被修改为a.txt中的内容
3、字节缓冲流
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,字节缓冲流使得读写速度更为高效
字节缓冲输出流:BufferedOutputStream
构造方法:
BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
字节缓冲输入流:BufferedInputStream
构造方法:
BufferedInputStream(InputStream in):创建一个 BufferedInputStream并保存其参数,输入流 in ,供以后使用。
字节缓冲输出流举例:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
public class Bdemo1 {
public static void main(String[] args) throws Exception{
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d.txt"));
bos.write("大数据".getBytes());
bos.close();
}
}
输出结果:
字节缓冲输入流举例:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class Bdemo2 {
public static void main(String[] args) throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d.txt"));
byte[] bytes = new byte[1024];
int length = 0;
while ((length = bis.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, length));
}
}
}
输出结果:
字节流一次只读一个字节的时候,如果数据是中文,出现了我们看不懂的符号,这是由于中文是三个字节或者两个字节组成,只一个字节的时候,将汉字转换成的字节拆分开进行输出的。
对此引入一个解码加密的方法:
加密解码:
String(byte[] bytes, Charset charset)
构造一个新的String由指定用指定的字节的数组解码charset 。解码
byte[] getBytes(Charset charset)
使用给定的charset将该String编码为字节序列,将结果存储到新的字节数组中。 编码
编码:把看得懂的变成看不懂的,类似于加密
String -- byte[]
解码:把看不懂的变成看得懂的,类似于解密
byte[] -- String
public class StringDemo {
public static void main(String[] args) throws Exception {
String s = "你好";
//String -- byte[]
// byte[] bytes1 = s.getBytes("GBK");
System.out.println(bytes1);
// printArray(bytes1);
byte[] bytes2 = s.getBytes("Unicode");
printArray(bytes2);
System.out.println();
//byte[] -- String
String s1 = new String(bytes2, "Unicode");
System.out.println(s1);
}
public static void printArray(byte[] bytes){
for (byte b : bytes){
System.out.print(b+",");
}
}
}
输出结果:
4、字符流
字符流的分类
字符输入流:Reader
字符输出流:Writer
把字节流转换为字符流:字符流 = 字节流 + 编码表
字符输出流
Writer
OutputStreamWriter:字符输出流,是以字节流加上编码表得到的一个转换流,后期写数据的时候可以根据自己指定的编码进行写入
OutputStreamWriter的构造方法
public OutputStreamWriter(OutputStream out):
创建一个使用默认字符编码的OutputStreamWriter。根据默认的编码用字符作为桥梁将字节流的数据转换为字符流
public OutputStreamWriter(OutputStream out,String charsetName):
根据指定的编码用字符作为桥梁将字节流的数据转换为字符流
代码举例:
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class Demo4 {
public static void main(String[] args) throws Exception{
// 创建字符输出流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e.txt"), "GBK");
// 此时若是没有文件,则会创建该文件
osw.write("中国万岁");
osw.close();
}
}
输出结果:e.txt文件中的内容
这里发现文件的内容并不是代码中所写的汉字
原因:这里文件中的输出格式是UTF-8形式,UTF-8以三个字节为一个汉字,代码中使用的格式是GBK,GBK以两个字节为一个汉字,格式虽然不对,但实际内容以及写入了文件中,转换格式为GBK就可以看到内容。
OutputStreamWriter 写数据的方法
public void write(int c):写一个字符
public void write(char[] cbuf):写一个字符数组
public void write(char[] cbuf,int off,int len):写一个字符数组的一部分,从off开始,长度为len的数据
public void write(String str):写一个字符串
public void write(String str,int off,int len):写一个字符串的一部分,从off开始,长度为len的数据
代码举例:
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class Demo5 {
public static void main(String[] args) throws Exception{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("f.txt"));
// 写一个字符
osw.write('a');//写a
// 写一个字符数组的一部分
char[] chars = new char[]{'a','b','c','d','e','f'};
osw.write(chars);//写abcdef
// 写一个字符数组的一部分
osw.write(chars,3,2);//写def
// 写一个字符串
osw.write("CHINA");//写CHINA
// 写一个字符串的一部分
osw.write("CHINESE",4,3);//写ESE
osw.close();
}
}
输出结果:
在运行前若是将代码中的close()去掉,数据将不写入文件中
原因:文件实际上在硬盘上存储的是字节,需要手动转换一下存储。
解决办法:使用刷新流:void flush(),这样就可以成功写入数据
进一步说明close()方法底层源码中含有flush()方法
flush()与close()的区别:
1、调用完flush(),流对象并没有关闭,可以继续写数据
2、调用完close()后,流对象就被关闭了,后续无法再通过这个对象写数据
5、字符输入流
Reader
InputStreamReader:字符输入流
InputStreamReader的构造方法
public InputStreamReader(InputStream in):
读取数据,根据默认的编码用字符作为桥梁将字节流的数据转换为字符流
public InputStreamReader(InputStream in,String charsetName):
读取数据,根据指定的编码用字符作为桥梁将字节流的数据转换为字符流
InputStreamReader读数据的方法(成员方法)
public int read():读一个字符
public int read(char[] cbuf):将字符读入数组
代码举例:第一种方式:一次读一个字符
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Demo6 {
public static void main(String[] args) throws Exception{
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
int i=0;
// 一次读取一个字符
while((i= isr.read())!=-1){
System.out.print((char)i);
}
isr.close();
}
}
第二种方式:一次读取一个字符数组
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Demo6 {
public static void main(String[] args) throws Exception{
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));;
System.out.println("========================");
// 一次读取一个字符数组
int length=0;
char[] chars = new char[1024];
while((length= isr.read(chars))!=-1){
System.out.println(new String(chars,0,length));
//使用匿名对象将获取到的字符转为字符串输出
}
isr.close();
}
}
输出结果:这两种方式都可以读到内容
结果相同,分别运行得到结果:
举例:
需求:把当前项目目录下的a.txt内容复制到当前项目目录下的c.txt中
这里与上面的例子相同,但这里使用字符输入输出流进行实现:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
/*
把当前项目目录下的a.txt内容复制到当前项目目录下的c.txt中
*/
public class Demo7 {
public static void main(String[] args) throws Exception{
// 创建字符输入输出流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c.txt"));
// 这里使用第二种方式:一次读取一个字符数组
int length=0;
char[] chars = new char[1024];
while((length= isr.read(chars))!=-1){
osw.write(chars,0,length);
osw.flush();
}
// 释放资源
osw.close();
isr.close();
}
}
输出结果:
由于我们常见的操作都是使用本地默认的编码,我们基本上使用的时候不去指定编码 但是我们在创建字符转换流对象的时候,名字有点长,所以Java提供了子类给我们使用
字符流 = 字节流 + 编码表
OutputStreamWriter = FileOutputStream + 编码表 = FileWriter
InputStreamReader = FileInputStream + 编码表 = FileReader
代码举例:复制操作:
import java.io.FileReader;
import java.io.FileWriter;
public class Demo8 {
public static void main(String[] args) throws Exception{
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("f.txt");
char[] chars = new char[1024];
int length = 0;
while ((length=fr.read(chars))!=-1){
fw.write(chars,0,length);
fw.flush();
}
}
}
输出结果:f.txt中出现了a.txt中的内容
6、字符缓冲流
字符流为了高效读写,也提供了字符缓冲流
字符缓冲输出流:BufferedWriter:
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。 可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途。
BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
代码举例:
import java.io.BufferedWriter;
import java.io.FileWriter;
public class Bdemo3 {
public static void main(String[] args) throws Exception {
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a2.txt")));
BufferedWriter bw = new BufferedWriter(new FileWriter("a2.txt"));
bw.write("hello");
bw.write("\r\n");
bw.write("world");
bw.write("\r\n");
bw.write("java");
bw.write("\r\n");
bw.write("hadoop");
bw.flush();
//释放资源
bw.close();
}
}
输出结果:
字符缓冲输入流:BufferedReader:
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。 可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
代码举例:
import java.io.BufferedReader;
import java.io.FileReader;
public class Bdemo4 {
public static void main(String[] args) throws Exception {
// BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("a2.txt")));
BufferedReader br = new BufferedReader(new FileReader("a2.txt"));
//1、一次读取一个字符
// int ch = 0;
// while ((ch=br.read())!=-1){
// System.out.print((char) ch);
// }
//2、一次读取一个字符数组
char[] chars = new char[1024];
int length = 0;
while ((length=br.read(chars))!=-1){
System.out.println(new String(chars,0,length));
}
//释放资源
br.close();
}
}
输出结果:
对图片进行复制到文件下也是一样:
代码举例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Cdemo5 {
public static void main(String[] args) throws Exception{
FileInputStream br = new FileInputStream("哆啦A梦.jpg");
FileOutputStream bw = new FileOutputStream("D:\\IdeaProjects\\dlam.jpg");
int length=0;
byte[] chars = new byte[1024];
while((length=br.read(chars))!=-1){
bw.write(chars,0,length);
}
bw.close();
br.close();
}
}
输出结果:
在本项目下生成了一个jpg文件:
7、字符流特殊的写数据方法:
BufferedWriter:
void newLine() 写一行行分隔符。 行分隔符字符串由系统属性line.separator定义,并不一定是单个换行符('\ n')字符。
BufferedReader:
public String readLine() 读一行文字。 一行被视为由换行符('\ n'),回车符('\ r')中的任何一个或随后的换行符终止。
代码举例:这里不再抛出异常,使用开发中标准的写法:
import java.io.*;
public class Bdemo5 {
public static void main(String[] args) {
try {
write();
} catch (Exception e) {
e.printStackTrace();
}
try {
read();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void write() throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter("f.txt"));
for(int i=1;i<=10;i++){
bw.write("大数据"+i);
bw.newLine();
}
bw.close();
}
public static void read() throws Exception {
BufferedReader br = new BufferedReader(new FileReader("f.txt"));
// public String readLine() 一次读取一行文字
//包含行的内容的字符串,不包括任何行终止字符,如果已达到流的末尾,则为null
String line=null;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
输出结果:f.txt中含有如下读取的数据:
8、操作基本数据类型的流
DataOutputStream
DataInputStream
代码举例:写数据
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class Demo1 {
public static void main(String[] args) throws Exception{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("d.txt"));
dos.writeByte(1);
dos.writeShort(5);
dos.writeInt(10);
dos.writeLong(1000L);
dos.writeChar('a');
dos.writeFloat(12.34F);
dos.writeDouble(34.56);
dos.writeBoolean(true);
dos.close();
}
}
输出结果:在d.txt文件中,内容为乱码
但内容已经写入到d.txt中。
读取基本数据类型的数据:
注意:这里需要保证写入数据的数据类型与读取数据的数据类型顺序保持一致。
import java.io.DataInputStream;
import java.io.FileInputStream;
public class Demo1a {
public static void main(String[] args) throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("d.txt"));
System.out.println(dis.readByte());
System.out.println(dis.readShort());
System.out.println(dis.readInt());
System.out.println(dis.readLong());
System.out.println(dis.readChar());
System.out.println(dis.readFloat());
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
dis.close();
}
}
输出结果:
二、序列化
序列化:就是把对象按照流一样的方式存放到文本文件或者数据库或者在网络中传输
对象---流数据:ObjectOutputStream
反序列化:把文本文件中的流对象数据或者网络中的流数据还原成一个对象
流数据---对象:ObjectInputStream
代码举例:
Person类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Demo1b {
public static void main(String[] args) {
// 写方法:将对象存入到文件中,其实就是把对象进行持久化
try {
write();
} catch (Exception e) {
e.printStackTrace();
}
// 读方法:把文本文件中的流对象数据或者网络中的流数据还原成一个对象
//
try {
read();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void write() throws Exception{
// 创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("g.txt"));
Person person = new Person("超人",20);
oos.writeObject(person);
oos.close();
}
public static void read() throws Exception{
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("g.txt"));
Object o = ois.readObject();
System.out.println(o);
ois.close();
}
}
这里会出现报错:NotSerializableException 未序列化异常
解释:类的序列化由实现java.io.Serializable接口的类启用。不实现此接口的类将不会使任何状态序列化或反序列化。可序列化类的所有子类型都是可序列化的。
序列化接口没有方法或字段,仅用于标识可串行化的语义。所以需要实现Serializable接口即可
不用重写任何方法。
实现接口后的Person类:
import java.io.Serializable;
public class Person implements Serializable {
private String name;
int age;
public Person() {
}
public Person(String name, int age) {
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
再次运行后的结果:
但此时若是将Person类中age的权限修饰符去掉进行改动,再单独运行读取数据方法,则会报错:InvalidClassException
解释:这里Serializable表示的是一个标记,相当于一个标记值,若一开始给的标记值为100,则写数据与读数据的标记值都是100,在写完数据读取数据完毕后,对Person类进行修改,此时会对改动后的Person类生成一个新的标记值200,当使用该标记进行读取数据时,Person标记为200,但写数据和读数据的标记并未修改,仍然是100,故标记找不到,所以报错。
解决办法:
有一种方法使得对类进行修改时,不会影响到标记值,Java提供了一个ID值:serialVersionUID
同时若是不希望某个值存储到文件中,Java提供一个关键字让我们在序列化过程中可以选择哪些成员不被序列化:transient
最终代码:
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -1408968761740608781L;
private String name;
private transient int age;
public Person() {
}
public Person(String name, int age) {
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出结果:
年龄变为默认值0