------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!
一、IO概述
IO流,即InputOutput的缩写。
IO的特点:
(1)IO流用来处理设备间的数据传输。
(2)Java中对数据的操作是通过流的方式。
(3)Java用于操作流的对象都在IO包中。
(4)流按操作数据分为两种:字节流和字符流。
按流向分为:输入流和输出流。
注意,流只能操作数据,不能操作文件。
IO流的常用基类:
(1)字节流的抽象基类:InputStream和OutputStream
(2)字符流的抽象基类:Reader和Writer
这四个基类派生出来的子类名称都是以父类名作为子类名的后缀,以功能为前缀。如InputStream的子类FileInputStream。
二、字符流
字符流中的对象融合了编码表,使用的是默认的编码,即当前系统的编码。字符流只用于处理文字数据,而字节流可以处理媒体数据。IO流是用于操作数据的,数据的最常见体现形式就是文件,查阅API,可以找到专门用于操作文件的Writer子类对象:FileWriter后缀是父类名,前缀是功能。该流对象一被初始化,就必须有被操作的文件存在。
字符流的读写:
1.写入字符流:
(1)创建一个FileWriter对象,对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件被覆盖。其实就是在明确数据要存放的目的地。
(2)调用write(String s)方法,将字符串写入到流中。
(3)调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。
(4)调用close()方法,关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。
close()和flush()区别:
flush()刷新后,流可以继续使用;close()刷新后,将会关闭流,不可再写入字符流。
如下:
public static void main(String[] args)
{
FileWriter fw=null;
try {
//建立写入流对象,定义要操作的文件,如果文件已经存在,不覆盖,在其后面续写。
fw=new FileWriter("F:\\123.txt",true);
//写入数据
fw.write("你好,早安");
//刷新数据到指定文件
fw.flush();
} catch (IOException e) {
throw new RuntimeException("写入失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("关闭失败");
}
}
}
要注意几点:1.其实Java自身不能写入数据,而是调用系统内部方式完成数据的写入,使用系统资源后,一定要关闭资源。3.由于在创建对象时,需要指定创建文件位置,如果指定的位置不存在,就会发生IOException异常,所以在整个步骤中,需要对IO异常进行try处理。
2.读取字符流:
(1)创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生FileNotFoundException异常。
(2)调用读取流对象的read方法。
第一种方式:read():一次读一个字符
第二种方式:read(char[] c):通过字符数组进行读取。
(3)读取后调用close方法关闭流资源。
如下:
public static void main(String[] args)
{
FileReader fr=null;
try {
//建立读取流对象,关联指定文件。
fr=new FileReader("F:\\123.txt");
//第一种:一个一个读取
int ch=0;
while((ch=fr.read())!=-1)
System.out.print((char)ch);
//第二种:通过数组进行读取
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
System.out.print(new String(arr,0,len));
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("读取失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("关闭失败");
}
}
}
下面通过字符流完成一个文件的拷贝:
public static void main(String[] args)
{
FileReader fr=null;
FileWriter fw=null;
try {
//建立流对象,关联指定文件。
fr=new FileReader("F:\\123.txt");
fw=new FileWriter("E:\\123.txt");
//读写操作
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
fw.write(arr, 0, len);
} catch (IOException e) {
throw new RuntimeException("复制失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("读取流关闭失败");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
throw new RuntimeException("写入流关闭失败");
}
}
}
}
缓冲区的出现,提高了流的读写效率,在缓冲区创建前,要先创建流对象,即先将流对象初始化到构造函数中。缓冲区中封装了数组,将数据存入,再一次性取出。
读取流缓冲区BufferedReader中提供了按行读取的readLine方法,方便对文本数据的读取,当返回null时表示读到文件末尾。readLine方法返回的是回车符之前的文本数据内容,并不返回回车符。
而写入流缓冲区BufferedWriter中提供了一个跨平台的换行符:newLine();可以在不同操作系统上调用,用作对数据换行。
下面通过利用加入缓冲技术的流对象,来完成对文件的拷贝。如下:
public static void main(String[] args)
{
BufferedReader fr=null;
BufferedWriter fw=null;
try {
//建立流对象,关联指定文件,为了提高效率,加入缓冲技术
fr=new BufferedReader(new FileReader("F:\\123.txt"));
fw=new BufferedWriter(new FileWriter("E:\\123.txt"));
//通过缓冲区的 特有操作,实现内容复制
String line=null;
while((line=fr.readLine())!=null){
fw.write(line);
//换行
fw.newLine();
//为了数据的安全性,写一行刷新一次。
fw.flush();
}
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("复制失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("读取流关闭失败");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("写入流关闭失败");
}
}
}
}
//定义一个缓冲区,继承Reader类
class MyBufferedReader extends Reader{
private Reader fr;
MyBufferedReader(Reader fr){
this.fr=fr;
}
//自定义读一行的方法。
public String myRead()throws IOException{
//定义一个临时容器,用来存储数据
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=fr.read())!=-1){
if(ch=='\r')
continue;
//碰到换行标识,返回该行数据
else if(ch=='\n')
return sb.toString();
sb.append((char)ch);
}
//判断容器中是否还有数据
if(sb.length()!=0)
return sb.toString();
return null;
}
//复写父类Read方法
public int read(char[] cbuf, int off, int len) throws IOException {
return fr.read(cbuf,off,len);
}
//复写关闭流方法
public void close() throws IOException{
fr.close();
}
}
这个自定义的类体现了Java设计模式中的一种常用模式:装修设计模式。当想对已有对象进行功能增强时,可定义一个类,将已有对象传入,基于已有对象的功能,并提供加强功能,自定义的类就是装饰类。装饰类通过构造方法接收被装饰的对象,并基于被修饰的对象的功能,提供加强的功能。
装饰和继承相比,装饰模式比继承更灵活,避免了继承体系的臃肿,降低了类与类之间的关系。装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了加强的功能,所以装饰类和被装饰的类通常都属于一个体系。从继承结构转为组合结构。在定义类时,不要以继承为主,可通过装饰设计模式进行增强类功能。灵活性强,当装饰类中的功能不适合时,可以再使用被装饰类的功能。
三、字节流
字节流和字符流的基本操作是相同的,但字节流除了可以操作文本文件之外,还可以操作媒体文件。由于媒体文件的数据都是以字节存储的,所以字节流对象可直接将媒体文件的数据写入到指定目的文件中,而不用进行刷新动作。
字节流不用刷新的原理:字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换,所以可直接将字节数据写入到指定目的文件中。
输入流InputStream中有一个available()方法,返回文件中的字节个数。可以使用此方法来指定读取方式中传入数组的长度,减少循环判断,但如果文件较大,虚拟机启动分配的默认内存有限,容易造成内存溢出。所以此方法谨慎使用。
下面通过字节流拷贝一个图片文件,如下:
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{ //建立字节流对象,与文件相关联
fos = new FileOutputStream("F:\\1.JPG");
fis = new FileInputStream("E:\\1.JPG");
byte[] buf = new byte[1024];
int len = 0;
//循环读写
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("复制图片失败");
}
//关闭流资源
finally
{
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(fos!=null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
有个知识点要注意:在字节流中,read():会将字节byte型值提升为int型值read() 返回值提升的原因:因为有可能会读到连续八个二进制1的情况,八个二进制1对应的十进制是-1,那么数据还没有读完就结束,因为-1是判断读取结束的结尾标记。为了避免这种情况就对读到的字节进行int类型的提升,在保留原字节数据的情况前面补24个0,变成int类型。可以通过将byte型数据&255获取。最后在write()中,会把int类型转换为byte型,保留了最后八位。
四、转换流
1.键盘录入:
标准输入输出流:
System.in:对应的标准输入设备,键盘。 System.in的类型是InputStream。
System.out:对应的是标准的输出设备,控制台。System.out的类型是PrintStream是OutputStream的子类FilterOutputStream的子类。
当使用输入流进行键盘录入时,只能一个字节一个字节进行录入。为了提高效率,可以自定义一个数组将一行字节进行存储。当一行录入完毕,再将一行数据进行显示。这种正行录入的方式,和字符流读一行数据的原理是一样的。也就是readLine方法。
那么能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?readLine方法是字符流BufferedReader类中方法。而键盘录入的read方法是字节流InputStream的方法。这时就可以利用到转换流了。
当字节流中的数据都是字符时,通过转换流来操作读写效率高。转换流是字符流和字节流之间的桥梁方便了字符流和字节流之间的操作。下面通过一段打印键盘录入的数据的程序,演示转换流的使用。如下:
public static void main(String[] args) throws IOException
{
//获取键盘录入,利用转换流以及缓冲技术。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//将数据打印到控制台上。
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
五、流操作规律
在要使用流对象时,要先明确三个问题:源和目的、数据是否是文本、设备是什么。具体:
明确源和目的: 源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
明确数据是否是文本 :是:字符流 否:字节流
明确了体系之后,这时就要明确源和目的的设备:源:内存、硬盘、键盘
目的:内存、硬盘、控制台
下面通过练习进行演示:
1. 将一个文本文件中数据存储到另一个文件中。
分析:
(1)源:因为是源,所以使用读取流:InputStream和Reader
明确体系:是否操作文本:是,Reader
明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Reader体系中可以操作文件的对象是FileReader
是否需要提高效率:是,加入Reader体系中缓冲区 BufferedReader.
(2)目的:输出流:OutputStream和Writer
明确体系:是否操作文本:是,Writer
明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Writer体系中可以操作文件的对象FileWriter。
是否需要提高效率:是,加入Writer体系中缓冲区 BufferedWriter
2.将键盘录入的数据保存到一个文件中。
分析:
(1)源:InputStream和Reader
是不是文本?是,Reader
设备:键盘。对应的对象是System.in。——为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。所以既然明 确了Reader,那么就将System.in转换成Reader。用Reader体系中转换流,InputStreamReader
需要提高效率吗?需要,BufferedReader
(2)目的:OutputStream Writer
是否是存文本?是,Writer。
设备:硬盘。一个文件。使用 FileWriter。
需要提高效率吗?需要。 BufferedWriter
有时使用流对象时,涉及到指定编码表,涉及到字符编码转换时就要用到转换流,如下:
/**
需求:想把键盘录入的数据存储到一个文件中。
源:键盘
目的:文件
把录入的数据按照指定的编码表(UTF-8),将数据存到文件中。
*/
class IODemo {
public static void main(String[] args)throws IOException
{
//获取键盘录入
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//按照指定的编码表(UTF-8),写入指定文件中,需要用到转换流
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("abc.txt"),"UTF-8"));
String line=null;
//读写操作
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
}
}
通过System类的setIn,setOut方法可以对标准输入输出设备进行改变,如:
System.setIn(new FileInputStream("abc.txt")) 将标准输入设备改为硬盘,对应硬盘上的一个文件。
System.setOut(new PrintStream("abc.txt")) 将标准输出设备改为 硬盘,对应硬盘上的一个文件。
异常的日志信息:
当程序正在执行的时候,出现问题不希望直接打印给用户,而是需要将文件存储其来。方便程序员查看,并即时调整、完善。
这时就要用IO,通过方法printStackTrace(PrintStream ps)来完成。在如下:
catch(Exception e)
{
//将异常输入到一个文件上,表示异常的日志信息
e.printStackTrace(new PrintStream("exception.log"));
}
建立日志信息,可以通过log4j工具完成。
系统属性信息存入文本:
之前学习过用System类获取系统信息,现在也可以通过IO,将获取的系统信息通过输出流,在控制台打印或存入到指定文件里。
1.将信息在控制台打印:list(System.out)
2.将信息存入到指定文件里:list(new PrintStream("abc.txt"));
如下:
public static void main(String[] args)throws IOException
{
//获取系统信息
Properties prop=System.getProperties();
//将信息打印在控制台上
prop.list(System.out);
//将信息存入到文件中
prop.list(new PrintStream("F:\\123.txt"));
}
流的基本应用:
1.流是用来处理数据的。
2.处理数据时,一定要先明确数据源,与数据目的地。
3.数据源可以是文件,也可以是键盘。
数据目的可以是文件、控制台、或其他设备。
4.流只是在帮助数据传输,并对传输的数据进行处理,比如过滤处理、转换处理等。
六、File
File是文件和目录路径名的抽象表现形式。
File类用来将文件或文件夹封装成对象,方便于对文件和文件夹的属性信息进行操作。File类的实例是不可变的,也就是说,一旦创建,File对象表示抽象路径名将不能再被改变。File对象可以作为参数传递给流的构造函数。
建立File对象的三种格式:
(1)File f=new File("123.txt");将当前目录下的123.txt封装成对象。File类可以将存在和不存在的文件或文件夹封装成对象。
(2)File f=nwe File("F:\\","123.txt");将文件所在目录路径和文件一起传入,指定文件路径。在以后的操作中,会应用到传入一个目录路径和一个变量。
(3)File d=new File("F:\\");
File f=new File(d,"123.txt");将文件所在目录路径封装成对象,再建立文件对象,降低了文件子父目录的关联性。
File.separator表示目录分隔符,可跨平台使用,相当于“\”("\\"在Windows中表示转义分割符,而在Linux系统中不是)
File类的常见方法:
1.创建
boolean creatNewFile() ;在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象已建立就创建文件,而文件已经存在时,将覆盖。
boolean mkdir() 创建文件夹,只能创建一级文件夹。
boolean mkdirs() 创建多级文件夹。
2.删除
boolean delete() 删除文件或目录,文件存在,返回true 文件;文件不存在或正在被执行,返回false。
void deleteOnExit() 在程序退出时,删除指定文件。
3.判断
canExecute(); 是否可以是可执行文件。和Runtime类结合使用
exists();文件是否存在。
isFile();是否是文件
isDirectory();是否是文件夹
isHidden()是否是隐藏文件,例如系统卷标目录就是隐藏文件
isAbsolute();是否是绝对路径,未创建的话也可判断是否是绝对路径。
在判断源文件对象是文件还是目录时,必须要先判断该文件对象封装的内容是否存在,通过exists方法。
4.获取信息
String getName();获取文件名,不包含目录名。
getPath();获取文件的相对路径,即创建时传入的参数是什么,就获取到什么。
getParent();获取文件父目录,返回的是绝对路径的父目录。如果传入的是相对路径,返回null;如果传入的相对路径有父目录,返回该目录。
getAbsolutePath();获取文件的据对路径。
下面通过代码演示这种方法的返回结果:
//第一种情况:传入绝对路径
File f=new File("F:\\123\\456.txt");
System.out.println(f.getName());//打印”456.txt“
System.out.println(f.getPath());//打印”F:\\123\\456.txt“
System.out.println(f.getParent());//打印”F:\\123“
System.out.println(f.getAbsolutePath());//打印”F:\\123\\456.txt“
//第二种:传入相对路径
File f1=new File("456.txt");
System.out.println(f1.getName());//打印”456.txt“
System.out.println(f1.getPath());//打印”456.txt“
System.out.println(f1.getParent());//打印null,如果传入的文件前有父目录,返回父目录。
System.out.println(f1.getAbsolutePath());//打印”D:\360Downloads\新建文件夹\TestReflect\456.txt“(当前目录)
long length();返回文件长度。
5.列出文件、文件过滤
static File[] listRoots() 列出可用的文件系统根目录,即系统盘符。
String[] list() 列出当前目录下的所在文件,包括隐藏,调用list方法的file对象必须封装了一个目录。该目录必须存在。
String[] list(FilenameFilter filter) 返回一个字符串数组。获取目录中满足指定过滤器的文件或目录。FilenameFilter即文件名过滤器,它是一个接口,其中包含一个方法accept(File dir,String name),通过这个方法的boolean型返回值,对不符合条件的文件过滤掉。如下:
/**
需求:获取一个一级目录下的.java文件的文件名,并打印。
通过File的list方法,过滤非java文件,获取所有java文件。
*/
class ListDemo {
public static void main(String[] args) throws IOException
{
File f=new File("F:\\123");
//对该目录下的文件过滤,获取所有java文件的文件名
String[]fileNames=f.list(new FilenameFilter(){
public boolean accept(File dir,String name){
return name.endsWith(".java");
}
});
//打印java文件名
for(String fileName:fileNames){
System.out.println(fileName);
}
}
}
File[] listFiles();返回一个抽象路径名数组,获取当前文件夹下的所有文件和目录与list方法相比,listFiles方法返回的是File对象,可以进行File特有的操作。
当我们要获取一个多级目录下所有的文件时,就要用到一种方法:递归。当函数内每一次循环还可以调用本功能来实现,也就是函数自身调用自身,这种表现形式或编程手段,称为递归。使用递归要注意:限定条件,用来结束循环调用,否则是一个死循环;
注意递归的次数,以免造成内存溢出,因为每次调用自身的时候都会先执行下一次调用自己的方法,所以会不断在栈内存中开辟新空间,次数过多,会导致内存溢出。下面就通过递归,获取一个包含很多子目录的目录下的所有文件。如下:
/**
列出一个目录下的所有内容的文件名,即所有的文件和文件夹。并打印
*/
class FileDemo1
{
public static void main(String[] args)
{
File dir = new File("F:\\123");
showDir(dir);
}
//定义获取所有文件和文件夹的方法
public static void showDir(File dir,int level)
{
System.out.println(dir.getName());
File[] files = dir.listFiles();
for(int x=0; x
下面是一个把获取到的文件名或路径存入到一个文件中的程序,通过这段程序可以将递归、集合、IO结合起来学习。如下:
/*
练习
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。
建立一个java文件列表文件。
思路:
1,对指定的目录进行递归。
2,获取递归过程所以的java文件的路径。
3,将这些路径存储到集合中。
4,将集合中的数据写入到一个文件中。
*/
import java.io.*;
import java.util.*;
class JavaFileList
{
public static void main(String[] args) throws IOException
{
File dir = new File("F:\\123");
//建立集合,用于存储文件
List list = new ArrayList();
//将获取到的文件写入集合
fileToList(dir,list);
//定义目的文件,并定义在当前目录下
File file = new File(dir,"javalist.txt");
//从集合写入文件
writeToFile(list,file);
}
public static void fileToList(File dir,List list)
{
File[] files = dir.listFiles();
for(File file : files)
{
if(file.isDirectory())
fileToList(file,list);
else
{ //将符合条件的文件,放入集合
if(file.getName().endsWith(".java"))
list.add(file);
}
}
}
public static void writeToFile(List list,File javaListFile)
{
BufferedWriter bufw = null;
try
{
bufw = new BufferedWriter(new FileWriter(javaListFile));
//遍历集合,通过输出流,写入文件中
for(File f : list)
{
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("写入失败");
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
-------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------