由于流在java中的重要性,和本身自己在流这方面存在混乱的原因。我将会对流进行一次很清晰的总结和记录下来我的学习过程。第一次开始了发表博客,我很高兴可以讲自己的理解和大家进行分享。有不好和不对的地方请给我指出。你的鼓励就是我的动力。
注:在下面的分类由于发表后格式有更改,显的有点乱。需要慢慢看了。
File类(流的输入输出必须使用到的文件类)
新建文件:(构造方法)
创建文件可以根据相对路径和相对绝对路径
相对路径
File file = new File("a.txt");
绝对路径:
File file=new File("D://a.txt");
File类中的常用方法
File.exists() //文件或者目录是否存在
File.isFile() //是否是文件
File.isDirectory(); //是否是目录
File.getName(); //取文件或者目录的名字
File.getPath(); //取文件或者目录的路径
File.getAbsolutePath(); //取文件绝对路径
File.lastModified(); //最后修改日期
File.length(); //文件或者目录的子节大小
File.createNewFile(); //创建新文件
File.canRead() //测试文件是否可以读取
File.canWrite() //测试文件是否可以写入
还有很多的方法,这里就不一一介绍。可以从JDK中可以查询的到。实际上,在开发过程中,这些方法比较少用。使用流也是一般都是创建一个新文件而已
流的概念
Java中对文件的操作是以流的体例进行的。流是Java内存中的一组有序数据序列。Java将数据年夜源(文件、内存、键盘、收集)读入到内存中,形成了流,然后将这些流还可以写到此外的目的地(文件、内存、节制台、收集),之所以称为流,是因为这个数据序列在分歧时刻所操作的是源的分歧部门。
流的分类
初学者对流分类是很模糊的。我也不例外。我记得让我最头疼的是。InputStream,FileInputStream,BufferedInputStream ,DataInputStream等等很多的流不知道如何使用。看到别人的代码有时候读取图片只用到InputStream,但是有些时候使用FileInputStream和BufferedInputStream,这个就是对流的整体的分类不清楚导致的
----------输入流(InputStream)
这两个流就是字节流的基类了。不管字节流怎么变化,都是继承了他们。以InputStream为例,下面
--------字节流: ByteArrayInputStream PipedInputStream,StringBufferedInputStream,FileInputStream。大家是否
有点眼前一亮,发现了常用的FileInputStream,对,只有记住这个流就好了。FileInputStream是
DataInputStream(常用),BufferedInputStream(常用),LineNumberInputStream等的父类
------------输出流(OutputStream) OutputSteam 和InputStream总是对应的。这里就不一一写出来的。有输入流就有输出流,他们结构一样
按照流的单位可以分:
---------Reader
----------字符流(常用于文本类型的数据或字符串流) 像上面一样,Reader和Writer就是字符流的基类,不管字符流这么变化,都是继承
了他们。Reader下面CharArryReader,FilerReader,PipedReader BufferedReader(常用),InputStreamReader(常用),只要记住这两个常用的字符流子类就好。InputStreamReader下面有FileReader(重要)
-----------Writer 读流和写流当然也是成对的了。
分类的截图如下:(如果看上面混乱那就直接看截图吧)
除了以上的流的分类外,还有人应该听说过 过滤流
那么什么是过滤流(又称包装流)呢?
大家请看上面的输入流,输入流InputStream子类的FileInputStream的子类DataInputStream(常用),BufferedInputStream(常用),LineNumberInputStream就是所谓的过滤流了。上面有说BufferedInputStream,DataInputStream比较常用,之后我会给出一些案例,也是介绍常用的流的使用。
那么,为什么需要过滤流呢?
不适用过滤流可以实现一些文件的读取吗?答案是可以的。这个就是为什么有时候我们看到别人的代码中有的时候BufferedInputStream和BufferedOutputStream,有些时候,看到代码中只使用了FileInputStream就可以读取和写入。
有朋友问,如果仅当InputStream和OutputStream流可以不可以呢?
当然是不可以的。我们要知道,InputStream和OutputStream都是抽象类。那么他们是不能直接使用的。但是他们有什么用呢?即使其他的操作类的基类。
介绍如下:
在需要想文件中写入或者读取文件内容各种数据类型的时候,一般是先将其他类型的数据转换为字节数组写入文件。或者将从文件独有的字节数组置换为其他类型。现在需要构造一个中间类,这个中间类提供了读/写各种类型数据的方法。通俗的讲,就是读取和写入一些字节数据的时候,这些过滤流为中间人,先转换成字节数组。
使用方法:
过滤流必须建立在基本的字节流之上,意思就是说,过滤流是一个高层流(如BufferedInputStream),他必须和低层流(InputStream)连接。通俗讲,低层流(InputStream)的对象作为高层流(如BufferedInputStream)的参数。对其中的数据进行了某些加工和处理。并提供一些方法供用户进行输入和输出操作以及流操作。
例如:BufferedInputStream可以对任何种类的输入流进行带缓冲区的封装以达到性能的改善。
对于字符流也来个总结吧。字符流我们学习什么呢?Reader和Writer。Reader和Writer下面的BufferedReader和BufferedWriter,InputStreamReader和OutputStreamWriter。
inputStreamReader ,outputStreamWriter下面的FileReader和FileWriter。有没有发现和上面字节流的有些结构上的混乱,让我们记忆也有点混乱,在上面Buffered是在InputStream...outputStream....下面的,但是在字符流中,他们是同级别的。但是Reader和Writer的直接子类的。但是呢,InputStream和OutputStream下面既然是FileReader和FileWriter。如果按照常理来说,如果子类的名字叫做FileInputStreamReader和FileOutputStreamWriter就好了。我们可以认为创造者怕名字长了,不按常理出牌。哈哈,开玩笑
实际上,我在学习流的时候,最让我对流的分类混乱的就是这里了。因为他们名字好像不按照常理出牌之后,记忆就经常混乱。时间久了我总是记成Buffered.....在InputStream...下面的。FileReader,FileWriter是和InputStreamReader。。。OutputStreamWriter同级的。
为了解决上面的问题,好像除了记忆好没什么好办法。记忆混乱的时候只能通过查找追究他们的继承关系了。不过个人觉得最好的记忆方法就是直接使用它们多几次。
好了,继承关系理清楚了。我们开始案例的编写了。
第一个学习的是:文件
import java.io.*;
import java.util.Date;
public class FileDemo {
public static void main (String[] args) {
//相对路径
File file=new File("D://a.txt");
if(file.exists()){
System.out.println ("a.txt文件存在");
}else{
System.out.println ("a.txt文件不存在");
}
//获得文件名
String fileName=file.getName();
System.out.println ("文件名:"+fileName);
String path=file.getAbsolutePath();
System.out.println ("完整路径文件名:"+path);
//获得文件的长度(以字节为单位)
long l=file.length();
System.out.println ("文件的长度为:"+l+"字节");
//获得文件创建或最后修改的时间
long time=file.lastModified();
System.out.println ("修改的时间毫秒数:"+time);
Date date=new Date(time);
System.out.println ("文件修改的时间:"+date.toLocaleString());
}
}
执行后会在D盘根目录下生成一个a.txt文件的
学会了创建文件之后,我们就开始了流的学习的
首先,先从字节流的最经常使用的FileOutputStream和FileInputStream开始了
在学习之前,我们先了解下几个方法(基类中的read和write最为重要)
FileInputStream中方法
close()
关闭此输入流并释放与该流关联的所有系统资源。
read()
从输入流中读取数据的下一个字节。
read(byte[] b)
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b
中。
read(byte[] b, int off, int len)
将输入流中最多 len
个数据字节读入 byte 数组。b为读入的缓冲区。off 为输入b中写入数据的初始化偏移量 Len为要读取的最大字节数
read方法都是返回的是Int类型,如果因为已经到达流末尾而没有可用的字节的话就会返回-1
FileOutputStream中的方法
close()
关闭此输出流并释放与此流有关的所有系统资源。
flush()
刷新此输出流并强制写出所有缓冲的输出字节。
write(byte[] b)
将 b.length
个字节从指定的 byte 数组写入此输出流。
write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off
开始的 len
个字节写入此输出流。 里面的参数和上面的read里面的参数是一个意思的,这里不写出了
write(int b)
将指定的字节写入此输出流。
在学习读和写之前,我们先来学习如果写文件
将字节数组中的内容写入到b.txt中 那么b.txt中就会有65,66,67
public class FileOutputStreamDemo {
public static void main (String[] args) {
try{
File file=new File("D://b.txt");
FileOutputStream fos=new FileOutputStream(file);
byte[] bytes={65,66,67};
fos.write(bytes);
fos.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
接下来那就要操作读和写了
案例:使用FileInputStream和FileOutputStream复制文件(这个就有了读和写两个过程了)
import java.io.*;
public class BufferInputOutputStreamDemo {
public static void main (String[] args) {
try{
//可以直接在创建流的时候创建文件,所以这里不需要new File了
FileInputStream fis=new FileInputStream("myFile.txt");
FileOutputStream fos=new FileOutputStream("myFile2.txt");
//定义字节数组
byte[] buffer=new byte[1024];
int num=-1;
while(true){
num=fis.read(buffer);
if(num==-1){//是否知道了返回-1的作用了?
break;
}
//read和write是有几种形式的,上面你可以看到
fos.write(buffer,0,num);
// fos.write(buffer); //照样可以是实现,不同的是,上面规定了最大的字节数
}
bis.close();
fis.close();
}catch(FileNotFoundException ex){
ex.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
读写操作案例二:
复制图片文件
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class File_In_Out_putStream {
public static void main(String []args){
//设定字节数组长度为100*1024个字节,即是100k
byte[]b=new byte[100*1024];
try {
//图片路径
FileInputStream fis=new FileInputStream("d://Winter.jpg");
//复制后图片所在路径
FileOutputStream fos=new FileOutputStream("d://Winter2.jpg");
//read方法需要捕获IOException异常
//从输入流fis中读取一定数量的字节并将其存储在缓冲区数组b中
fis.read(b);
//将b.length个字节从指定的字节数组b写入输入流
fos.write(b);
//必须记得关闭流 不然你会碰到一些问题的
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
这样的话,你就会发现,既然复制了一个图片喔。但是,你会发现了没有,这个图片如果大的话,既然图片没有完整。那是为什么呢?如果你细心的话,你会发现,第一个复制文件我使用了循环。第二个文件我既然没有使用循环。不使用循环就会导致了一次读进去多少字节就多少字节。所以图片是不完整的。
我们应该修改。
修改后的代码如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class File_In_Out_putStream2 {
public static void main(String []args){
//设定字节数组长度为100*1024个字节,即是100k
byte[]b=new byte[100*1024];
try {
//图片路径
FileInputStream fis=new FileInputStream("d://Winter.jpg");
//复制后图片所在路径
FileOutputStream fos=new FileOutputStream("d://Winter2.jpg");
int num=-1;
while(true){
//read方法需要捕获IOException异常
//从输入流fis中读取一定数量的字节并将其存储在缓冲区数组b中
num=fis.read(b);
if(num==-1){
break;
}
//将b.length个字节从指定的字节数组b写入输入流
fos.write(b,0,num);
}
//必须记得关闭流 不然你会碰到一些问题的
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
这样你会发现了一个问题,就是你的图片就是完整的了。那么,你懂了吗?
那好,以下我们就开始使用过滤流来做“第三者”了。鼓励大家使用这种,为什么呢?好处就是:可以提高性能和速度
为什么会提高了性能呢?
因为使用了缓冲输入和输出流之后,可以从此流中成批读取字符,而不会每次都直接对数据源的读操作。数据输入时,首先会放到缓冲区,随后的读操作就是对缓冲区中的内容进行访问。那必须快的了。
BufferedInputStream和BufferedOutputStream的学习
案例:上面的复制文件的案例的改进版本
import java.io.*;
public class BufferInputOutputStreamDemo {
public static void main (String[] args) {
try{
FileInputStream fis=new FileInputStream("myFile.txt");
//创建缓冲输入流
BufferedInputStream bis=new BufferedInputStream(fis);
FileOutputStream fos=new FileOutputStream("myFile2.txt");
//创建缓冲输出流
BufferedOutputStream bos=new BufferedOutputStream(fos);
//定义字节数组
byte[] buffer=new byte[1024];
int num=-1;
while(true){
num=bis.read(buffer);
if(num==-1){//如何读到文件结尾
bos.flush();//清空缓冲到输出流
break;
}
bos.flush();
bos.write(buffer,0,num);
}
bos.close();
fos.close();
bis.close();
fis.close();
}catch(FileNotFoundException ex){
ex.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
这样和上面实现的效果一模一样。但是有朋友说,怎么看不到性能的提高了呢?那是因为我们的文件是小文件。看不出效果而已
使用BufferedInputStream和BufferedOutputStream复制文件(同样是上面的案例的改进)
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
public class Buffered_In_Out_putStream {
public static void main(String []args){
//设定字节数组长度为100*1024个字节,即是100k
byte[]b=new byte[100*1024];
try {
//图片路径
FileInputStream fis=new FileInputStream("d://Winter.jpg");
//低层流作为高层流的参数 即是让他们建立连接
BufferedInputStream bis=new BufferedInputStream(fis);
//复制后图片所在路径
FileOutputStream fos=new FileOutputStream("d://Winter2.jpg");
BufferedOutputStream bos=new BufferedOutputStream(fos);
int num=-1;
while(true){
//read方法需要捕获IOException异常
//从输入流fis中读取一定数量的字节并将其存储在缓冲区数组b中
num=bis.read(b);
if(num==-1){
bos.flush();//清空缓冲输出流
break;
}
//将b.length个字节从指定的字节数组b写入输入流
bos.flush();
fos.write(b,0,num);
}
//必须记得关闭流 不然你会碰到一些问题的
//先关闭高层流再关闭底层流
fis.close();
fos.close();
bis.close();
bos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
嘻嘻。。。。那么。。。。思路很清晰吗?如果到这里你还是一知半解的话,建议多看几遍了。
那么,接下来我们学习DataInputStream和DataOutputStream吧
从前面我们知道,当向流中写入数据时,首先必须要将数据转换成byte数组或者char数组
(字符流)。如果数据比较少,比较简单,则向流中吸入数据还不麻烦。但是当写入数据
比较多的时候,这种转换就会出现问题了。
DataInputStream和DataOutputStream的作用:简化流数据的读写
使用方法:
如实使用了DataOutputStream流格式写入数据,那么读取时必须使用DataInputStream进
行读取。因为,在DataOutputStream吸入数据时,还写入了特定的数据格式,必须使用
DataInputStream来读取才能保持类型一样。
写案例之前,我们先来了解下他们的常用方法吧。
DataInputStream中方法
int readInt()
从输入流中读取整数
double readDouble()
从输入流中读取double型小数
float readFloat()
从输入流中读取Float型小数
char readChar()
从输入流中读取一个字符
readUTF()
参见 DataInput 的 readUTF 方法的常规协定。
read(byte[] b)
从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b 中。
int read(byte[] b, int off, int len)
从包含的输入流中将最多 len 个字节读入一个 byte 数组中。
DataOutputStream中方法
flush()
清空此数据输出流。
void writeInt(int v)
将一个 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。
void writeFloat(float v)
使用 Float 类中的 floatToIntBits 方法将 float 参数转换为一个 int 值,然后将该 int
值以 4-byte 值形式写入基础输出流中,先写入高字节。
void writeDouble(double v)
使用 Double 类中的 doubleToLongBits 方法将 double 参数转换为一个 long 值,然
后将该 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。
void writeChar(int v)
将一个 char 值以 2-byte 值形式写入基础输出流中,先写入高字节。
void writeChars(String s)
将字符串按字符顺序写入基础输出流。
void writeUTF(String str)
以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
void write(int b)
将指定字节(参数 b 的八个低位)写入基础输出流。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入基础输出流。
那么案例还是以前的案例,这样更容易理解
复制图片
import java.io.*;
public class DataStreamDemo {
public static void main (String[] args) {
try{
FileInputStream fis=new FileInputStream("Winter.jpg");
//创建高层输入流
DataInputStream dis=new DataInputStream(fis);
//创建输出流
FileOutputStream fos=new FileOutputStream("Winter2.jpg");
DataOutputStream dos=new DataOutputStream(fos);
//发现了没有,我们不再需要创建一个byte数组了
int temp=0;
//因为没有数组了所有这里就是read()方法,没有参数了,返回的是一个整数,这个整数以二进制返回,所以可以直接dos.write(temp);
while((temp=dis.read())!=-1){
//写入文件
dos.write(temp);
}
dis.close();
fis.close();
dos.close();
fos.close();
System.out.println ("文件复制成功");
}catch(FileNotFoundException ex){
System.out.println ("文件不存在");
ex.printStackTrace();
}catch(IOException ex){
System.out.println ("读写异常");
ex.printStackTrace();
}
}
}
那么,字节流我们就学完了常用的了。其他的如果读者有兴趣的话,自己去研究了。接下来我还会在我的博客更新上字符流的部分。