Java的IO技术知识体系
----------------------------------------------------------------------------------------------------------------------
IO流概述:
IO流是用来处理设备之间的数据传输;
Java对数据的操作是通过流的方式来完成的;
Java用于操作流的对象都是在IO包中;
流按照操作的数据类型分为:字节流和字符流(可以选择编码表且仅限文本文件);
流按照流向可以分为:输入流和输出流。
IO流常用的基类:
字节流的抽象基类:
InputStream ---字节输入流 OutputStream---字节输出流
字符流的抽象基类:
Reader ---字符输入流 Writer---字符输入流
以此四个类派生出来的子类名称都是以父类名为子类名的后缀!
--------------------------------------------------------------------------------------------------------------------------------------------
操作文本数据的IO流技术:
由于文本是由字符组成的,所以通常是使用字符流来操作稳文本数据,
我们用到比较多的是:FileWriter和FileReader!
FileWriter:
现在我们通过下面这个例子来演示如何使用IO流技术!
需求:在硬盘上创建一个文件并写入一些文字数据:
import java.io.*;
class FileWriterDemo{
public static void main() throw IOException {
//创建一个FileWriter对象,他一被初始化就必须明确被操作的文件,
//而且该文件会被自动的创建到指定的目录下!
FileWriter fw = new FileWriter(“demo.txt”);
//如果在指定目录下已经有了同名文件,会创建一个新的文件覆盖掉同名文件
fw.write(“abcdef”); //将字符串写入到流中,此时还没有写入到文件中;
fw.flush( ); //将流中的缓冲数据写入到文件中;
fw.close( ); //关闭流,这一步也会将流中的缓冲数据写入到文件中;但是以后
//就无法添加数据了,而flush可以保证能够继续添加数据;
}
}
文件续写:
如果再执行一次上面的代码,当指定目录下已经有了同名文件,会创建一个新的文件覆盖掉同名文件,
也就是说上述代码再执行一次,输入别的一些信息,文件中只会出现最新一次的输入数据。
那么我们如果想要在原有文件的基础上添加一部分数据该咋办呢?
方法很简单:我们只需要值定义的FileWriter对象的构造函数中,添加一个参数,就可以满足文件
续写的要求,而不会发生产生新文件覆盖旧文件:
FileWriter fw = new FileWriter(“demo.txt”,true); //这里的参数true可以到达上述效果
还有一点需要注意的是:续写的数据的换行问题!我们只需要在需要换行的文本中添加换行标记就可
以到达换行的效果!在windows中添加的换行标记是“\r\n”;
文本文件的读取---FileReader:
FileReader是一个用于读取字符文件的便捷类,他使用系统默认的编码表;
需求:我们将一个文件中的数据读取出来,并打印在控制台上;
代码演示一(不带缓冲区):
import java.io.*;
class FileReaderDemo1 throw IOException {
public static void main(String[ ] args){
//创建一个文件读取流,并和指定文件相关联;
FileReader fr = new FileReader(“demo.txt”);
//必须保证文件存在,否则会报FileNotFoundExcption;
while(true){
int ch = fr.read(); //一次读一个字符,而且会自动往后读;
if(ch == -1){
break;
}
System.out.println(“ch=”+(char)ch);
//将ASCII码转成字符然后打印出来
}
fr.close( ); //关闭流资源!
}
}
代码演示二(带有缓冲区):
import java.io*;
class FileReaderDemo2 throw IOException {
public static void main(String[] args){
FileReader fr = new FileReader(“demo.txt”); //定义个读取流和一个文件关联
Char[] buf = new char[]1024]; //定义一个数组用于存储读取流读取到的数据
int num = 0;
while((num = fr.read(buf))!=-1){
//此处的fr.read(buf)是将读取到的数据存入数组中,并返回数组的大小
//这里是一次对一大堆读进内存中,然后一次性从内存中写出来!
System.out.println(new String(buf,0,num));
}
}
}
从代码二中我们可以发现使用这种带有缓冲区的IO流操作,可以提高对数据的读写效率。我们来举
一个例子:我们要修一座房子,需要砖头。方式一就是一个人从砖厂一次拿一块砖往工地跑,这样跑上n
次以后,他才将修房所要用的砖头凑齐了!这样的做法费时、费力还费鞋子!而方式二是:用一个拖拉机
将砖厂的砖头先搬到车上,然后用车运到目的地再将砖头卸下来,这样的做法省时间,省力气,效率高!
----------------------------------------
于是Java也就为我们提供了带缓冲区的IO流对象:BufferedWriter和BufferedReader。
BufferedWriter:
将文件写入字符输出流,缓冲了各个字符,从而提供了单个字符、数组、字符串的高效写入;
下面我们通过一个实际的例子来见识见识BufferedWriter的强大:
代码演示:
import java.io.*;
class BufferedWriterDemo{
public static void main(){
FileWriter fw = new FileWriter(“buf.txt”);
BufferedWriter bufw = new BufferedWriter(fw); //将流对象传给缓冲区
bufw.write(“abcdefg”);
burw.flush(); //可以不用刷新,关闭资源时,会将内存中的数据自动刷出,
//但是如果关闭资源以前,突然停电就糟糕了,数据就没了!
bufr.close(); //此处关闭缓冲区,其实就是关闭缓冲区中的流对象,所以
//不用再写fw.close()了!
}
}
此外介绍一个倍儿牛的方法:newLine( ):换行,此方法具有跨平台性!
BufferedReader:
将文本文件中的字符数据读进一个带缓冲区的输入流,实现了字符、数组、字符串等数据的高效读取;
代码演示:
Class bufferedReaderDemo{
public static void main(){
//创建一个字符读取流对象与一个文件相关联;
FileReader fr = new FileReader(“buf.txt”);
//将支付读取流传给缓冲区对象,提高读取效率
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while( (line =bufr.readLine())!=null){
/*这里的readLine()是BufferedReader所特有的一个方法,
它可以完成一次读一行的效果*/
System.out.println(line);
}
}
}
我们在使用BufferedReader中的readLine()方法时要注意两点:
1、 如果没有读取到数据,返回null;
2、 他读取一行依据的是一行中的换行标记,也就是说如果他没有读取到换行标记,他是不会返回数据的,
所以有可能文件中最后一行数据有可能没有被返回到内存中,造成数据丢失,使用时需要小心!针对最
后一行数据可能由于没有换行标导致无法被返回到内存中这种情况,我们可以通过下面的方式解决:
在while循环后面跟上这样的代码:
if(sb.length()!=0){
sb.toString();
}
3、 此外readLine()方法他返回的数据只是换行标记之前的数据内容,并不带回车符,所以当我们将一个文
件中的数据通过readLine()方法复制到另外一个文件中时,往往需要通过newlLine()方法进行手动换行!
--------------------------------------
装饰设计模式:
当想要对已有的功能进行增强时,可以定义一个类,将已有的类传入,基于已有功能,并提供加强功能,
那么定义的这个类就成为装饰类;
在上面的代码中,为了实现对字符串的高效读取和写入,提供了BufferedReader和BufferedWriter两个
带有缓冲区的类,他们将传统的FileReader和FileWriter对象传入,并提供了readline(),read(buf,0,num),
write(buf,0,num)等读写缓冲数组的高效读写功能。在这里BufferedReader和BufferedWriter就是装饰类,
他们接受FileReader和FileWriter对象,基于已有的read()和write()方法,提供了加强的功能!
装饰类通常会通过构造函数接受被装饰的对象,并基于被装饰对象,提供更强大的功能;
带行号的装饰类---LinenumberReader:
LinenumberReader是BufferedReader的一个子类,他除了继承了父类的readLine()方法以外,
还有自己特有的方法:
setLineNumber(int)----设置从多少开始排列行号;
getLineNumber()----获取行号;
---------------------------------------------------------------------------------------------------------------------------------------------
操作非文本文件的IO留技术---字节流操作
之前我们学习了字符流操作的四个常见的类,他们主要是用来操作文本文件的,因为他们使用默认的编码
表,操作文本文件更加方便;但是字符流不适合用来操作,这是为什么呢?
举个例子:如果一个文件有奇数个字节(byte),那么用字符流来复制的话,最后一个字节装不满一个
char,因为一个char是两个字节,而这个char复制后,却任然占两个字节,这样一来,文件的大小就改变了,
文件就打不开的,复制是失败的!所以对于媒体等类型的文件来说,使用字符流操作是有安全隐患的!-------
-----因此需要其他的流对象来操作,那就是字节流!
可以这么说:字节流可以操作字符流,而字符流不一定可以操作字节流!
字符流的两个基本类:
FileWriter FileReader
字节流的两个基本类:
FileOutputStream FileInputStream
eg:我们下面通过使用字节流来完成对文本数据的复制操作:
阿訇
import java.io*;
class FileStreamDemo{
public static void main(String[] args) throws IOException{
}
//写一个字节写出流对象,将数据写到一个文件中
public static void writeFile( ) throws IOException{
FileOutputStream fos = new FileOutputStream(“fos.txt”);
fos.write(“abcdefg”.getBytes( ));
fos.close( );
}
//使用字节读流来从一个文件中读取数据;----方式一
public static void readFile_1( ) throws IOException{
FileInputStream fis = new FileInputStream(“fos.txt”);
int ch = 0;
while((ch=fis.read())!=-1){
System.out.println(( char )ch);
}
fis.close( );
}
//使用字节读流来从一个文件中读取数据,带缓冲区;----方式二
public static void readFile_2( ) throws IOException{
FileInputStream fis = new FileInputStream(“fos.txt”);
//定义一个数组作为缓冲区,其大小为1024;
bytep[] buf = new byte[1024];
/*在FileInputStream中实际上有一个方法可以定义一个大小和目标文件
大小刚好一致的方法:available( );但是在使用时应该注意,不可以
用他来定义一些较大的文件,以免超出虚拟机的内存容量!*/
int len = 0;
while((len=fis.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fis.close();
}
}
代码分析:
字节流中的read方法一次是读一个字节,然后将这一个字节类型提升为int类型,这样数据就由1个字节变
成了4个字节;而字节流中的write方法在写的时候有会将int类型强转为byte类型,这样数据由4个字节变成了
1个字节!
大家也许就纳闷了,为啥要将一个字节类型提升为int类型呢?而不用byte呢?
我们知道所有的数据在硬盘上是以大量的二进制数据存在的,那么就有这样的一种可能:字节读取流对象
的read方法读到了数据,当时很不巧,他中大奖了---读到的数据是连续八个1,这个数是多少呢?是-1!如果
用byte来接收while循环就被终止了,所以数据没有复制完,他就停止了复制,导致复制的文件不全,打不开!
如果我们用int来接收以后再&255,就可以保证数据的高位是用0补齐的,这个值就不是-1,那么while循环就
不会终止!所以总结成一句话就是:将byte类型提升为int是为了避免小概率-1的出现!
---------------------------------------------------
读取键盘录入:
/*需求:通过键盘录入数据,当录入一行以后,打印这行数据,如果录入的数据是over,那么就停止录入。*/
代码演示:
class ReadIn{
public static void main(String[] args ){
InputSream in = System.in;
//定义一个缓冲区用于存储键盘录入的数据
StringBuilder sb = new StringBuilde();
while(true){
int ch = in.read( );
if(ch == ‘\r’) //在window中遇到这个说明有可能要换行了!
continue;
if(ch == ‘\n’){ //说明一行结束!
String s = sb.toString();
if(“over”.equals(s)) //判断结束标记!
break;
System.out.println(s.toUpperCase());//将输入的数据转成大写输出
sb.delete(0,sb.length()); //清空缓冲区,下一行存入时就是空的
}else{
sb.append((char)ch);
}
}
in.close( );
}
}
小知识点:\r对应的ASCII码是13,\n对应的ASCII码是10.
-----------------------------
转换流---InputStreamReader、OutputStreamWriter
(1)通过上面的例子我们知道了录入一行数据的方法,其实他就是readLine方法的原理!那么可不可以
通过readLine方法来完成键盘录入一行数据呢?从阵营来看readLine()是属于字符流的方法,而我们通过
键盘录入是使用的字节流,看似不可以,但是字节流中有一个转换流---InputStreamReader,他可以将
字节流转成字符流,从而使用字符流的特有方法!
代码演示:
import java.io.*;
class TransStreamDemo{
public static void main(String[] args){
//获取键盘录入对象;
InputStream in = System.in;
//通过转换流将字节流转成字符流对象;
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率使用缓冲区,从而可以使用readLine();
BufferedReader bufr = new BufferedReader(isr);
String line = null;
while((line = bufr.readLine())!=null){
if(“over”.equals(line)) //判断结束标记;
break;
System.out.println(line.toUpperCase()); //将结果转成大写输出;
}
}
}
(2)写入转换流---OutputStreamWriter---可以指定编码表
代码演示:import java.io.*;
class TransStreamDemo2{
public static void main(String[] args){
//获取键盘录入对象;
InputStream in = System.in;
//通过转换流将字节流转成字符流对象;
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率使用缓冲区,从而可以使用readLine();
BufferedReader bufr = new BufferedReader(isr);
//定义一个字节写入流对象;
OutputStream out = System.out;
//通过转换流将字节流转成字符流对象;
OutputStreamWriter osw = new OutputStreamWriter(out);
//使用缓冲流对象,接收要装饰的类的实例对象!
BufferedWriter bufw = new BufferedWriter(osw);
//使用字符读取流的一次读一行的方法
while((line = bufr.readLine())!= null){
if(“over”.equals(line))
break;
bufw.write(line.toUpperCase());//使用字符写入流的一次写一行的方法
bufw.newLine(); //另起一行,此方法具有跨平台性!
bufr.flush( ); //将数据从内存中刷出来;
}
bufw.close();
}
}
通过转换流可以使用字符流的特有方法来操作字节流对象所要进行的操作!
此外需要注意一点的是:OutputStreamWriter是可以指定编码表的!比如我们想要将录入的数据按照
指定的编码表将数据存放到文件中应该咋办呢?
我们可以这样做:
OutputStreamWriter osw= new OutputStreamWriter(
new FileoutputStream(“d.txt”,“utf-8”));
//将读入的数据按照utf-8进行编码后存入到d.txt文件中!
----------------------------------------------------------------------------------------------------------------------
IO流操作规律:
键盘录入:BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
屏幕写出:BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
通常我们很烦恼:流对象很多,我们到底使用哪一个呢?
我们可以通过下面介绍的三个明确来判断:
首先明确:
源是什么---InputStream Reader
目的是什么---OutputStream Writer
其次明确:操作的是纯文本数据吗?
是:字符流
否:字节流
最后明确:具体是操作哪个设备!
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,屏幕
下面我们来根据一个例子演示整个操作的步骤:
/*需求:将键盘录入的数据保存到一个文件中!*/
分析: 源:InputStream Reader
是否为纯文本:是--->Reader
设备:键盘--->System.in,但是它对应是字节流,所以需要转换流InputStreamReader
目的:OutputStream Writer
是否是纯文本数据:是--->Writer
设备:硬盘--->FileWriter
如果需要使用缓冲技术,那么可以考虑使用BufferedReader和BufferedWriter!
-----------------------
改变标准输入输出设备:
我们在前面的学习中见识了System.in和System.out他们默认的设备分别是键盘和屏幕,但是我们可以
通过System类中的两个静态方法System.setIn()和System.out()来改变标准输入和输出设备!譬如:
System.setIn(new FileInputStream(“a.txt”));
//此时标准输入设备就不再是键盘而是关联的这个a.txt文件了!
System.setOut(new PrintStream(“b.txt”));//此处是PrintStream而不是FileOutputStream
//此时标准输出设备就不再是屏幕而是关联的这个b.txt文件了!
------------------------
异常日志信息:
通常对于程序发生的异常,我们并不希望程序将他展现在控制台上,而是将他保存在
一个文件中,对于这个需求,因该如何处理呢?
代码演示:
import java.io.*;
class ExceptionInfo{
public static void main(String[] args ){
try{
int[] arr = new int[2];
System.out.println(arr[2]); //产生一个异常
}catch(Exception e){
try{
System.setOut(new PrintStream(“exception.log”));
//改变设备的标准输出设备,指向一个文件!
}catch(IOException e1){
throw new RuntimeException();
}
e.printStackTrace(System.out); //将异常输出到标准输出设备!
}
}
}
--------------------------------------------------------------------------------------------------------------------------------------
系统信息:
System类中有一个getProperties()方法可以获得系统的属性信息,当年我们是通过打印集合来查看
里边的信息,但是现在我们想要将这些信息保存到一个文件中,应该咋办呢?
实际上获得的这个properties对象的身上就有和流技术相关联的方法:
list(PrintStream() ps),而且是按照键值对分行打印出来!
如果这个PrintStream()对应的对象是Sytem.out,那么就是将信息展示在屏幕上;
如果是PrintStream ps = new PrintStream(“a.txt”),信息将保存在相关联的a.txt文件中!
----------------------------------------------------------------------------------------------------------------------
File概述:
流是用于操作数据的,如果他想要操作将数据封装进文件的文件或者文件夹的属性信息,
就需要借助于File对象!
类File ----java.io.File:他是文件和目录路径名称的抽象表达形式;
代码演示:
import java.io.*;
class FileDemo{
pulbic static void main(){
consMethod();
}
public static void consMethod(){
File f1 = new File(“a.txt”); //创建一个file实例,方式一
File f2 = new File(“c:\\abc”, “a.txt”); //创建一个file实例,方式二
File d = new File(“c:\\abc”);
File f3 = new File(d,“a.txt”); //创建一个file实例,方式三
System.out.println(“f1:”+f1);
System.out.println(“f2:”+f2);
System.out.println(“f3:”+f3);
}
}
上面将目录分割符写成“\\”的形式不具备跨平台性,我们用File.separator来替换!
也就是说将new File(“c:\\abc”,“a.txt”)写成:new File(“c:”+File.separator+“abc”, “a.txt”);
---------------------------
File类的常见方法:
1,创建:
booleancreateNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
这和输出流不一样,输出流对象一建立创建文件。而且文件已经存在,会覆盖。
booleanmkdir(): 创建文件夹。
booleanmkdirs(): 创建多级文件夹。
2,删除。
booleandelete():删除失败则返回false。如果文件正在被使用删除不了,则返回falsel。
voiddeleteOnExit():在程序退出时删除指定文件。即使因为异常使得程序终止,在虚拟
机退出时也会执行此操作!
3,判断。
booleanexists() :判断文件是否存在.
isFile():检查此抽象路径名表示的文件是否是一个标准文件;
isDirectory():检查此抽象路径名表示的文件是否是一个目录;
isHidden():检查此抽象路径名指定的文件是否是一个隐藏文件;
isAbsolute():检查此抽象路径名是否为绝对路径名。
4,获取信息。
getName():返回由此抽象路径名表示的文件或目录的名称
getPath(): 将此抽象路径名转换为一个路径名字符串
getParent(): 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目 则返回null
;
getAbsolutePath():获取绝对路径(也就是带有盘符以及目录分割符号的完整路径)
longlastModified() :获取最有一次修改时间;
longlength():返回:此抽象路径名表示的文件的长度,以字节为单位;如果文件不存 在,则返回 0L;
如果此路径名表示一个目录,则返回值是不确定的!
File[ ] listRoots():列出可用文件的根目录---各盘符;
String[] list():列出目录下所有文件(包括隐藏文件);
String[] list(FilenameFileter ) :添加一个过滤器,将符合条件的文件或者目录添加进数组中;
File[] listFiles():功能和list相同,但是返回的是一个抽象路径名数组;
此外再介绍一个更改File对象所指向的文件的方法:
boolean renameTo(File dest) :重新命名此抽象路径名表示的文件。
--------------------------------------------------------------------------------------------------------------------------------------------
递归:
递归是一种编程技巧,他在形式上体现为:
函数调用函数本身,从而实现相同的功能可以被多次的执行的效果!
使用递归时要注意两点:
(1) 限定递归条件,没有限制的递归就是死循环;
(2) 控制递归次数,尽量避免内存溢出。
代码演示:
import java.io.*;
class DiGuiDemo{
public static void main(String[] args){
File dir = new File(“d:\\java1223”);
showDir(dir);
}
public static void showDir(File dir){
System.out.println(dir);
File[ ] files = dir.listFiles();
For(int x=0; x<fies.length(); x++){
if(files[x].isDirectory()){ //设定了递归的条件,避免死循环的发生;
showDir(Files[x]); //调用函数本身,开始递归;
}
else
System.out.println(files[x]);
}
}
}
------------------------------------------
递归的应用1---删除带内容的目录:
删除原理:
在windows中删除是从里面向外面删除的,既然要一层一层的从里向外删除,那么就需要用到递归!
代码演示:
import java.io.*;
class RemoveDirDemo{
public static void main(){
File dir = new File(“d:\\testdir”);
removeDir(dir);
}
//定义了一个通过递归删除带内容目录的方法;
public static void removeDir(File dir){
File[] files = dir.listFiles();
for(int x=0; x<files.length(); x++){
if(files[x].isDirectory())
//判断是否是一个文件夹,如果是就通过递归进入文件夹,进一步删除!
removeDir(files[x]);
else{ //如果是文件就直接删除!
System.out.println(files[x]+“::”+files.delete());
}
}
System.out.println(dir+“::”+dir.delete()); //将外层文件夹删除!
}
}
---------------------------------------
递归的应用2----创建Java文件列表
/*需求:将指定目录下的所有Java文件的绝对路径存到一个文本文件中,建立一个Java
文件列表清单*/
思路:1、对指定目录进行递归;
2、获取递归过程中所有的Java文件的路径;
3、将这些路径存储到一个集合中;
4、将集合中的路径数据写入到一个文件中;
代码:
import java.io.*;
import java.util.*;
class JavaFileList{
public static void main(String[] args){
File dir = new File(“d:\\java1223”);
List<File> list = new ArrayList<File>();
}
//定义一个方法可以将文件目录中的相关信息录入到内存中的集合中!
public static void fileToList(File dir,List<File> list){
File[] files = dir.listFiles();
for(File file : files){
if(file.isDirctory()) { //判断是否是目录。
//如果是,就递归,继续解析目录里的文件或者文件夹
fileTolist(file, list);
}else{
//如果不是目录就直接获取文件的信息然后添加到集合里;
if(file.getName().endsWith(.java)) //过滤掉非Java文件
list.add(file);
}
}
}
//将集合中的数据写入到一个文本文件中,所以用字符流;
public static void writeToFile(List<File> list, String javaListFile)
throws IOException{
BufferedWriter bufw = null;
try{
bufw = new BufferedWriter(new FileWriter(javaListFile));
for(File f : list){
//获取File对象的路径信息;
String path = f.getAbsolutePath();
bufw.write(path); //将路径信息写入到文本文件中;
bufw.newLine(); //换行
bufw.flush(); //将数据从内存中刷出到文件中去;
}
}catch(IOException e1){
throw e;
}finally{
try{
if(bufw!=null)
bufw.close(); //关闭流资源!
}catch(IOEception e2){
throw e;
}
}
}
}
-------------------------------------------------
Properties:
他是HashTable的子类,具备集合的特点,而且它里面存储的都是键值对,所以不需要泛型;
他是集合和IO技术相结合的容器!
该对象的特点是:用于键值对形式的配置文件;
Properties类是java.util包中的一个持久属性集;Properties 可保存在流中或从流中加载。
属性列表中每个键及其对应值都是一个字符串。
该类中有几个比较有特色的方法:
Set<String> stringPropertyNames(): 获取property对象上的键的集合!
然后我们可以通过遍历Set集合取出所有的值value;
配置文件的应用:
想要将配置文件中的数据读入到内存的集合中进行操作我们应该怎么办呢?
通常我们需要经过以下三步:
1、 用一个流和配置文件相关连;
2、 每读一行数据,用等号将数据切割,左边的是键,右边的是值;
3、 将键值对应的存储到property集合中;
但是我们通过property类中load(InputStream in)或者load(Readerreader)(JDK1.6出现!)
这两个方法就可以大大的简化操作!
代码演示:
import java.io.*;
import java.util.*;
class Demo{
public static void main(){
loadAndStoreDemo();
}
// 定义一个方法可以从配置文件中加载信息,同时可以将处理过的数据存回文件
public static void loadAndStoreDemo(){
FileInputSream fis = new FileInputStream(“info.txt”);
Properties prop = new Properties();
prop.load(fis); //通过InputStream将关联的配置文件中的信息加载到内存中;
System.out.println(prop); //此语句可以用:prop.list(System.out)来代替!
Prop.setProperty(“wangwu”, “39”); //此时只是在内存集合中修改了,但是配置
//文件中的键值对信息还没有更新!
FileOutputStream fos = new FileOutputStream(“info.txt”);
Prop.store(fos,“haha”);
/*通过OutputStream将更改后的信息存回配置文件中,这里的haha是注释信息,
这个注释信息不要写成中文,因为编码时容易出现乱码 */
//在配置文件中带有#的那一行数据是不会被Properties加载的!
fos.close();
fis.close();
}
}
------------------------------------------------------------------------------------
打印流:
该流提供了打印的方法,可以将各种数据类型的数据原样打印!
它主要有这两个类:
PrintWriter(字符流) PrintStream(字节流)
PrintStream的构造函数可以接收:
1、 File对象;
2、 字符串路径;
3、 字节输出流;
PrintWriter的构造函数可以接收:
1、 File对象;
2、 字符串路径;
3、 字节输出流;
4、 字符输出流---比PrintSream多了一个!
PrintWriter在web开发中很常用,所以要做必要掌握!
代码演示:
import java.io.*;
class PrintStreamDemo{
public static void main() throws IOException{
BufferedReader bufr = new BufferedReader(
new InputSreamReader(System.in));
//将录入的数据输出到屏幕,相关方法自动刷新!
PrintWriter out = new PrintWriter(System.out, true);
/*将录入的数据输出到文件中,相关方法不会自动刷新;
因为所谓的自动刷新是指流,如果想要文件可以自动刷新
可以将文件传进一个流中---也就是下面的形式*/
//PrintWriter out = new PrintWriter(“a.txt”);
//将录入的数据输出到文件中,相关方法自动刷新;
//PrintWriter out = new PrintWriter(new FileWriter(“a.txt”), true);
String line = null;
while((line = bufr.readLine())!=null){
if(“over”.equals(line))
break;
out.println(line.toUpperCase());
//由于构造函数中传入的参数是true,所以回自动刷新
}
out.close();
bufr.close();
}
}
当打印流的构造函数中是否自动刷新的参数传入的是true时,只有下面这三个方法可以自动刷新,
println()、printf()和format(),因为这三个方法带有输入结束的结束标记!并不是所有的方法都自动刷新!
--------------------------------
合并流----sequenceInputStream
他可以对多个流进行合并,比如我们想要将多个文件中的数据存入到一个文件中去,以前我们是通过
不断的续写来完成的---太麻烦了!有了这个类,我们就可以大大的简化操作了!
他的原理是:将多个流合并成一个流,当一个流结束后,下一个流继续存入数据,直到最后一个流也将数
据存完了,整个流才结束!
应用:电子书连载,每天出一章,当一部电子书写完了,已经有成百上千个小文件,我们想要将他们
合并成一个txt文件,如果自己手动的复制粘贴,简直就是灾难啊!但是如果我们通过合并流来进行合并,
将是一件小Case!
代码演示:
class SequenceInputStream{
public static void main() throws IOExeption{
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream(“c:\\1.txt”));
v.add(new FileInputStream(“c:\\2.txt”));
v.add(new FileInputStream(“c:\\3.txt”));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
//将多个流合并为一个流
FileOutputStream fos = new FileOutputStream(“c:\\4.txt’);
//将多个文件存储到4.txt这一个文件中;
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1){
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}
切割文件---将一个大文件切成多个小文件!
应用场景:那些知名的网络写手连载的小说有可能有一千多章,文件可能有十几兆,有的手机配置太低,
打不开这样的大文件,有必要对这个文件进行切割,把他们切成较小的文件,这样手机就可以打开了。
网上有这样的软件:电子书切割器!
原理代码演示:
import java.io.*;
import java.util.*;
class Demo{
public static void main(){
splitFile();
}
//定义一个切割文件的方法
public static void splitFile() throws IOException{
FileReader fr = new FileReader (“c:\\story.txt”);
FileWriter fw = null ;
byte[ ] buf = new byte[512*1024]; //定义一个可以装1M数据的缓冲区
int len =0;
int count = 1;
while ( (len=fr.read(buf))!=-1){
//将一个缓冲数组中的数据存如一个文件中!
fos = new FileOutputStream(“c:\\”+(count++)+“.txt”);
fos.write(buf,0,len);
}
fis.close();
}
}
---------------------------------------------------------------------------------------------------------------------
对象的序列化(又叫对象的持久化或者可串行性)
我们知道对象是存在于堆内存中的,一旦程序运行结束,或者对象没有被指向,那么就会被清理掉,
对象中的数据也就没有了。现在我们有这样的一个需求:将对象存在硬盘上,实现对象的持久化存储,
应该咋办呢?
我们可以使用ObjectInputStream和ObjectOutputStream这两个流对象来将对象传入到流中进行操作!
要实现对象的序列化,那么该对象就需要实现Serializable接口,该接口中没有方法,是一个标记接口!
他会自动生成一个UID来保证序列化的对象是用相应UID的类文件产生的!我们也可以手动设置UID!
还有两点需要明确:
1、 被static修饰的成员是无法被序列化的;
2、 被transient修饰的成员是无法被序列化的。
代码演示:
import java.io.*;
class Person implements Serializable{ //定义一个类,他的实例对象可以被序列化
public static final long serialVersionUID = 42L; //手动设置UID;
private String name;
transient int age; //该成员无法被序列化;
static String country = "cn"; //该成员也无法被序列化;
Person(String name,int age,String country){
this.name = name;
this.age = age;
this.country = country;
}
public String toString(){
return name+":"+age+":"+country;
}
}
import java.io.*;
class ObjectStreamDemo {
public static void main(String[] args) throws Exception{
//writeObj();
//readObj();
}
public static void readObj()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject(); //特色方法,从文件中读取对象!
System.out.println(p); //输出的结果是lisi0,0,cn,因为年龄和国际没有序列化
ois.close();
}
public static void writeObj()throws IOException{
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi0",399,"kr")); //讲对象写入到硬盘文件中!
oos.close();
}
}
注意:ObjectInputStream和ObjectOutputStream这两个流对象通常是配对使用的,也就 是说,用
ObjectOutputStream流写进文件的数据,必须使用ObjectInputStream流来从文件中来读取。
---------------------------------------------------
管道流---PipedInputStream/PipedOutputStream---设计到多线程技术
在以前我们使用写入流和输出流时是通过一个中转站---缓冲区,来实现输出流将写入流读取的
数据输出,达到将两个流连接起来;而现在我们有了管道流,就可以不需要中转站,就可以直接将
两个流连接起来;通常我们在使用管道流时,是用两个线程来操作的,一个线程负责管道输入流,
一个线程负责管道输出流,这样可以防止死锁情况的发生!
两个管道流可以通过下面两种方式来完成连接:(要注意数据的流向!)
1、 通过构造方法:
PipedInputStream(PipedOutputStream src):
让管道输入流连接到管道输出流,数据流向是从管道输出流到管道输入流!
PipedOutputStream(PipedInputStreamsnk):
让管道输出流连接到管道输流流,数据流向是从管道输入流到管道输出流!
2、 通过connect()方法连接:
in.connect(out):
让管道输入流连接到管道输出流,数据流向是从管道输出流到管道输入流!
out.connect(in):
让管道输出流连接到管道输流流,数据流向是从管道输入流到管道输出流!
代码演示:
import java.io.*;
class Read implements Runnable{ //实现Runnable接口,复写run()
private PipedInputStream in;
Read(PipedInputStream in){ //接收一个管道输入流;
this.in = in;
}
public void run(){
try{
byte[] buf = new byte[1024];
int len = in.read(buf); //直接读取输出流传递过来的数据,然后存入数组
String s= new String(buf,0,len);
System.out.println(s);
in.close();
}catch (IOException e){
throw new RuntimeException("管道读取流失败");
}
}
}
class Write implements Runnable{ //实现Runnable接口,复写run()
private PipedOutputStream out;
Write(PipedOutputStream out){ //接收一个管道输出流;
this.out = out;
}
public void run(){
try{
Thread.sleep(6000);
out.write("piped lai la".getBytes());
//数据写到内存中,直接传给了输入流,没有经过缓冲区
out.close();
}catch (Exception e){
throw new RuntimeException("管道输出流失败");
}
}
}
class PipedStreamDemo{
public static void main(String[] args) throws IOException{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);
//将管道输入流和输出流连接起来,这里的数据流向是从out到in
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start(); //开启一个线程负责管道输入流;
new Thread(w).start(); //开启一个线程负责管道输出流;
}
}
--------------------------------------------
RandomAccessFile(随机访问文件)
该类其实不算是IO体系中的子类,他是直接继承自Object,但他仍然是IO包中的成员,因为他具有
读和写的功能(既能读还能写,功能强大啊!),他的内部封装了一个大型的byte数组,而且能够通过
指针对元素进行操作;
用途:使用该对象可以实现多线程下载;
局限性:只能操作文件,而且还要设置模式mode:(r , rw , rws, red)
通常我们操作的模式是r(只读)和rw(读写)!
模式为r:不会创建文件,当文件不存在时,会报告文件找不到异常;
模式为rw:当文件不存在时,会创建文件,而且不会发生文件覆盖,而是修改文件;
前面说过RandomAccessFile中封装了一个大型数组,当一个文件被传入以后,它里面的数据就被存储在了
这个byte数组中,我们可以通过指针操作数组,从而达到操作文件的目的。
他操作指针的方法有:
getFilePoint():获取指针位置;
seek( ):设置指针位置;
skipBytes( ):跳过指定数目的字节数,操作数组中的元素;---只能从头向尾跳;
另外要注意一点:write( )写出的是一个字节,即使传入一个int类型的数据,他也只会输出这个占32个字节
内存数据的最后八位,从而导致丢失数据,所以该类提供了writeInt()等可以一次输出多个字节的方法!
------------------------------
可以操作基本数据类型的流对象:DataInputStream DataOutputStream
这两个流对象的构造函数都需要接收一个相应的字节流对象。他之所以可以操作基本数据类型是因为他
里面有操作基本数据类型的方法,比如writeInt()、writeBoolean( )、readInt()、readBoolean()等方法;
在使用时需要注意的是:对于一段数据是使用的writeInt()、writeBoolean( )、writeDouble()
等方法存入的数据,在从文件中取出是也要按照相应的读取顺序取出,否则数据会发生紊乱;
还要关注基本数据操作流中的两个比较特殊的方法:
writeUTF( String str):按照特定的编码表将字符串写入输出流;这里写进内存中后本来占两个字节的字符将
会占用4个字节
String readUTF():将流中的字节数据解析成相应的字符串;
这两种方法必须配套使用,否则写进文件的数据解析不出来!
-------------------------------
可以操作字节数组的流对象-----用于操作内存中的数据
由于这种流对象操作的是内存中的数据,而不涉及调用系统资源,所以不需要关闭流对象,就算是关闭了
流对象,还是可以继续使用里边的方法;而且里面的方法除了writeTo()以外,都不会发生IOException!
ByteArrayInputStream :在构造时需要接受字节数组作为数据源;
ByteOutputStream:在构造时,不需要定义数据目的,因为他本身有一个可变长度字节数组,作为数据的
目的。此外还可以通过writeTo()将内存数组中的数据写入到别的地方;
此外还有可以操作字符数组和字符串数组的流对象:
CharArrayInputStream CharArrayOutputStream
StringReader StringWriter
----------------------------------------------------------------------------------------------------------------------
字符编码:
常见的字符编码表有:
ASCII码表: 美国码表 一个字节的七位
ISO8850-1: 欧洲码表 一个字节的八位
GBK2312: 中国早期的码表 两个字节代表一个字符
GBK: 中国升级的码表 两个字节代表一个字符
Unicode: 国际标准码表 -----------------
UTF-8: ---------------- 有可能一个字节代表一个字符--->
格式为:八个字节中第一个字节为0
有可能两个字节代表一个字符--->
格式为:第一个字节以110打头,第二个字节以10打头;
有可能三个字节代表一个字符--->
格式:第一个字节以1110打头,第二个字节以10打头,第三个字节以10打头
其中我们最常用的码表是GBK和Unicode这两个码表 ,对于用GBK码表写入的数据必须通过GBK码
表来编译,同理用UTF-8码表写入的数据也必须通过UTF-8来编译。如果写入时和读取时实用的码表不一致,
那么读取的数据将会发生错误;
比如:我们通过UTF-8码表将“你好”写入到文件中,这时只能通过UTF-8码表来编译文件,这时如果
直接用记事本将用文件打开(也就是相当于通过GBK编译),看到的是“浣犲y”。相应的在记事本中写入“你好”,
然后再通过UTF-8码表读取数据的话,展现在控制台上的是“??”我们可以通过这个来判断码表的使用情况,
来帮助我们发现错误!
编码:是将字符串变成字节数组:String--->byte[] 对应方法:str.getBytes( 编码表);
解码:是将字节数组变成字符串:byte[]--->String 对应方法:new String(byte[ ],编码表)
下面我们通过代码来演示编码和解码:
import java.util.*;
class EncodeDemo{
public static void main(String[] args){
String s = “你好”;
byte[] b1 = s.getBytes(“GBK”); //将字符串通过GBK码表变成字节数组!
System.out.println(Arrays.toString(b1));
String s1 = new String(b1,“gbk”); //将字节数组再通过GBK码表变成字符串
System.out.println(“s1:”+s1);
byte[] b2 = s.getBytes(“utf-8”);
//将字符串通过utf-8码表变成字节数组,一个字符占3个字节
System.out.println(Arrays.toString(b2));
String s2 = new String(b2,“GBK”);
/*将字节数组再通过GBK码表变成字符串。两个字节组成一个字符,
所以解出来三个字符,解除的数据和写入的“你好”肯定不一致*/
System.out.println(“s2:”+s2);
}
}
联通------美丽的编码错误!
如果在一个记事本中写入联通二字,然后保存,在打开记事本,发现联通变成不认识的字符了!
这时因为联通二字他们在内存中的字节的二进制形式是:
11000001 10101010 11001101 10101000
由于他的字节的二进制数据的识别头符合utf-8的编码规则,所以记事本就将解码的码表当作了utf-8,于是用gbk编码存入的数据,用utf-8解码当然会出错了!