IO(Input Output)流
- IO流用来处理设备之间的数据传输
- Java对数据的操作是通过流的方式
- Java用于操作流的对象都是IO包中
- 流按操作数据分为两种:字节流和字符流
- 流按流向分为:输入流,输出流。
英文码表 ASCII码表
中文码表 GB2312 GBK GB18030
国家标准码表 UNICODE UTF-8(国际标准化组织 整一张表 各个国家的文字都包含)
字符流的由来--字符流对象中融合了编码表
字符流基于字节流
IO流常用基类
- 字节流的抽象基类:
putStream,OutputStream
- 字符流的抽象基类:
ader,Writer。
- 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀.
InputStream的子类FileInputStream。
Reader的子类FileReader。
后缀名是父类名,前缀名是功能
既然IO流是用来操作数据的,那么数据的最常见体现形式是:文件。
FileWriter
需求:在硬盘上,创建一个文件并写入一些文字数据 。
public static void main(String[] args)throws IOException{
//创建一个FileWriter对象 。该对象一被初始化就必须要明确被操作的文件
//该文件会被创建到指定目录下,如果该目录下有同名文件,会被覆盖
FileWriter fw = new FileWriter("demo.txt");
//调用 write方法,将 字符串写入到流中。
fw.write("abcd");
//刷新流对象中的缓冲中的数据,将数据刷新的目的地.
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲的数据,将数据刷到目的地中,
//和flush的区别 :flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
注意:操作IO流会出现IO异常 。try...catch 或者 throws
凡是和设备上发生数据关系,都会产生异常
IO异常的处理方式
import java.io.FileWriter; import java.io.IOException; public class FileWriterDemo { public static void main(String[] args){ FileWriter fw = null; try { fw = new FileWriter("demo.txt"); fw.write("abcd"); } catch (IOException e) { e.printStackTrace(); } finally{ try { if(fw!=null) fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
对已有的文件的数据续写 , 传递一个true参数,代表不覆盖已有的文件,并在已有的文件的末尾处续写
FileWriter fw = new FileWriter("demo",true); fw.write("nihao\r\nma");//回车符\r\n
FileReader
-
文本文件读取方式一:
知识点:int read() 一次只能读一个字符 ,返回是读取的字符的int型,读到-1结束
FileReader fr = new FileReader("demo.txt"); int ch = 0; while((ch=fr.read())!=-1){ System.out.println("ch="+(char)ch); } fr.close();
- 文本文件读取方式二:
知识点:int read(char[] cbuf)将字符读入数组,返回的是读取字符的个数FileReader fr = new FileReader("demo2.txt"); char[] buf = new char[1024]; int num = 0; while((num =fr.reader(buf))!= -1) { syso(new String(buf,0,num)); } fr.close();
FileReader----FileWriter
需求:将C盘的一个文件存放在D盘的一个文件中
public static void main(String[] args){ FileWriter fw = null; FileReader fr = null; try { fw = new FileWriter("d:\\copy.txt"); fr = new FileReader("c:\\copy.txt"); char[] cbuf = new char[1024]; int len = 0; while((len=fr.read(cbuf))!=-1){ fw.write(cbuf,0,len); //这儿要注意,要把有效长度的数据写进去 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ if(fw!=null) try { fw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(fr!=null) try { fr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
拷贝的原理图
字符流的缓冲区
- 缓冲区的出现提高了对数据的读写效率.
对应类
- BufferedWriter
- BufferedReader
- 缓冲区要结合流才可以使用
- 在流的基础上对流的功能进行了增强
缓冲区技术的原理:
其实就是这个对象里面封装了数组。先把数据都存起来,存完以后再一次性的写出去。把数组变成对象,以后就不用数组了,方便。
- BufferedWriter字符写入流缓冲区
public static void main(String[] args)throws IOException{ FileWriter fw = new FileWriter("demo.txt"); BufferedWriter bw = new BufferedWriter(fw); for(int x=1;x<5;x++){ bw.write("abced"); bw.newLine();//换行,不管是Linux还是Windows都是换行 bw.flush();//防止停电,边写边刷新 } bw.close();//关闭缓冲区就等于关闭缓冲区所关联的流对象 }
- BufferedReader字符读取流缓冲区
该缓冲区提供了一次读一行的方法,方便于对文本数据的读取---String readeLine() 返回字符串 不包含任何行终止符 到达流末尾,则返回 null
public static void main(String[] args)throws IOException{ FileReader fr = new FileReader("demo.txt"); BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null){ System.out.println(line); } }
练习:通过缓冲区复制一个.java文件public static void main(String[] args){ BufferedWriter bufw = null; BufferedReader bufr = null; try { bufw = new BufferedWriter(new FileWriter("D:\\copyWriter.txt")); bufr = new BufferedReader(new FileReader("C:\\copyReader.java")); String line = null; while((line = bufr.readLine())!=null){ bufw.write(line); bufw.newLine(); //这个不要忘记 bufw.flush(); }catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ if(bufw!=null) try { bufw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(bufr!=null) try { bufr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
eaderLine的原理图
自定义一个类模拟BufferedReader其中包含一个功能和readLine一致的方法。
class MyBufferedReader extends Reader{ private Reader r = null; //用Reader而不用FileReader是因为用了多态,增加扩展性,把Reader体系中想增加缓冲技术的类都可以传进来 public MyBufferedReader(Reader fr) { this.r = fr; } //定义一个临时容器 ,原BufferedReader是字符数组 //为了掩饰方便,定义一个StringBuilder容器,因为最终还是要将数据变成字符串。 public String myReadeLine()throws IOException{ StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1){ //存回车以外的字符,要进行判断 if("ch"=="\r") continue; if("ch"=="\n") return sb.toString(); else sb.append((char)ch); } //为了防止读到的数据末尾没有回车符而不返回值,要进行下面的判断if if(sb.length()!=0) return sb.toString(); return null; } public void myClose()throws IOException{ r.close(); //关 缓冲区流的内部其实就是关与缓冲区关联的流对象 } //实现父类的抽象方法 @Override public void close()throws IOException{ r.close(); } @Override public int read(char[] cbuf, int off, int len) throws IOException{ return r.read(cbuf, off, len);//用传进来的子类的方法,因为传进来的子类已经实现了父类的所有抽象方法。 } }
装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类 ,将已有的对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
BufferedReader就是用装饰设计模式的思想,如readeLine()方法。 装饰类通常会通过构造方法接收被装饰的对象。 并基于被装饰类提供增强的功能 。
装饰设计模式和继承的区别:
为什么不用继承的方式把缓冲类作为子类继承某一个类,而用装饰设计模式整一个独立的类?
因为继承,每一个需要缓冲区技术的类都要有一个带缓冲区技术的子类,多了会使整个体系臃肿,而独立的缓冲类,谁需要缓冲技术就把谁传进来,
不需要那么多的子类,并且降低了类与类之间的关系 ,增强了灵活性。 装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所有装饰类和被装饰类通常都属于一个体系中的。
LineNumberReader 带行号的装饰类
继承了BufferedReader,多两个方法,getLineNumber()获取行号,默认从0开始 ,setLineNumber()设置当前行号
public static void main(String[] args)throws IOException{ LineNumberReader lnr = new LineNumberReader(new FileReader("xxx.java")); String line = null; lnr.setLineNumber(100);//从100开始 while((line=lnr.readLine())!=null){ System.out.println(lnr.getLineNumber()+":"+line); } lnr.close(); }
自定义类模拟LineNumberReader
class MyLineNumberReader extends BufferedReader{ private int lineNumber; public MyLineNumberReader(Reader r) { super(r); } public String myReadLine()throws IOException{ lineNumber++; return super.readLine(); } public void mySetLineNumber(int count){ this.lineNumber=count; } public int myGetLineNumber(){ return lineNumber; } }
字节流
FileOutputStream:字节写入流
- 字节写入流不需要刷新,字符流刷新是因为在底层调用了字节流,然后将其存入一个数组,因为内部封装了缓冲,所以字符流需要刷新。
public static void writeFile()throws IOException{ OutputStream ops = new FileOutputStream("E:\\demo.txt"); ops.write("111abcsd".getBytes()); ops.close(); }
FileInputSteam:字节读取流
- available(),返回输入流read读取的文件的剩余字节大小。可以直接创建相同的缓冲区大小,不用写循环,如果文件太大,此方法不可用。(JVM默认分配的内存是64M)
public static void readeFile()throws IOException{ InputStream fis = new FileInputStream("e:\\demo.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=fis.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } }
应用available()方法:
public static void readeFile2()throws IOException{ InputStream fis = new FileInputStream("e:\\demo.txt"); byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区不用循环 fis.read(buf); System.out.println(new String(buf)); }
- 拷贝图片:
public static void copyPicture(){ OutputStream fos = null; InputStream fis = null; try { fos = new FileOutputStream("f:\\a.jpg"); fis = new FileInputStream("E:\\360data\\重要数据\\桌面\\aa.jpg"); byte[] buf = new byte[fis.available()]; fis.read(buf); fos.write(buf); } catch (Exception e) { throw new RuntimeException("拷贝图片失败"); } finally{ try { if(fos!=null) fos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try {if(fis!=null) fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
- 通过字节流缓冲区拷贝Mp3;
不需要手动定义缓冲区数组了,因为Buffered里已经封装了
public static void copy_1()throws IOException{ MyBufferedInputStream bis = new MyBufferedInputStream(new FileInputStream("e:\\a.mp3")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:\\b.mp3")); int b = 0;//不需要定义字节数组byte[],Buffered中已经封装了 while((b=bis.myRead())!=-1){ bos.write(b); } bis.myClose(); bos.close(); }
- 自定义字节流的缓冲区
class MyBufferedInputStream { private FileInputStream in; private byte[] buf = new byte[1024]; private int count,pos; MyBufferedInputStream(FileInputStream in){ this.in = in ; } //为什么返回的是int而不是byte public int myRead()throws IOException{ if(count==0){ count = in.read(buf); if(count<0) return -1; pos=0; } int b = buf[pos]; count--; pos++; return b&255; } public void myClose()throws IOException{ in.close(); } }
为什么字节读取中的read方法返回的不是byte而是int
因为一个字节是8位的二进制,,当read读取到8个1时,就会返回-1,在没有读完数据就结束了。为了避免这种情况的发生,把byte转成int型,通过&255操作。这时候返回的值的二进制是32位,前面24位是零,后面8位是1。
当然这样做会使原有的数据发生改变,所有在写入流中的write()方法就把read返回的值做一次转换,只保留后面的8个1,。
总结:read方法在提升,write方法在强转 字节流缓冲区读取流和写入流要成对出现。
读取键盘录入。
System.out:对应的是标准的输出设备,控制台。
System.in:对应的是标准的输入设备,键盘。
需求:
通过键盘录入一行数据后,就将改行数据打印。
如果录入的数据是over,那么停止录入。
public static void main(String[] args)throws IOException { InputStream in = System.in; StringBuilder sb = new StringBuilder(); while(true){ int ch = in.read(); if(ch=='\r') continue; if(ch=='\n'){ String s = sb.toString(); System.out.println(s.toUpperCase()); sb.delete(0, sb.length()); if("over".equals(s)) break; } sb.append((char)ch); } in.close(); }
键盘录入,ctrl+c返回的-1(键盘是录入不进去的)
转换流
l InputStreamReader读取转换流字节流转换成字符流的桥梁
键盘录入最常见写法。
BufferedReader bis = new BufferedReader(new InputStreamReader(System.in));
可以一次读取一行键盘录入内容
public static void main(String[] args)throws IOException { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\22.txt"))); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s = null; while((s=br.readLine())!=null){ if("over".equals(s))break; bw.write(s.toUpperCase()); bw.newLine(); bw.flush(); } bw.close(); br.close(); }
l OutputStreamWriter写入转换流 字符流转字节流的桥梁
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\22.txt")));
流操作规律:
两个明确:
1, 明确源和目的
a) 源:输入流。InputStream Reader
b) 目的:输出流。OutputStream Writer
2, 明确操作的是不是纯文本文件
是 字符流
不是 字节流
通过设备来区分用哪个对象:
a) 内存
b) 硬盘
c) 键盘
扩展:按照指定编码表存储或读取数据时,用转换流。因为只有转换流可以指定编码表。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"),"utf-8");
InputStreamReaderosw = new InputStreamReader(new FileInputStream("a.txt"),"utf-8");
字符流的底层封装了字节流,其实就是封装了可以指定表码表的code,和一个字节数组缓冲区。
按照流操作规则步骤练习
将一个文本文件打印在控制台上。
源:
1,InputStream Reader
2,是纯文本 Reader
3,设备,硬盘: 一个文件,FileReader
4,需要高效。
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
目的
1,OutputStream Writr
2,是不是纯文本 是 Writer
3,设备 控制台 System.out
4,需要用到写入转换流 OutputStreamWriter
5,需要高效 BufferedWriter
BufferedWriter bw = new BufferedWriter(new OutputSteamWriter(System.out));
public static void main(String[] args)throws IOException { BufferedReader br = new BufferedReader(new FileReader("D:/workspace/javaenhance/src/cn/itcast/day4/ReadIn.java")); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } br.close(); bw.close(); }
System.in System.out 的setIn 和setOut方法。可以设置键盘录入和控制台输出的流对象 具体用于参考java参考文档
异常的日志信息
异常信息打印在控制台上没意义,如果让异常信息保存到一个文件中呢?
void
printStackTrace(PrintStream s)
将此 throwable 及其追踪输出到指定的输出流。
public class ExceptionInfor { /** * @param args * @throws FileNotFoundException */ public static void main(String[] args) throws FileNotFoundException { try { int[] arr = new int[3]; System.out.println(arr[3]); } catch (RuntimeException e) { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy--MM--dd HH:mm:ss"); String s = sdf.format(d); PrintStream ps = new PrintStream("e:\\exception.log"); ps.println(s); //System.setOut(ps); //e.printStackTrace(); e.printStackTrace(ps); } } } //一般用log4j工具处理日志信息
打印当前系统属性信息
void
list(PrintStream out)
将属性列表输出到指定的输出流。
public class SystemInfo { public static void main(String[] args) throws IOException{ Properties ps = System.getProperties(); //把键值对list集合关联到打印流 PrintStreamps.list(new PrintStream("e:\\system.txt")); //ps.list(System.out); //System.out.println(ps); } }
File类
:文件和目录路径名的抽象表示形式。
- 用来将文件或者文件夹封装成对象
- 方便对文件与文件夹进行操作。
- File对象可以作为参数传递给流的构造函数
- 了解FIle类中的常用方法。
static String
separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
File类的创建
public static void consMethod(){ //将a.txt封装成File对象,可以将已有的和未出现的文件或者文件夹封装成对象 File f1 = new File("a.txt"); //把目录名和文件名分明作为File对象的构造参数 File f2 = new File("E:\\abc","a.txt"); File d = new File("E:\\abc"); //把目录名封装成对象作为构造参数 File f3 = new File(d,"a.txt"); //跨平台分隔符File.separatorFile f4 = new File("E:"+File.separator+"abc"+File.separator+"a.txt"); }
File类的常用方法
- 创建
boolean createNewFile():在指定位置创建文件,如果文件已存在,返回flase。和输出流不一样,输出流对象一建立就创建文件,文件存在会覆盖。
boolean mkdir:创建文件夹,一级文件夹
boolean mkdirs:创建多级文件夹
- 删除
boolean delete():删除失败返回flase,如果文件正在使用则删除不了。
void deleteOnExit():在程序退出时删除指定文件。通常放在File类定义语句的后面。
- 判断
boolean
canExecute()
测试应用程序是否可以执行此抽象路径名表示的文件。
boolean
exists()
测试此抽象路径名表示的文件或目录是否存在。
boolean
isDirectory()
测试此抽象路径名表示的文件是否是一个目录。
boolean
isFile()
测试此抽象路径名表示的文件是否是一个标准文件。
对一个File对象判断是文件还是文件夹的时候,必须判断文件是否存在 exists()
boolean
isHidden()
测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean
isAbsolute()
测试此抽象路径名是否为绝对路径名。
- 获取
getName:获取名称
getPath:获取相对路径
getAbsolutePath:获取绝对路径
getParent:获取父目录 没有返回null
getParentFile:获取父目录对象
long
lastModified()
返回此抽象路径名表示的文件最后一次被修改的时间。
long
length()
返回由此抽象路径名表示的文件的长度。
boolean
文件列表
static File[]
listRoots()
列出可用的文件系统根。即列出机器中有效盘符public static void listRoots(){ File[] files = File.listRoots(); for(File f:files){ System.out.println(f); } }
String[]
list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。包含隐藏文件public static void listDemo(){ File file = new File("e:"); String[] names = file.list(); for(String name:names){ System.out.println(name); } }
String[]
list(FilenameFilter filter)
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。文件名过滤:
public static void FilenameFilter(){ File file = new File("d:\\html"); if(file.isDirectory()){ String[] arr = file.list(new FilenameFilter(){ @Override public boolean accept(File dirFile ,String name){ /*if(name.endsWith(".bmp")) return true; else return false;*/ return name.endsWith(".html"); } }); for(String s:arr){ System.out.println(s); } } }
File[]
listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。public static void listFiles(){ File dirFile = new File("d:"); File[] files = dirFile.listFiles(); for(File file : files){ System.out.println(file); }
递归调用
函数自身调用自身
- 限定条件
- 要注意递归的次数,尽量避免内存的溢出
递归调用练习:
- 1+2+3+...+100;
- 十进制转换二进制
public class DiGuiDemo { private static StringBuilder sb = new StringBuilder(); public static void main(String[] args) { int sum = sum(100); //System.out.println(sum); String binString = toBin(6); System.out.println(binString); } //1加到100的和,用递归 public static int sum(int num){ if(num==1) return 1; else { return num+sum(num-1); } } //整数转换二进制 public static String toBin(int num){ if(num>0){ toBin(num/2); sb.append(num % 2); } return sb.toString(); } }
递归调用缺陷:内存泄露拿上面代码举例:如果num=100000;在栈内存中就会有100000个方法块,超出内存所能承受的大小。导致内存泄露。
练习::通过递归调用遍历指定目录内的所有文件:
import java.io.File; public class ShowDirDemo { public static void main(String[] args) { File dirFile = new File("D:\\html\\"); showDir(dirFile,0); } public static void showDir(File dir,int level){ System.out.println(getLevel(level)+dir.getName()); level++; File[] files = dir.listFiles(); for(File file : files){ if(file.isDirectory()){ showDir(file,level); } System.out.println(getLevel(level)+file); } } public static String getLevel(int level){ StringBuilder sb = new StringBuilder(); sb.append("|--"); for(int x=0; x<level; x++){ sb.insert(0, " "); } return sb.toString(); } }
练习:创建java文件列表
public class JavaFileList {
public static void main(String[] args) throws FileNotFoundException {
File dirFile = new File("d:\\java110");
List<File> list = new ArrayList<File>();
fileToList(dirFile,list);
File file = new File(dirFile,"javaList.txt");
writeToFile(list,file.toString());
}
public static void fileToList(File dir,List<File> 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<File> list ,String javaListFile){
BufferedWriter bWriter = null;
try {
bWriter = new BufferedWriter(new FileWriter(javaListFile));
Iterator<File> iterator = list.iterator();
while(iterator.hasNext()){
bWriter.write(iterator.next().getAbsolutePath());
bWriter.newLine();
bWriter.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
if(bWriter!=null)
try {
bWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Properties
- 是HashTable的子类,具有Map的特点,而且它里面存储是键和值都是字符串,所以不用泛型。
- 是集合中和IO技术相结合的容器。
- 该对象的特点:可以用于存储键值对形式的配置文件。
常用的方法:
String | getProperty(String key) 用指定的键在此属性列表中搜索属性。 |
String | getProperty(String key,String defaultValue) 用指定的键在属性列表中搜索属性。 |
void | list(PrintStream out) 将属性列表输出到指定的输出流。 |
void | list(PrintWriter out) 将属性列表输出到指定的输出流。 |
void | load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。 |
void | load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 |
Object | setProperty(String key,String value) 调用 Hashtable 的方法 put 。 |
Set<String> | stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。 |