学习感悟:
在学习流的时候,要先有一个整体结构,先认识什么是输入和输出,要明白输入输出是相对于谁?然后理解四个抽象流(抽象类基类),在根据不同类型的流进行分类,最后再去详细学习基本流,其实流的学习是需要技巧的,按照前面说的思想去学习,就会发现,流都是有共同点的,就可以依葫芦画瓢,一个个击破。
文件和目录
– 在Java语言里,文件(File)代表的是文件和目录
– 使用文件类File可以完成如下任务:
• 创建新文件对象,可以使用相对路径和绝对路径来创建文件。
• 不同系统的分隔符不一样的:
– Window系统分隔符:\,这就要求实际用的时候写成“\”
– Unix系统分隔符:/
• 构造方法:
– File(File parent, String child)
– File(String pathname)
– File(String parent, String child)
使用文件类File可以完成如下任务:
– 删除文件
• public boolean delete()
– 获得文件信息
• String getName():获得文件或目录的名字
• String getPath():获得文件或目录的路径
• String getAbsolutePath():获得文件或目录的绝对路径
• String getParent():获得文件或目录的父目录
– 重命名
• boolean renameTo(File newName)把文件重命名为指定的文件路径
比如: file1.renameTo(file2)为例:要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
使用文件类File可以完成如下任务:
– 测试文件
• public boolean canRead():是否能读
• public boolean canWrite():是否可写
• public boolean exists():是否存在
• public boolean isFile():是不是文件
• public boolean isDirectory():是不是目录
• public boolean isHidden():是否隐藏
– 新建文件
• public boolean createNewFile() throws IOException
– 新建目录
• public boolean mkdir():新建单个文件
• public boolean mkdirs():可新建多层文件
伪代码演示:
import java.io.File;
……
//创建一个新文件
File f = new File("c:\\java_test\\test\\readme.txt");
//判断如果文件不存在就创建它
if(!f.exists())
f.createNewFile();
//删除文件
f.delete();
//查看已经存在的目录下的内容
File f1 = new File("c:/java_test/");
//获取文件和目录名字数组
String[] s1 = f1.list();
for(String str : s1)
System.out.println(str);
//创建多层目录
File f2 = new File("c:/a/b/c/d/e");
//f2.mkdirs();
//删除目录
f2.delete();
File file = new File(f2);
System.out.println("文件或目录是否存在:" + file.exists());
System.out.println("是文件吗:" + file.isFile());
System.out.println("是目录吗:" + file.isDirectory());
System.out.println("名称:" + file .getName());
System.out.println("路径: " + file.getPath());
System.out.println("绝对路径: " + file.getAbsolutePath());
System.out.println("最后修改时间:" + file.lastModified());
System.out.println(“文件大小:” + file.length()+ “ 字节”);
经典案例:递归删除多层文件(点这里)
【I/O流】
Input/Output:输入输出机制
输入机制:允许java程序获取外部设备的数据(磁盘,光盘,网络等)。
输出机制:保留java程序中的数据,输出到外部设备上(磁盘,光盘等)。
【可以看出,IO的入出是以java程序为第一人称的】
流的分类:
通过不同的方法,可以对于进行分类。
1.按照功能来划分:
输入流:只能从中读取数据,而不能向其写入数据。
输出流:只能向其写入数据,而不能从中读取数据。
2.按照处理单元来划分
字节流和字符流操作的方式基本上完全相同。操作的数据单元不同:
字节流:操作的是8位的字节 InputStream/OutputStream 作为字节流的基类,操作的可以是图片、音频、work、ppt文件等,不能读取中文文字。
字符流:操作的是16位的字符 Reader/Writer 作为字符流的基类,操作的是纯文本文件,如.txt.java文件等,可以读取中文文字。
3.按照角色进行划分
节点流:可以直接从/向外部设备读取/写入数据的流,可以从一个特定的数据源(节点)读写数据(如:文件),称之为节点流。节点流也被称之为低级流。
处理流:对于已经存在的流进行了连接和封装,扩展了原来的读/写的功能。是“连接”在已经存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更强的读写能力。处理流也被称之为高级流。
java的io包当中包括40多个流,他们都有紧密的联系,和使用的规律,这些流都源于4个抽象基类。
InputStream / Reader :所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer :所有输出流的基本,前者是字节输出流,后者是字符输出流。
认识四个基本抽象类
大概看一下有什么方法就好了,你会发现,它们基本都是一样的。需要注意的是它们都是抽象类,不可以直接创建对象。
抽象基类
InputStream
• int read()
– 从输入流读取下一个数据字节。
– 返回 0 到 255 范围内的 int 字节值。
– 如果因已到达流末尾而没有可用的字节,则返回值 -1
• int read(byte[] b)
– 从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中
– 以整数形式返回实际读取的字节数
• int read(byte[] b, int off, int len)
– 将输入流中最多 len 个数据字节读入字节数组。
– 尝试读取多达 len 字节,但可能读取较少数量。
– 以整数形式返回实际读取的字节数
OutputStream
• void write(int b)
– 将指定的字节写入此输出流
• void write(byte[] b)
– 将 b.length 个字节从指定的字节数组写入此输出流
• void write(byte[] b, int off, int len)
– 将指定字节数组中从偏移量 off 开始的 len 个字节写入此输出流
Reader
• int read()
– 读取单个字符
• int read(char[] cbuf)
– 将字符读入数组
• int read(char[] cbuf, int off, int len)
– 将字符读入数组的某一部分
Writer
• void write(int c)
– 写入单个字符
• void write(char[] cbuf)
– 写入字符数组
• void write(char[] cbuf, int off, int len)
– 写入字符数组的某一部分
• void write(String str)
– 写入字符串。
流的操作步骤:
文件字节输入流读取文件内容的步骤:
- 1.创建流对象
- 2.创建一个缓存字节的容器数组
- 3.定义一个变量,保存实际读取的字节数
- 4.循环读取数据
- 5.操作保存数据的数组
- 6.关闭流
@Test
public void testInputStream() {
File file = new File("F:\\ img.jpg");
File file1 = new File("F:\\ img2.jpg");
InputStream is = null;
OutputStream os = null;
try {
//1.创建流对象
is = new FileInputStream(file);
os = new FileOutputStream(file1);
//2.创建一个缓存字节的容器数组
// 字节流按照8位1个字节为单位来读取数据,byte类型是1个字节的
// short 2字节;int 4字节;loog 8字节
byte[] bt = new byte[1024];
// 返回值:读取数据的实际数量(比如120B);返回为-1时,表示读取到文件尾部
//3.定义一个变量,保存实际读取的字节数
int length;
length = is.read(bt);
//4.循环读取数据
while (length != -1) {
//错误的写入文件的方法,在读取文件的时候,可能出现最后一次读到的没有1024B,好比是只有124B,
//则会把数组中上次读到的125-1024的数据也和最后一次的124B的数据一起写到文件中,轻则导致多出部分数据,
//复制文件会比原文件的大,重则可能导致文件出现错误。
//os.write(bt);
//5.操作保存数据的数组
os.write(bt, 0, length);
length = is.read(bt);
}
System.out.println("图片复制成功...");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//6.关闭流
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件字符输出流写入文件内容的步骤:
- 1.选择流:创建流对象
- 2.准备数据源,把数据源转换成字符数组类型
- 3.通过流从文件当中读取数据
- 4.通过流向文件当中写入数据
- 5.关闭流
@Test
public void testReader2() {
// 字符输入流Reater;16位2个字节为单位来读取数据的,适合读取中文文档
Reader reader = null;
Writer writer = null;
try {
//1.选择流:创建流对象
reader = new FileReader(
"F:\\a.txt");
writer = new FileWriter("F:\\b.txt");
//2.准备数据源,把数据源转换成字符数组类型
char[] ch = new char[1024];
//3.通过流从文件当中读取数据
int length = reader.read(ch);
while (length != -1) {
//4.通过流向文件当中写入数据
//写的方法一
//writer.write(ch, 0, length);
//写的方法二
writer.write(new String(ch,0,length));
length = reader.read(ch);
}
System.out.println("复制成功...");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//5.关闭流
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面的代码充分体现了文件的读写操作,做了一个简单的复制文件功能,可以看到,字节流和字符流的操作基本一致,需要注意基类是抽象类,不可以直接new,所以使用了它们的子类,上面代码,操作的都是文件,所以使用了File(文件)流,理解了上面的代码,接下来的其它流就可以依葫芦画瓢,一个个击破,理解不同类型的流各自特点就能事半功倍了。这就是学习流的一些技巧,还等什么,一起往下学习吧~~~gogogo
注意:前面读写文件都是直接对磁盘上进行操作的,所以属于节点流;下面将认识处理流,读写是对于已经存在的流进行了连接和封装。
缓冲流
• 缓冲字节流
• BufferedInputStream
• BufferedOutputStream
• 缓冲字符流
• BufferedReader
• BufferedWriter
处理流内部包含了节点流,节点流决定了与其沟通的外部设备,而处理流则增加了其读写功能。
缓冲流的好处:
• 缓冲流内部包含一个缓冲区域,默认8kb,每一次程序调用read方法其实都是从缓冲区域当中读取内容,如果读取失败就说明缓冲区域当中没有内容,那么就从数据源当中读取内容,然后会尽可能读取更多的字节放入到缓冲区域当中,最后缓冲区域当中的内容,会全部返回给程序。
• 从缓冲区读取数据会比直接从数据源读取数据的速度快,效率也更高,性能更好。
简单说:
没有缓存区,那么每read一次,就会发送一次IO操作;有缓存区,第一次read时,会一下读取x个字节放入缓存区,然后后续的read都会从缓存中读取,当read到缓存区末尾时,会再次读取x个字节放入缓存区。
处理流处理数据和节点流处理数据的方法基本上完全相同。
伪代码(复制文件(二进制))演示:
InputStream in = new FileInputStream("pic.jpg");
BufferedInputStream bis = new BufferedInputStream(in);
OutputStream os = new FileOutputStream("c:/pic_bak.jpg");
BufferedOutputStream bos = new BufferedOutputStream(os);
byte[] b = new byte[1024];
while((bis.read(b))>0){
bos.write(b);
bos.flush();//刷新流
}
for(byte t :b)
System.out.print(t);
bis.close();
in.close();
os.close();
bos.close();
转换流
转换流作用:把字节流转换成字符流,可以解决出现的因为编码集和解码集造成的乱码问题。
-
InputStreamReader:
-
OutputStreamWriter:
-
编码:字符—–编码字符集——–》二进制
-
解码:二进制—解码字符集———》字符
-
在处理文件时,如果文件的字符格式和编译器处理格式不一样时,会出现乱码问题。比如文件字符格式GBK,而编译器是UTF-8格式,那么就会产生该问题。
-
出现乱码问题的原因:
-
1.编码和解码字符集不一致造成了乱码
-
2.字节的缺失,长度的丢失
-
大部分情况下,出现乱码问题是因为中国汉字,因为中国汉字在不同的字符编码当中占据的字节数不相同,但是都占据多个字节。
-
而英文字母没有这个问题,因为英文字母在所有的字符编码当中都占据一个字节。
InputStreamReader :转换输入流将字节输入流转换成字符输入流
作用:为了防止文件使用字符输入流处理时出现乱码问题。
打印流(了解即可)
– PrintStream
– PrintWriter
– 都属于输出流,分别针对字节流和字符流
– 都有自动flush的功能
Print流-演示
OutputStream os = new FileOutputStream("c:/newFile.txt");
PrintStream ps = new PrintStream(os);
System.setOut(ps);
for(int i = 0 ;i<65535 ; i++){
System.out.print((char)i);
if((i%100)==0)
System.out.println();
}
ps.close();
说明:在上面代码中改变了打印输出流的输出方式,System.out.println();语句的输出,讲不会在编辑器控制台打印输出,而是直接把内容打印到c:/newFile.txt文件中。
数据流(了解)
– 数据流提供了存取Java基本数据类型操作
– DataInputStream
– DataOutputStream
特点: 既能够保存数据本身,又能够保存数据类型(基本数据类型+String)
代码演示:
//写数据
@Test
public void testDataStream() throws IOException {
//首先写入java基本数据类型,然后再读取
OutputStream os=new FileOutputStream("F:\\mydata.dat");//也可以是.txt
DataOutputStream dos=new DataOutputStream(os);
dos.writeBoolean(false);
dos.writeChar('小');
dos.writeInt(100);
dos.writeDouble(525.252);
dos.writeUTF("你好呀小伙子~~");
dos.close();//实际调用的是flush()方法
os.close();
System.out.println("完成任务...");
}
//读取数据
@Test
public void testDataStream1() throws IOException {
File file=new File("F: \\mydata.dat");
InputStream is=new FileInputStream(file);
DataInputStream dis=new DataInputStream(is);
//读取数据的时候,需要按照写入的顺序,依次读取
boolean b = dis.readBoolean();
char ch = dis.readChar();
int i = dis.readInt();
double d = dis.readDouble();
String s = dis.readUTF();
System.out.println(b);
System.out.println(ch);
System.out.println(i);
System.out.println(d);
System.out.println(s);
}
对象流(序列化流)
– Object流提供了直接存取对象的操作
– ObjectInputStream
– ObjectOutputStream
• 对象序列化
– Serializable 接口,实现这个接口表示类可以序列化。
– transient 关键字,表示不参与序列化。
伪代码演示:
class Student implements Serializable {
int id = 1234;
String name = "张三";
transient int age = 22;
java.util.Date d = new java.util.Date();
}
Student s = new Student();
OutputStream fos = new FileOutputStream("obj.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();
InputStream is = new FileInputStream("obj.dat");
ObjectInputStream ois = new ObjectInputStream(is);
Student s1 = (Student)ois.readObject();
System.out.println(s1.id + " " + s1.name + " " + s.d);
ois.close();
对象的序列化版本: serialVersionUID
• serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
具体序列化的过程是这样的:序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败。
对象的序列化: 类没有加入serialVersionUID
• 演示一:
– 步骤一:定义Person类,包含id和name两个属性和,其中没有声明serialVersionUID常量。
– 步骤二:使Person对象序列化。写入文件中
– 步骤三:序列化操作后,修改Person类的属性,添加age属性,修改构造器。
– 步骤四:执行反序列化操作,报错:java.io.InvalidClassException
– 因为类的属性发生变化了,版本无法匹配了,所以报错了。
• 演示二:
– 步骤一:为Person类加入serialVersionUID版本。
private static final long serialVersionUID = -5875948774444383406L;
– 步骤二:重新序列化。
– 步骤三:重复刚刚修改Person属性的操作,
– 步骤四:反序列化操作,就没有问题了。
结论就是:在实体类上最好加上serialVersionUID,避免出现因版本不同,带来的问题。
注意事项:
-
1。先序列化然后在反序列化,而且反序列化的顺序必须和序列化的顺序保持一致。
-
2.并不是所有的对象都能够被序列化。只有实现了Serializable接口的类的对象才能够被序列化。对象当中并不是所有的属性都能够被序列化。transient 关键字修饰的属性,表示不参与序列化。
-
对象序列化的主要用途:
-
1.把对象转换成字节序列,保存到硬盘当中,持久化存储,通常保存为文件。
-
2.在网络上传递的是对象的字节序列
NIO的介绍(了解)
Java NIO (New IO, Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
JavaAPI中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
java.nio.channels. Channel
—FileChannel:处理本地文件
--------SocketChannel: TCP网 络编程的客户端的Channel
---------------ServerSocketChannel:TCP网絡编程的服务器端Channel
---------------DatagramChannel: UDP网络编程中发送端和接收端Channel
随着JDK 7的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为NIO.2。因为NIO提供的一些功能,NIO已经成为文件处理中越来越重要的部分。
Path、Paths和Files核心API
早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出锴时仅返回失败,并不会提供异常信息。
NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可 以看成是File类的升级版本。
在以前IO操作都是这样写的:
import java.io.File;
File file = new File(“abc.txt");
但在Java7中,我们可以这样写:
import java.nio.file.Path;
Import java.no.file.Paths;
Path path = Paths.get(“abc.txt");
第三方组件的使用
Apache 提供的Commons-io的使用,复制jar包到项目中,就可以直接使用了。
需要的可以下载:
链接:https://pan.baidu.com/s/1q6ChFYG98CIFARV8jamF9Q
提取码:zheg
第三方组件已经帮我们完成了流的包装,在实际开发中更多的还是使用第三方组件来操作流,原因:1.这些组件一般都是大佬写的,已经进过无数次的试错和优化,可以放心使用;2.方便,可以提高开发速度。
那既然有第三方组件为什么还要学流呢?别人的东西虽然好,可是应不应该要了解一下别人是怎么实现的???话不多说,学就对了。
结束语
有不理解或者GTQ28写错的都可以留言哟~~~
希望多多交流,多多关注
白白laaaaa