IO流
IO流用来处理设备之间的数据传输,传输是通过流的方式
Java用于操作流的对象都在java.io
包中
流就是指一连串流动的字符,以先进先出的方式发送信息的通道。
流的方式
按照流的方向划分
输入流:将外部数据源的数据转换成流,程序通过读取流中的数据,完成对数据源读取的访问
输出流:将流中的数据转换到对应的数据源中,程序通过向流中写入数据,完成对数据源写入
按照类型划分
字节流:以字节为单位(8位),可以访问所有文件
字符流:以字符为单位(16位Unicode),只能访问文本文件
输出流
比如我们经常使用的 System.out.println("cocci");
就是将字符串输出到控制台中,或者说输出到屏幕中,这里的屏幕就是输出设备,由程序将cocci通过流输出到目的地。
输出设备除了屏幕,还有打印机、文件等。
输入流
比如使用键盘接收数据 Scanner sc = new Scanner(System.in);
这里的System.in就是输入流,程序从数据源这里指键盘去读取数据通过流输入到程序当中。
输入设备除了键盘,还有扫描仪、文件等。
File类
文件和目录路径名的抽象表示形式,表示磁盘上的文件或目录
文件:可认为是相关记录或放在一起的数据的集合
构造方法
示例:以下三种方式等价,各有不同的使用场景
//Windows中路径分隔可使用/或者\\
File file1 = new File("d:\\java\\a.txt");
File file2 = new File("d:\\java","a.txt");
File file3 = new File(new File("d:\\"),"java\\a.txt");
常用方法
注意事项
-
delete()
方法在删除目录时如果其内有内容则无法删除,需要先清空目录下的内容,再删除目录本身 -
isDirectory()
和isFile()
方法在判断是否为文件或者目录时,如果文件或目录不存在则返回false -
createNewFile()
方法在创建文件时,如果文件所在的目录不存在则创建失败并抛出异常 -
String[] list()
和File[] listFiles()
都是返回对象包含的所有的文件和目录,返回类型不同
文件与目录的相关操作
public static void main(String[] args) {
/*目录的创建与删除*/
File file=new File("d:\\test\\java");
if(file.exists()){
System.out.println("目录存在,删除目录");
file.delete(); //只会删除一级目录,即java
}else{
boolean b=file.mkdirs(); //创建多级目录
System.out.println("创建文件目录结果:" + b);
}
/*文件的创建与删除*/
File file2=new File("d:\\test\\a.txt");
if(file2.exists()){
System.out.println("文件存在,删除文件");
file2.delete();
}else{
try {
file2.createNewFile(); //创建文件
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("创建文件");
}
//重命名,把a.txt重命名为b.txt
//file2.renameTo(new File("d:\\test\\b.txt"));
//把b.txt剪切到java目录下并重命名为c.txt
file2.renameTo(new File("d:\\test\\java\\c.txt"));
}
获取File对象的所有子文件和目录
public class TestFile {
public static void main(String[] args) {
//获取磁盘的所有根目录
File[] files =File.listRoots();
for(int i=0;i
字节流
字节输入流
InputStream
,此抽象类是表示字节输入流的所有类的超类,其主要子类如下
字节输出流
OutputStream
,此抽象类是字节输出流的所有类的超类,其主要子类如下
过滤器输入输出流
FileInputStream
从文件系统中的某个文件中获得输入字节
用于读取诸如图像数据之类的原始字节流
构造方法
主要方法
read方法不带参数的返回值为读取的单个字节值,带参数的返回值表示读取的字节长度。如果返回值为-1,则表示已经达到文件末尾!
FileOutputStream
构造方法
主要方法
演示Demo
public class IOTest {
public static void main(String[] args) {
OutputStream fos = null;
InputStream fis = null;
try {
/********* 输出流写文件 ***********/
fos=new FileOutputStream("d:\\silly.txt");
String str="最好的我们隔了一整个青春";
byte[] words=str.getBytes(); //把字符串编码成字节序列
//写入操作
fos.write(words, 0, words.length);
System.out.println("写入成功!");
/********* 输入流读文件 ***********/
fis = new FileInputStream("d:\\silly.txt");
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024]; //字节数组缓存数据
int n = 0; //记录读取的字节长度
//循环读取数据
while((n = fis.read(buf)) != -1){
//这里使用三个参数的构造方法,因为最后一次读取的长度可能达不到buf数组的长度
//所以根据实际读取的长度n去构造对象更合理
sb.append(new String(buf, 0, n));
buf = new byte[1024]; //重新初始化,避免数据重复
}
System.out.println(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
//释放资源
if(fos != null) fos.close();
if(fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件复制
public static void main(String[] args) {
//将d盘的图片复制到桌面
InputStream is = null;
OutputStream os = null;
try {
//实例化输入输出流对象
is = new FileInputStream("d:\\girl.jpg");
os = new FileOutputStream("C:\\Users\\ruoxiyuan\\Desktop\\mm.jpg");
//定义一个2048字节的缓存
byte[] buffer=new byte[2048];
int len= 0; //读取的字节长度
while((len = is.read(buffer)) != -1){
//最后一次读取的长度len不一定能达到buffer数组的长度,也就是空间有可能富余
//如果直接使用write(buffer)方法可能导致写入更多的字节,新文件会稍大
os.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
//释放资源
if(is != null) is.close();
if(os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流
字符输入流
Reader
,此抽象类是表示字符输入流的所有类的超类,其主要子类如下
字符输出流
Writer
,此抽象类是表示字符输出流的所有类的超类,其主要子类如下
字节字符转换流
InputStreamReader:
是字节流通向字符流的桥梁,它使用指定的 charset 读取字节并将其解码为字符。
OutputStreamWriter:
是字符流通向字节流的桥梁,它使用指定的 charset 将写入其中的字符编码成字节。
其使用的字符集可以由构造方法指定,或者使用平台默认的字符集。
FileReader
用来读取字符文件的便捷类,使用默认字符集进行编码,InputStreamReader
的子类
构造方法
常用方法
FileWriter
用来写入字符文件的便捷类,使用默认字符集进行编码,OutputStreamWriter
的子类
构造方法
如果使用new FileWriter(file),已有文件内容此时会被清空,设置第二个参数为true表示追加内容
常用方法
演示Demo
public class IOTest {
public static void main(String[] args) {
try {
/*****写文件*****/
FileWriter writer = new FileWriter("d:\\silly.txt");
String str = "成功的人只会去做他们该做的事情,只会面对他们的困难,不会有抱怨,"
+ "更不会有羡慕,因为他们心中有目标,理想,动力以及那颗沉淀的心";
writer.write(str);
writer.flush(); //刷新缓冲区
/*****读文件*****/
FileReader reader = new FileReader("d:\\silly.txt");
char[] buffer = new char[1024];//定义缓冲区
int len = 0;
while((len = reader.read(buffer)) != -1){
String res = new String(buffer, 0, len);
System.out.println(res);
}
writer.close();
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲流
使用缓冲流可以提高读写速度,注意缓冲流在调用write
方法写入数据时,是写入的缓冲区,如果缓冲区满了自动执行写操作,缓冲区不满则需要执行flush
方法强制写入到输出设备,调用close
方法也会强制写入
字节缓冲流
缓冲输入流BufferedInputStream
,缓冲输出BufferedOutputStream
只是对字节流进行了包装,使用方式和字节流基本一致
构造方法:
public BufferedInputStream(InputStream in)
public BufferedOutputStream(OutputStream out)
字符缓冲流
缓冲输入流BufferedReader
构造方法:public BufferedReader(Reader in)
特殊方法:String readLine()
读取一个文本行
缓冲输出流BufferedWriter
构造方法:public BufferedWriter(Writer out)
特殊方法:void newLine()
写入一个行分隔符
演示Demo
public class IOTest {
public static void main(String[] args) {
try {
/*****写文件*****/
FileWriter writer = new FileWriter("d:\\silly.txt");
BufferedWriter bw = new BufferedWriter(writer);
bw.write("书山有路勤为径");
bw.newLine(); //写入换行符(根据平台写入对应的换行符)
bw.write("学海无涯苦作舟");
bw.newLine();
//windows下使用\r\n也能换行
bw.write("世间安得两全法" + "\r\n");
bw.write("不负如来不负卿");
bw.flush(); //必须刷新缓冲区
/*****读文件*****/
String record = null; //存储文件的内容
int count = 0; //记录行数
FileReader reader = new FileReader("d:\\silly.txt");
BufferedReader br = new BufferedReader(reader);
//每次读取一整行数据,返回值为空时说明读取到文件末尾
while((record = br.readLine()) != null){
count++;
System.out.println("当前行数"+count+":"+record);
}
/**输出结果
当前行数1:书山有路勤为径
当前行数2:学海无涯苦作舟
当前行数3:世间安得两全法
当前行数4:不负如来不负卿
*/
bw.close();
br.close();
writer.close();
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象序列化与反序列化
序列化:把Java对象转换为字节序列的过程
反序列化:把字节序列恢复为Java对象的过程
只有实现了Serializable
接口的类的对象才能被序列化
序列化用途
- 把对象的字节序列永久保存在硬盘上,通常放在一个文件中
- 在网络上传送对象的字节序列
相关类
对象输入流类:ObjectInPutStream
对象输出流类:ObjectOutPutStream
序列化步骤
- 创建一个对象输出流:
ObjectOutPutStream out = new ObjectOutPutStream(OutPutStream out);
- 调用方法对对象进行序列化
out.writeObject(Object obj);
把对象序列化,得到字节序列写入流中 - 刷新缓冲并关闭流
out.flush(); out.close();
反序列化步骤
- 创建一个对象输入流:
ObjectInPutStream in = new ObjectInPutStream(InPutStream in);
- 调用方法
in.readObject();
读取字符序列,把参数反序列化成对象,返回该对象 - 关闭流
in.close();
演示Demo
定义一个学生类实现Serializable接口
public class Student implements Serializable{
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
实现对象序列化与反序列化
public class IOTest {
public void save(Student stu){
OutputStream os=null;
try {
os = new FileOutputStream("d:\\stu.dat");
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(stu);
//写入其他类型数据
oos.writeBoolean(true);
oos.flush();
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public Student read(){
InputStream is=null;
Student stu=null;
try {
is = new FileInputStream("d:\\stu.dat");
ObjectInputStream ois = new ObjectInputStream(is);
stu = (Student)ois.readObject();
//注意读取顺序要和写入顺序保持一致
System.out.println(ois.readBoolean());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return stu;
}
public static void main(String[] args) {
Student stu = new Student("小小", 18);
IOTest test = new IOTest();
test.save(stu);
Student res = test.read();
System.out.println(res);
//true
//Student [name=小小, age=18]
}
}
Serializable接口
实现该接口会按默认方式进行序列化和反序列化
默认方式序列化
- 这种序列化方式仅对非transient的实例变量进行序列化
- 不会序列化对象的transient的实例变量,也不会序列化静态变量
默认方式反序列化
- 如果内存中对象所属的类还没有被加载,那么会先加载并初始化这个类,如果classpath中不存在该类文件,那么抛出ClassNotFoundException;
- 在反序列化时不会调用类的任何构造方法
如果希望控制类的序列化方式添加如下方法
private void writeObject(ObjectOutputStream out) throws IOException
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
可序列化类添加该方法后进行可序列化操作会执行该方法,否则按默认方式执行。在以上方法中可以先调用默认的defaultWriteObject()和read方法
序列化说明
- 对于包含敏感信息的对象,可以先加密后再序列化,反序列化时需要解密
- 默认序列化方式会序列化整个对象图,需要递归遍历对象图,如果对象图复杂,递归遍历需要消耗大量空间和时间,图内部数据结构为双向列表
- 实际应用中,如果对某些成员变量改为transient类型,可以节省空间和时间,提高可序列化的性能