黑马程序员——java基础---I/O流
一、I/O流概述
概念:I/O流用来处理设备之间的数据传输。Java对数据的操作是通过流的方式,而操作流的对象都放在IO包中。
分类:
按操作数据分为:字符流与字节流。
按流向分为:输入流与输出流。
IO流常用基类:
字符流的抽象基类:Reader——Writer
字节流的抽象基类:InputStream——OutputStream
注意:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如FileReader、FileInputStream等。
二、字符流
FileReader和FileWriter专门用于操作文件。
创建文件:
FileWriter fw = new FileWriter("test.txt");
创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步就是在明确数据要存放的目的地。
FileWriter fw1 = new FileWriter("test.txt",true);
创建一个FileWriter对象。传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写(如果没有该文件则会创建一个)。
fw.write("abcde");
调用write方法,将字符串写入到流中。
fw.flush();
刷新流对象中的缓冲中的数据,为了将数据写入目标文件。如果不刷新文件将一直在缓冲中直到fw关闭。
fw.close();
关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据,将数据刷到目的地中。
close和flush区别:
flush刷新后,流可以继续使用,close刷新后,会将流关闭。
读取文件:
1、按字符读取
FileReader fr = new FileReader("demo.txt");
创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常 FileNotFoundException。
1 int ch = 0; 2 while((ch=fr.read())!=-1) 3 { 4 System.out.println((char)ch); 5 } 6 fr.close();
通过调用读取流对象的read方法。read()一次读一个字符,而且会自动读取下一个。返回的是该字符对应的整数,如果读到流的末尾就返回-1。
2、按字符数组读取
FileReader fr = new FileReader("Test.txt");
建立一个流对象,将已存在的一个文件加载进流。
char[] ch = new char[1024];
创建一个临时存放数据的数组。
1 int num = 0; 2 while((num=fr.read(ch))!=-1) 3 //read(char[])返回的是读到字符个数。如果数据没有超过数组长度if也可以,但如果超出了数组长度就要用while,所以使用while 4 { 5 System.out.println(new String(ch,0,num)); 6 } 7 fr.close();
通过调用流对象的读取方法将流中的数据读入到字符数组中。返回的是读取到的字符个数。如果到达流的末尾就返回-1.
IO异常的处理:
创建文件时:
1 FileWriter fw = null; 2 try 3 { 4 fw = new FileWriter("Test.txt"); 5 fw.write("text"); 6 } 7 catch (IOException e) 8 { 9 System.out.println(e.toString()); 10 } 11 finally 12 { 13 If(fw!=null) 14 try 15 { 16 fw.close(); 17 } 18 catch (IOException e) 19 { 20 System.out.println(e.toString()); 21 } 22 }
读取文件时:
1 FileReader fr = null; 2 try 3 { 4 fr = new FileReader("c:\\test.txt"); 5 char[] buf = new char[1024]; 6 int len= 0; 7 while((len=fr.read(buf))!=-1) 8 { 9 System.out.println(new String(buf,0,len)); 10 } 11 } 12 catch (IOException e){ 13 System.out.println("read-Exception :"+e.toString()); 14 } 15 finally 16 { 17 if(fr!=null) 18 { 19 try 20 { 21 fr.close(); 22 } 23 catch (IOException e) 24 { 25 System.out.println("close-Exception :"+e.toString()); 26 } 27 } 28 }
定义文件路径时,一定要将文件路径书写正确注意'\'和'/'的区别。在读取文件时,如果文件不存在,否则会抛出异常。
文件复制简易代码:
1 import java.io.FileReader; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 5 public class CopyFile { 6 7 public static void main(String[] args) throws IOException { 8 9 //创建要写如的文件 10 FileWriter fw = new FileWriter("a.txt"); 11 //读取已有的文件 12 FileReader fr = new FileReader("b.txt"); 13 int ch = 0; 14 while((ch=fr.read()) != -1){ 15 fw.write(ch); 16 } 17 fw.close(); 18 fr.close(); 19 } 20 21 }
字符流缓冲区:字符流缓冲区BufferedWriter和BufferedReader
好处:缓冲区的出现提高了对数据的读写效率。缓冲区是为了提高流的操作效率而出现的。所以得先有流对象,缓冲区才会起作用。
字符写入流缓冲区:BufferedWriter
该缓冲区中提供了一个跨平台的换行符,newLine()方法。
字符读取流缓冲区:BufferedReader
该缓冲区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。其实现原理还是read()方法。
模拟BufferedReader代码:
1 import java.io.*; 2 class MyBufferedReader extends Reader 3 { 4 private Reader r; 5 MyBufferedReader(Reader r) 6 { 7 this.r = r; 8 } 9 //可以一次读一行数据的方法。 10 public String myReadLine()throws IOException 11 //该异常应该抛不应try,因为这是一个功能,为了被其它使用者调用,出现的问题也应该由调用者处理 12 { 13 //定义一个临时容器。BufferReader封装的是字符数组。为了演示方便。定义一个StringBuilder容器。因为最终要将数据变成字符串。 14 StringBuilder sb = new StringBuilder(); 15 int ch = 0; 16 while((ch=r.read())!=-1) 17 { 18 if(ch=='\r') 19 continue; 20 if(ch=='\n') 21 return sb.toString(); 22 else 23 sb.append((char)ch); 24 } 25 if(sb.length()!=0) 26 return sb.toString(); 27 return null; 28 } 29 //覆盖Reader类中的抽象方法。 30 public int read(char[] cbuf, int off, int len) throws IOException 31 { 32 return r.read(cbuf,off,len) ; 33 } 34 public void close()throws IOException 35 { 36 r.close(); 37 } 38 public void myClose()throws IOException 39 { 40 r.close(); 41 } 42 }
装饰设计模式:
概念:自定义一个类,将已有对象传入,基于已有的功能,并提供加强功能,该类就称为装饰类。装饰类通常会通过构造方法接收被装饰的对象,并在对象的基础功能上,提供更强的功能。通常情况下,装饰类和被装饰类都会同属于一个接口或者类,是同一体系中的成员。
装饰和继承的区别:以前是通过继承让每一个子类都具备缓冲功能。那么做继承体系会很复杂,并不利于扩展。现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类与类之间的关系。装饰类因为是增强已有对象,它具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
装饰类demo:
1 class Person 2 { 3 public void chifan() 4 { 5 System.out.println("吃饭"); 6 } 7 } 8 class SuperPerson 9 { 10 private Person p ; 11 SuperPerson(Person p) 12 { 13 this.p = p; 14 } 15 public void superChifan() 16 { 17 System.out.println("开胃酒"); 18 p.chifan(); 19 System.out.println("甜点"); 20 System.out.println("来一根"); 21 } 22 } 23 class PersonDemo 24 { 25 public static void main(String[] args) 26 { 27 Person p = new Person(); 28 //p.chifan(); 29 SuperPerson sp = new SuperPerson(p); 30 sp.superChifan(); 31 } 32 }
LineNumberReader类:BufferedReader的子类,跟踪行号的缓冲字符输入流。此类定义了方法setLineNumber(int lineNumber)和getLineNumber(),它们可分别用于设置和获取当前行号。默认情况下,行编号从0开始。
三、字节流
特点:不但可以操作字节流,还可以字符流等其它流媒体。
字节流读一个字节的read()方法为什么返回值类型不是byte,而是int?
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,write()方法其实在做强转动作,只写该int类型数据的最低8位。所以,read()方法在做提升,write()方法在做强转。可以保证原数据不变化。
字节流自定义缓冲区:
1 import java.io.*; 2 class MyBufferedInputStream 3 { 4 private InputStream in; 5 private byte[] buf = new byte[1024*4]; 6 private int pos = 0,count = 0; 7 MyBufferedInputStream(InputStream in) 8 { 9 this.in = in; 10 } 11 //一次读一个字节,从缓冲区(字节数组)获取。 12 public int myRead()throws IOException 13 { 14 //通过in对象读取硬盘上数据,并存储buf中。 15 if(count==0){ 16 count=in.read(buf); 17 if(count<0) 18 return -1; 19 pos=0; 20 } 21 byte b=buf[pos]; 22 pos++; 23 count--; 24 return b&255;//将b提升为int型并在前面补0 25 } 26 public void myClose()throws IOException 27 { 28 in.close(); 29 } 30 }
四、转换流
读取键盘的录入:System.in和System.out,分别代表了系统标准的输入和输出。默认的输入设备是键盘,输出设备是显示器。
System.in:类型是InputStream,对应的标准输入设备:键盘。
System.out:类型是PrintStream,它是 OutputStream 的子类 FilterOutputStream 的子类,默认的输出设备:控制台。
InputStreamReader 和 OutputStreamWriter:
InputStreamReader :字节转字符,字节流通向字符流的桥梁。将字节解码为字符,专门用于操作字节流的字符流对象。
字节流转换为字符流 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter :字符转字节,字符流通向字节流的桥梁。将字符编码成字节。
字符流转换为字节流 BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
注意:转换流的原因是因为它可以指定编码表,它是字符流与字节流之间的桥梁,方便了字符流与字节流之间的操作。
转换流的应用:字节流处理的是文本数据,即字节流中的数据都是字符时,转成字符流操作更高效。
流操作的基本规律:
1、明确源和目的。
源:输入流 InputStream Reader
目的:输出流 OutputStream Writer
2、操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3、明确使用哪个对象后。通过设备来进一步区分:
源设备:内存,硬盘。键盘
目的设备:内存,硬盘,控制台。
转换流什么时候用?
转换流是字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。
注意:可以通过System类的setIn(),setOut()方法对默认设备进行改变。
五、File类
用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。File对象可以作为参数传递给流的构造函数。流对象也能操作文件,但不能操作文件夹和文件的属性信息。流只能操作数据,想要操作被数据封装成的文件的信息,必须使用file对象。
File类常见方法:
1、创建
createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
mkdir() :只能创建一级文件夹。
mkdirs():可以创建多级文件夹。
2、删除。
delete():删除失败返回false。如果文件正在被使用,则删除不了返回false。
deleteOnExit():在程序退出时删除指定文件。
3、修改
renameTo(File dest): 重新命名此抽象路径名表示的文件。有剪切的效果。
4、判断
canExecute() :判断文件是否可执行。
exists() :文件是否存在.用流操作对象时,如果文件存在了才能去读取,如果文件不存在流一读就会抛异常。可以先用本方法判断。
isFile():是否是文件。
isDirectory():是否是目录。
isHidden():是否是隐藏文件。
isAbsolute():是否是绝对路径。
5、获取信息
length() :文件大小,不能获取文件夹大小。
lastModified(): 最后一次修改的时间。
listRoots():返回当前的系统盘符。
getName()
getPath()
getAbsolutePath():当路径是绝度路径时,path和AbsolutePath返回的是一样的,当路径是相对路径时,path返回相对路径,AbsolutePath返回的是当前路径的前面加上所属目录的绝对路径。
getParent():getParent()+getName()=getPath()
getAbsoluteFile():File和String可以互换,字符串new一下可以变成对象,对象toString一下变成字符串。
getParentFile()
6、其它
list() :返回指定路径下的文件和文件夹名称,包括隐藏文件。调用list方法的file对象必须是封装了一个目录,该目录还必须存在。当对象是一个文件时,返回的是null,会造成空指针异常。如果对象是一个空目录,则返回一个长度为0的数组。
listFiles() :返回指定路径下的文件和文件夹对象。不能拿文件夹内的文件。
六、Properties类
概念:它是Hashtable的子类。具备Map集合的特点,而且它里面存储的键值对都是字符串,是集合中和IO技术相结合的集合容器。
特点:可以用于键值对形式的配置文件。那么在加载数据时,需要数据有固定格式:键=值。
常用方法:
load(InputStream inStream):从输入流中读取属性列表(键和元素对)。
load(Reader reader) :按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
list(PrintStream out) :将属性列表输出到指定的输出流。
list(PrintWriter out):将属性列表输出到指定的输出流。
setProperty(String key, String value) :调用 Hashtable 的方法 put。 改变的是内存中的结果,store方法是将内存中结果保存到文件中。
getProperty(String key) :用指定的键在属性列表中搜索属性。
stringPropertyNames() J:返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。
store(OutputStream out, String comments) :以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
store(Writer writer, String comments): 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符流。
七、字符编码
字符流的出现是为了方便操作字符,更重要是的加入了编码转换。
编码表出现的原因:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
常见的编码表:
ASCII: 美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表,用两个字节表示。GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8:最多用三个字节表示一个字符。一个字节可以的用一个字节,一个字节无法表示的用两个字节,两个字节无法表示的用3个字节,一个字节0开头,两个字节分别用110和10开头,三个字节分别用1110、10和10开头。
转换流的编码应用:可以将字符以指定编码格式存储;可以对文本数据指定编码格式来解读;指定编码表的动作由构造函数完成。
1、将“你好”两个字符查指定的utf-8的码表,获取对应的数字,并写入到text.txt文件中。
1 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("text.txt"),"utf-8"); 2 osw.write("你好"); 3 osw.close();
2、读取硬盘上的文件数据,将获取到的数据查指定utf-8的码表来解析该数据。
1 InputStreamReader isr = new InputStreamReader(new FileInputStream("text.txt"),"utf-8"); 2 char[] buf = new char[10]; 3 int num = isr.read(buf); 4 String s = new String(buf,0,num); 5 System.out.println(s);
传入编码表的方法都会抛出不支持编码异常(UnsupportedEncodingException)。
字符编码:
编码:字符串变成字节数组。
String-->byte[]: str.getBytes(charsetName);
解码:字节数组变成字符串。
byte[]-->String: new String(byte[],charsetName);