@liayun
2016-06-19T08:29:20.000000Z
字数 17822
阅读 1639
java基础
注意:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
先学习一下字符流的特点。
既然IO流是用于操作数据的,那么数据的最常见体现形式是:文件,那么先以操作文件为主来演示。
例,需求:在硬盘上,创建一个文件并写入一些文字数据。
解:
分析:找到一个专门用于操作文件的Writer子类对象——FileWriter,后缀名是父类名,前缀名是该流对象的功能。
创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件,而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步骤就是在明确数据要存放的目的地。
FileWriter fw = new FileWriter("demo.txt"); // 可能会抛出IOException,为了方便,将异常直接抛给JVM
调用write(),将字符串写入到流中
fw.write("abcde");
刷新流对象中的缓冲区中的数据,将数据刷到目的地中。
fw.flush();
关闭流资源,但是关闭之前会刷新一次内部的缓冲区中的数据,将数据刷到目的地中。和flush()区别:flush刷新后,流可以继续使用,close()刷新后,会将流关闭。
fw.close();
如果想在原有文件上继续加入新的数据呢?
创建一个FileWriter对象时,传递一个true参数,代表不覆盖已有的文件,即在已有文件的末尾处进行数据续写。如:
import java.io.*;class FileWriterDemo {public static void main(String[] args) throws IOException {FileWriter fw = new FileWriter("demo.txt", true);fw.write("nihao\r\nxeixie");fw.close();}}
注意:\r\n在windows中表示行终止符。
IO异常的处理方式:最后无论如何都应关闭资源,所以应放在finally代码块中。
import java.io.*;class FileWriterDemo {public static void main(String[] args) {FileWriter fw = null;try {fw = new FileWriter("demo.txt");fw.write("abcdefg");} catch(IOException e) {System.out.println("catch:"+e.toString());} finally {try {if(fw!=null) // 写在外面也可fw.close();} catch(IOException e) {System.out.println(e.toString());}}}}
例,需求:从硬盘的一个文件中读取内容。
解:
第一种方式读取:
创建一个文件读取流对象,和指定名称的文件相关联,要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException。
FileReader fr = new FileReader("demo.txt");
调用读取流对象的read()方法。read():一次读取一个字符,会自动往下读。
int ch = 0;while((ch=fr.read()) != -1) {System.out.println((char)ch);}
关闭流资源。
fr.close();
原理:
第二种方式读取:通过字符数组进行读取。
创建一个文件读取流对象,和指定名称的文件相关联,要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException。
FileReader fr = new FileReader("demo.txt");
定义一个字符数组,用于存储读到的字符,该read(char[])返回的是读到的字符个数。
char[] buf = new char[1024]; // 字符数组大小为2KBint num = 0;while((num = fr.read(buf)) != -1) {System.out.println(new String(buf, 0, num));}
关闭流资源。
fr.close();
原理:
练习1、读取一个.java文件,并打印在控制台上。
解:
import java.io.*;class FileReaderTest {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("DateDemo.java");char[] buf = new char[1024];int num = 0;while((num=fr.read(buf)) != -1) {System.out.print(new String(buf, 0, num));}fr.close();}}
练习2、将c盘一个文本文件复制到d盘中。
解:
分析:复制原理——其实就是将c盘下的文件数据存储到d盘的一个文件中。
步骤:
第一种复制方式:从c盘读一个字符,就往d盘写一个字符。
public static void copy_1() throws IOException {// 创建目的地FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");// 与已有文件关联FileReader fr = new FileReader("RuntimeDemo.java");int ch = 0;while((ch=fr.read()) != -1) {fw.write(ch);}fw.close();fr.close();}
第二种复制方式:
public static void copy_2() {FileReader fr = null;FileWriter fw = null;try {fw = new FileWriter("SystemDemo_copy.txt");fr = new FileReader("SystemDemo.java");char[] buf = new char[1024];int len = 0;while((len = fr.read(buf)) != -1) {fw.write(buf, 0, len);}} catch(IOException e) {throw new RuntimeException("读写失败");} finally {if(fr != null)try {fr.close();} catch(IOException e) {}if(fw != null)try {fw.close();} catch(IOException e) {}}}
原理:
缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须要先有流对象。
该缓冲区中提供了一个跨平台的换行符:newLine()。
创建一个字符写入流对象
FileWriter fw = new FileWriter("buf.txt");
为了提高字符写入流的效率,加入了缓冲技术。只要将需要被提高效率的流对象作为参数,传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);for (int x = 1; x < 5; x++) {bufw.write("abcd"+x);bufw.newLine();bufw.flush(); // 记住,只要用到缓冲区,就要刷新。}
关闭资源,其实关闭缓冲区,就是在关闭缓冲区中的流对象。
bufw.close();
字符读取流缓冲区,该缓冲区提供了一个一次读一行的方法:readLine(),方便于对文本数据的获取,当返回null时,表示读取到文件末尾。
readLine()方法返回的时候只返回回车符之前的数据内容,并不返回回车符(行终止符)。
创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲区对象的构造函数。
BufferedReader bufr = new BufferedReader(fr);
调用readLine()。
String line = null;while((line = bufr.readLine()) != null) {System.out.print(line);}
关闭资源。
bufr.close();
例,需求:明白了BufferReader类中特有方法readLine()的原理后,可以自定义一个类中包含一个功能和readLine()一致的方法,来模拟一下BufferReader。
解:
import java.io.*;class MyBufferedReader extends Reader {private Reader r;MyBufferedReader(Reader r) {this.r = r;}// 可以一次读一行数据的方法public String myReadLine() throws IOException {// 定义一个临时容器,原BufferReader封装的是一个字符数组// 为了演示方便,定义一个StringBuilder容器,因为最终还是要将数据变为字符串。StringBuilder sb = new StringBuilder();int ch = 0;while((ch=r.read()) != -1) {if(ch == '\r')continue;if(ch == '\n')return sb.toString();elsesb.append((char)ch);}if(sb.length() != 0) // 如果文本数据最后的行终止符故意去掉,那么StringBuilder里面还是有数据的 ,也要给予返回return sb.toString();return null; // 如果已到达流末尾,则返回null}public void myClose() throws IOException {r.close();}/*覆盖Reader类中的抽象方法。*/public int read(char[] cbuf, int off, int len) throws IOException {return r.read(cbuf, off, len);}public void close() throws IOException {r.close();}}
测试自定义类MyBufferedReader。
class MyBufferedReaderDemo {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("buf.txt");MyBufferedReader myBuf = new MyBufferedReader(fr);String line = null;while((line=myBuf.myReadLine()) != null) {System.out.println(line);}myBuf.myClose();}}
装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
举例来说明装饰设计模式:
class Person {public void chiFan() {System.out.println("吃饭");}}class SuperPerson {private Person p;SuperPerson(Person p) {this.p = p;}public void superChiFan() {System.out.println("开胃酒");p.chiFan();System.out.println("甜点");System.out.println("来一根");}}class PersonDemo {public static void main(String[] args) {Person p = new Person();// p.chiFan();SuperPerson sp = new SuperPerson(p);sp.superChiFan();}}
假设有这样一个继承体系:
可发现这样的继承体系很臃肿,所以应该换一个继承体系。这样设计类呢?
class MyBufferReader {MyBufferReader(MyTextReader text) {}MyBufferReader(MyMediaReader media) {}}
可发现该类扩展性很差,可以找到其参数的共同类型,通过多态的形式,可以提高扩展性。
class MyBufferReader extends MyReader {private MyReader r;MyBufferReader(MyReader r) {}}
这样,最后的继承体系就是这个样子的:
结论:装饰模式比继承要灵活,避免了继承体系臃肿,而且降低了类与类之间的关系。装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能,所以装饰类和被装饰类通常都是属于一个体系中的。
例,通过缓冲区复制一个.java文件。
解:
分析:readLine()方法的原理:无论是读一行,或者读取多个字符,其实最终都是在硬盘上一个一个读取,所以最终使用的还是read()方法,一次读一个的方法。
import java.io.*;class CopyTextByBuf {public static void main(String[] args) {BufferedReader bufr = null;BufferedWriter bufw = null;try {bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));String line = null; // 中转站while((line = bufr.readLine()) != null) {bufw.write(line);bufw.newLine();bufw.flush();}} catch(IOException e) {throw new RuntimeException("读写失败");} finally {try {if(bufr != null)bufr.close();} catch(IOException e) {throw new RuntimeException("读取关闭失败");}try {if(bufw != null)bufw.close();} catch(IOException e) {throw new RuntimeException("写入关闭失败");}}}}
一个带行号的缓冲区。
import java.io.*;class LineNumberReaderDemo {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("PersonDemo.java");LineNumberReader lnr = new LineNumberReader(fr);String line = null;lnr.setLineNumber(100);while((line=lnr.readLine()) != null) {System.out.println(lnr.getLineNumber()+":"+line);}lnr.close();}}
练习:模拟一个带行号的缓冲区对象。
解:
class MyLineNumberReader {private Reader r;private int lineNumber;MyLineNumberReader(Reader r) {this.r = r;}public String myReadLine() throws IOException {lineNumber++; // myReadLine()方法读一次自增一次StringBuilder sb = new StringBuilder();int ch = 0;while((ch=r.read()) != -1) {if(ch=='\r')continue;if(ch=='\n')return sb.toString();elsesb.append((char)ch);}if(sb.length() != 0)return sb.toString();return null;}public void setLineNumber(int lineNumber) {this.lineNumber = lineNumber;}public int getLineNumber() {return lineNumber;}public void myClose() throws IOException {r.close();}}
可发现myReadLine()方法与我们自定义类MyBufferedReader中的myReadLine()类似,所以我们可如此做:
class MyLineNumberReader extends MyBufferedReader {private int lineNumber;MyLineNumberReader(Reader r) {super(r);}public String myReadLine() throws IOException {lineNumber++; // myReadLine()方法读一次自增一次return super.myReadLine();}public void setLineNumber(int lineNumber) {this.lineNumber = lineNumber;}public int getLineNumber() {return lineNumber;}}
测试自定义类MyLineNumberReader:
class MyLineNumberReaderDemo {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("CopyTextByBuf.java");MyLineNumberReader mylnr = new MyLineNumberReader(fr);String line = null;mylnr.setLineNumber(100);while((line=mylnr.myReadLine()) != null) {System.out.println(mylnr.getLineNumber()+"::"+line);}mylnr.myClose();}}
字符流:
字节流:
基本操作与字符流类相同,但它不仅可以操作字符,还可以操作其他媒体文件。
例,向一个文本文件中写入数据。
public static void writeFile() throws IOException {FileOutputStream fos = new FileOutputStream("fos.txt");fos.write("abcde".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");byte[] buf = new byte[1024];int len = 0;while((len=fis.read(buf)) != -1) {System.out.println(new String(buf, 0, len));}fis.close();}
第三种方式(不建议使用,因为可能会有内存溢出异常)
public static void readFile_3() throws IOException {FileInputStream fis = new FileInputStream("fos.txt");byte[] buf = new byte[fis.available()]; // 定义一个刚刚好的缓冲区,不用再循环了。不过慎用!!!fis.read(buf);System.out.println(new String(buf));fis.close();}
练习1、复制一个图片。
解:
思路:
import java.io.*;class CopyPic {public static void main(String[] args) {FileOutputStream fos = null;FileInputStream fis = null;try {fos = new FileOutputStream("c:\\2.jpg");fis = new FileInputStream("c:\\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("写入关闭失败");}}}}
练习2、复制一个MP3文件。
解:
分析:通过缓冲区(BufferedOutputStream/BufferedInputStream),演示MP3的复制。
import java.io.*;class CopyMp3 {public static void main(String[] args) throws IOException {long start = System.currentTimeMillis();copy_1();long end = System.currentTimeMillis();System.out.println((end-start)+"毫秒");}// 通过字节流的缓冲区完成复制public static void copy_1() throws IOException {BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\爱的秘密 蓝调口琴曲.mp3"));BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\爱的秘密 蓝调口琴曲_copy.mp3"));int by = 0;while((by=bufis.read()) != -1) {bufos.write(by);}bufos.close();bufis.close();}}
练习3、自定义一个类模拟BufferedInputStream,完成一个MP3文件的复制。
解:
分析:
mp3是由二进制数据组成的,如:11111111-1110000000000000000000101010110111010111010010110001。
问题:自定义的myRead()函数为什么会返回int类型,而不直接返回byte类型呢?
分析:
byte: -1 ----> int: -1
11111111(-1)提升为11111111 11111111 11111111 11111111(-1)
11111111(-1)--->提升为一个int类型,那还不是-1吗??是-1的原因是因为在8个1前面补的都是1导致的。那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。怎么补0呢?(&255)
11111111 11111111 11111111 11111111
& 00000000 00000000 00000000 11111111(255)
------------------------------------------
00000000 00000000 00000000 11111111
所以应把11111111(-1)提升为00000000 00000000 00000000 11111111(255),避免返回-1这种情况。
自定义一个MyBufferedInputStream类,如下:
import java.io.*;class MyBufferedInputStream {private InputStream in;private byte[] buf = new byte[1024*4];private int pos = 0, count = 0;MyBufferedInputStream(InputStream in) {this.in = in;}// 一次读一个字节,从缓冲区(字节数组)获取。public int myRead() throws IOException {// 通过in对象读取硬盘上数据,并存储到buf中。if(count == 0) {count = in.read(buf);if(count < 0)return -1;pos = 0;byte b = buf[pos];count--;pos++;return b & 255; // 必须&255(0xff),然后再返回} else if (count > 0) {byte b = buf[pos];count--;pos++;return b & 0xff; // 必须&255(0xff),然后再返回}return -1;}public void myClose() throws IOException {in.close();}}
复制MP3:
class CopyMp3 {public static void main(String[] args) throws IOException {long start = System.currentTimeMillis();copy_2();long end = System.currentTimeMillis();System.out.println((end-start)+"毫秒");}public static void copy_2() throws IOException {MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\爱的秘密 蓝调口琴曲.mp3"));BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\爱的秘密 蓝调口琴曲_copy_2.mp3"));int by = 0;// System.out.println("第一个字节:"+bufis.myRead()); // 第一个字节:-1,因为读到了连续的11111111while((by=bufis.myRead()) != -1) {bufos.write(by);}bufos.close();bufis.myClose();}}
例,需求:通过键盘录入数据。当录入一行数据后,就将该行数据进行打印,如果录入的数据是over,那么录入停止。
解:
import java.io.*;class ReadIn {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();if("over".equals(s))break;System.out.println(s.toUpperCase());sb.delete(0, sb.length()); // 清空缓冲区} else {sb.append((char)ch);}}}}
通过以上的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine()方法。
能不能直接使用readLine()方法来完成键盘录入的一行数据的读取呢?
readLine()方法是字符流BufferedReader类中的方法,而键盘录入的read()方法是字节流InputStream的方法,那么能不能将字节流转成字符流,再使用字符流缓冲区的readLine()方法呢?此时就需要用到转换流。
例,需求:通过键盘录入数据。当录入一行数据后,就将该行数据进行打印,如果录入的数据是over,那么录入停止。
解:
import java.io.*;class TransStreamDemo {public static void main(String[] args) throws IOException {// 获取键盘录入对象。// InputStream in = System.in;// 将字节流对象转成字符流对象,使用转换流——InputStreamReader// InputStreamReader isr = new InputStreamReader(in);// 为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedReader// BufferedReader bufr = new BufferedReader(isr);// 简写格式,键盘录入最常见写法BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));// OutputStream out = System.out; // 屏幕输出// OutputStreamWriter osw = new OutputStreamWriter(out); // 字符流输出对象转换成字节流输出对象// BufferedWriter bufw = new BufferedWriter(osw);BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));String line = null;while((line=bufr.readLine()) != null) {if("over".equals(line))break;bufw.write(line.toUpperCase());bufw.newLine();bufw.flush();}bufr.close();bufw.close();}}
最痛苦的就是流对象有很多,不知道该用哪一个。但通过三个明确来完成:
例1,将一个文本文件中的数据存储到里另一个文件中(复制文件)。
分析:
FileReader fr = new FileReader("a.txt");BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = new FileWriter("b.txt");BufferedWriter bufw = new BufferedWriter(fw);
练习:将一个图片文件中的数据存储到另一个文件中。(复制图片)
解:
import java.io.*;class MyCopyPicture {public static void main(String[] args) {BufferedInputStream bis = null;BufferedOutputStream bos = null;try {bis = new BufferedInputStream(new FileInputStream("c:\\1.jpg"));bos = new BufferedOutputStream(new FileOutputStream("c:\\ye.jpg"));// 方式一/*int by = 0;while((by=bis.read()) != -1) {bos.write(by);}*/// 方式二byte[] buf = new byte[1024];int len = 0;while((len=bis.read(buf)) != -1) {bos.write(buf, 0, len);}} catch(IOException e) {throw new RuntimeException("复制图片失败");} finally {try {if(bis != null)bis.close();} catch(IOException e) {throw new RuntimeException("读取关闭失败");}try {if(bos != null)bos.close();} catch(IOException e) {throw new RuntimeException("写入关闭失败");}}}}
例2,需求:将键盘录入的数据保存到一个文件中。
分析:这个需求中有源和目的都存在,那么分别分析:
设备:键盘。对应的对象是System.in。
不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文本数据方便,转成字符流,按照字符串操作是最方便的,所以既然明确了Reader,那么就将System.in转换成字符流Reader,用到了Reader体系中的转换流,InputStreamReader。
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader bufr = new BufferedReader(isr);
设备:硬盘上的一个文件。使用FileWriter。
FileWriter fw = new FileWriter("c.txt");
BufferedWriter bufw = new BufferedWriter(fw);
扩展一下,想要把录入的数据按照指定的编码表(如UTF-8),将数据存到文件中,怎么办呢?
设备:硬盘上的一个文件,使用FileWriter。但是FileWriter是使用的默认编码表(GBK),但是存储时,需要加入指定的编码表(UTF-8),而指定的编码表只有转换流可以指定,所以要使用的对象是OutputStreamWriter,而该转换流对象要接收一个字节输出流,而且还可以操作文件的字节输出流,FileOutputStream。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"), "UTF-8");
BufferedWriter bufw = new BufferedWriter(osw);
所以,记住,转换流什么时候使用?字符和字节之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。
练习:将一个文本数据打印在控制台上。
解:
import java.io.*;class MyTest {public static void main(String[] args) {BufferedReader bufr = null;BufferedWriter bufw = null;try {bufr = new BufferedReader(new FileReader("MyCopyPicture.java"));bufw = new BufferedWriter(new OutputStreamWriter(System.out));String line = null;while((line=bufr.readLine()) != null) {bufw.write(line);bufw.newLine();bufw.flush();}} catch(IOException e) {throw new RuntimeException("打印文件到控制台失败");} finally {try {if(bufr != null)bufr.close();} catch(IOException e) {throw new RuntimeException("读取文件关闭失败");}try {if(bufw != null)bufw.close();} catch(IOException e) {throw new RuntimeException("写入关闭失败");}}}}
System类中的字段:in,out,它们各代表了系统标准的输入和输出设备,默认输入设备是键盘,输出设备是显示器。
可通过System类的setIn,setOut方法对默认设备进行改变,不建议使用。
System.setIn(new FileInputStream("PersonDemo.java")); // 将源改成文件PersonDemo.javaSystem.setOut(new PrintStream("zzz.txt")); // 将目的改成文件zzz.txt
log4j:记录日志信息的一个工具。
import java.io.*;import java.util.*;import java.text.*;class ExceptionInfo {public static void main(String[] args) throws IOException {try {int[] arr = new int[2];System.out.println(arr[3]);} catch(Exception e) {try {Date d = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String s = sdf.format(d);PrintStream ps = new PrintStream("exception.log");// ps.write(d.toString().getBytes());ps.println(s);System.setOut(ps);} catch(IOException ex) {throw new RuntimeException("日志文件创建失败");}e.printStackTrace(System.out);}}}
import java.util.*;import java.io.*;class SystemInfo {public static void main(String[] args) throws IOException {Properties prop = System.getProperties();// System.out.println(prop);prop.list(new PrintStream("sysinfo.txt"));}}
通过sysinfo.txt文本文件可以知道平台默认的字符集,即默认字符编码是GBK。