黑马程序员——I/O流(一)
2015-06-25 23:36
591 查看
——- android培训、java培训、期待与您交流! ———-
代码示例:
该缓冲区提供了一次读一行的方法 readline,方便于对文本数据的获取。
当返回null时,表示读到文件末尾
readLine方法返回的时候,只返回回车符之前的数据内容。并不返回回车符。
代码示例:
装饰类通常会通过构造方法接收被装饰类的对象。并基于被装饰的对象的功能,提供更强更全的功能。
装饰类因为增强已有对象,具备的功能和已有对象的功能是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
装饰类比继承灵活,避免了继承体系的臃肿,并降低了类与类之间的关系
代码示例:
代码示例:
System.out:对应的标准输出设备:控制台。
System.in:属于InputStream类型
System.out:属于OutputStream类型
代码示例:
通过三个明确来完成。
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2,明确操作的数据是否是纯文本
是:字符流
不是:字节流
3,当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存、硬盘、键盘。
目的设备:内存、硬盘、控制台。
代码示例:
字符流
代码示例:
/* * 先以操作文件为主来演示 * * 需求:在硬盘上,创建一个文件并写入一些数据。 * * 找到一个专门用于操作文件的Writer子类对象。FileWriter。 后缀名是父类名。前缀名是该流对象的功能。 * */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { // 创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。 // 而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。 // 其实该步就是在明确数据要存放的目的地。 FileWriter fw = new FileWriter("demo.txt");// 会抛出IOException异常 // 调用writer方法,将字符串写入到流中。(注意,数据存在流中,还没有存储到demo.txt文件中) fw.write("abcdef"); // 刷新流对象中的缓冲中的数据 // 将数据刷到目的地中。(数据存储到了demo.txt文件中) // fw.flush(); // 关闭流资源,但是关闭之前会刷新一次内部缓冲区中的数据。 // 将数据刷到目的地中(将数据存到demo.txt文件中) // 和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭 fw.close(); } }
IO流异常处理的规范
/* * IO异常的处理方式 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) { FileWriter fw = null;//fw不能定义在try中,那样会导致fw.close //无法关闭流,因为定义在try中的fw是局部变量 try { fw = new FileWriter("Demo.txt"); fw.write("abcdefgh"); } catch (IOException e) { System.out.println(e.toString()); } finally { if (fw != null)//如果fw不为空,则关闭流。如果try中的 //fw = new FileWriter("KKK:\\Demo.txt") //那么会抛出异常(找不到KKK:\这个盘符), //当抛出异常后fw值为null,fw为空就不能调用close try { fw.close(); } catch (IOException e) { System.out.println(e.toString()); } } } }
字符流的两种读取方式(读取单个字符、读取字符数组)
代码示例:/* * 第一种方式:演示对已有文件的读取,读取单个字符 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) { // 创建一个文件读取流对象,和指定名称的文件相关联。 // 要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException FileReader fr = null; try { fr = new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java"); // 调用读取流对象的read方法 // read():一次读一个字符。而且会自动往下读,当读取到没有文件时,返回-1 int ch = 0; while ((ch = fr.read()) != -1) { System.out.print((char) ch); } /* //这种读取方式也可以,但是比较麻烦 while (true) { int ch = fr.read(); if (ch == -1) break; System.out.println((char) ch); } */ } catch (IOException e) { System.out.println(e.toString()); } finally { if (fr != null) try { fr.close(); } catch (IOException e) { System.out.println(e.toString()); } } } }
/* * 第二种方式:通过字符数组进行读取 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) { FileReader fr = null; try { fr = new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java"); // 定义一个字符数组。用于存储读到的字符。 // 该read(char[])返回的是读到字符个数。 // 数组的大小最好定义成1024的整数倍 char[] chs = new char[1024]; int num = 0; while ((num = fr.read(chs)) != -1) {// 如果还有数据则继续读取,并存入到字符数组中。如果没有数据则然会-1,则不再执行 System.out.print(new String(chs, 0, num));// 将字符数组转换成字符串的时候,从第一个字符开始转换,一直转换到num则不再转换 //如:一个字符数组长度char[1024],num的值为5,那么,new String(chs,0,num)只将前5个字符转换成字符串 } } catch (IOException e) { System.out.println(e.toString()); } finally { if (fr != null) try { fr.close(); } catch (IOException e) { System.out.println(e.toString()); } } } }
使用字符流,复制一个文件
/* * 将D盘一个文本复制到C盘。 * * 复制的原理: * 其实就是将D盘下的文件数据存储到C盘的一个文件中。 * * 步骤: * 1,在C盘创建一个文件,用于存储D盘文件中的数据。 * 2,定义读取流和D盘文件相关联。 * 3,通过不断的读写完成数据存储。 * 4,关闭资源。 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) { // function1(); function2(); } // 第一种方式:读一个字符存一个字符 public static void function1() { FileReader fr = null; FileWriter fw = null; try { // 需要读取的文件 fr = new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java"); // 在c盘中创建一个文件 fw = new FileWriter("C:\\copyDemo1.txt"); int len = 0; while ((len = fr.read()) != -1) { fw.write(len); } } catch (IOException e) { throw new RuntimeException("读取失败"); } finally { if (fr != null)// 如果fr已经创建了对象,则关闭流 try { fr.close(); } catch (IOException e) { throw new RuntimeException("读取失败"); } if (fw != null)// 如果fw已经创建了对象,则关闭流 try { fw.close(); } catch (IOException e) { throw new RuntimeException("读取失败"); } } } // 第二种方式:通过字符数组进行读取 public static void function2() { // 要读取的文件 FileReader fr = null; // 在c盘中创建一个文件 FileWriter fw = null; try { fr = new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java"); fw = new FileWriter("C:\\copyDemo2.txt"); char[] chs = new char[1024]; int len = 0; while ((len = fr.read(chs)) != -1) { fw.write(chs, 0, len);// 装入的字符数为chs数组中的字符个数,这样节省内存空间 } } catch (IOException e) { throw new RuntimeException("读取失败"); } finally { if (fr != null)// 如果fr已经创建了对象,则关闭流 try { fr.close(); } catch (IOException e) { throw new RuntimeException("关闭FileReader流失败"); } if (fw != null)// 如果fw已经创建了对象,则关闭流 try { fw.close(); } catch (IOException e) { throw new RuntimeException("关闭FileWriter流失败"); } } } }
字符读取流缓冲区
缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须先有流对象。该缓冲区提供了一次读一行的方法 readline,方便于对文本数据的获取。
当返回null时,表示读到文件末尾
readLine方法返回的时候,只返回回车符之前的数据内容。并不返回回车符。
代码示例:
import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException{ //创建一个读取流对象和文件相关联 FileReader fr = new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java"); //为了提高字符写入流效率。加入了缓冲技术。将字符读取流对象作为参数,传递给缓冲对象的构造函数 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null){ print(line); } bufr.close(); } public static void print(Object obj){ System.out.println(obj); } }
模拟BufferedReader类中特有方法readLine
/* * 练习: * 模拟BufferedReader类中特有方法readLine。 * * 明白了BufferedReader类中特有方法readLine的原理后, * 可以自定义一个类中包含一个功能和readLine一致的方法。 * 来模拟一下BufferedReader * * 本示例中采用了装饰设计模式 */ import java.io.*; import java.util.*; class MyBufferedReader extends Reader{ private Reader r; public MyBufferedReader(Reader r) { this.r = r; } // 可以一次读一行数据得到方法 public String myReadLine() throws IOException { // 定义一个临时容器。原BufferedReader封装的是字符数组。 // 为了方便演示。定义一个StringBuilder容器。因为最终还是要将数据变成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while ((ch = r.read()) != -1) { if ((char) ch == '\r') { continue; } if ((char) ch == '\n') {// 读取到换行\n时,才往回返 return sb.toString(); } else sb.append((char) ch); } if(sb.length()!=0)//如果容器中还存在元素,则返回元素。例如:当读取的某行字符串没有换行\n时,那么就读取不到换行符,导致没有返回那一行数据, //而此处做判断就是为了返回没有换行的字符串 return sb.toString(); return 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); } //覆盖Reader类中的抽象方法 public void close() throws IOException { r.close(); } } public class Demo { public static void main(String[] args) throws IOException { MyBufferedReader bufr = new MyBufferedReader(new FileReader("D:\\java\\javaCode\\Demo\\src\\Demo.java")); String line = null; while((line = bufr.myReadLine())!=null){ print(line); } bufr.myClose(); } public static void print(Object obj) { System.out.println(obj); } }
装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有对象的功能,并提供加强功能。即,自定义的类称为装饰类。装饰类通常会通过构造方法接收被装饰类的对象。并基于被装饰的对象的功能,提供更强更全的功能。
装饰类因为增强已有对象,具备的功能和已有对象的功能是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
装饰类比继承灵活,避免了继承体系的臃肿,并降低了类与类之间的关系
代码示例:
/* * 装饰设计模式: * 当想要对已有的对象进行功能增强时, * 可以定义类,将已有对象传入,基于已有对象的功能,并提供加强功能。 * 那么自定义的该类称为装饰类 * * 装饰类通常会通过构造方法接收被装饰的对象。 * 并基于被装饰对象的功能,提供更强的功能 */ import java.io.*; class Person { public void chiFan() { System.out.println("吃饭"); } } class SuperPerson extends Person{//继承Person是因为是因为该类中的功能和Person中的功能是相同的。只不过提供了更强的功能。 如superChifan方法 private Person p; public 121f2 SuperPerson(Person p) { this.p = p; } public void superChiFan() {//提供增强的吃饭功能 System.out.println("开胃酒"); p.chiFan(); System.out.println("甜点"); System.out.println("抽根烟"); } } public class Demo { public static void main(String[] args) throws IOException { Person p = new Person(); // p.chifan(); SuperPerson sp = new SuperPerson(p); sp.superChiFan(); } }
自定义LineNumberReader对象
/* 使用装饰模式模拟LineNumberReader中的readLine原理 */ import java.io.*; import java.util.*; class MyLineNumberReader extends MyBufferedReader{ private int lineNumber; public MyLineNumberReader(Reader r) { super(r); } public String myReadLine() throws IOException { lineNumber++; return super.myReadLine(); } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return lineNumber; } } class MyBufferedReader extends Reader{ private Reader r; public MyBufferedReader(Reader r) { this.r = r; } // 可以一次读一行数据得到方法 public String myReadLine() throws IOException { // 定义一个临时容器。原BufferedReader封装的是字符数组。 // 为了方便演示。定义一个StringBuilder容器。因为最终还是要将数据变成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while ((ch = r.read()) != -1) { if ((char) ch == '\r') { continue; } if ((char) ch == '\n') { return sb.toString(); } else sb.append((char) ch); } if(sb.length()!=0) return sb.toString(); return 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(); } } public class Demo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader( "D:\\java\\javaCode\\Demo\\src\\Demo.java"); MyLineNumberReader mlnr = new MyLineNumberReader(fr); String line = null; mlnr.setLineNumber(100); while ((line = mlnr.myReadLine()) != null) { print(mlnr.getLineNumber()+"::"+line); } } public static void print(Object obj) { System.out.println(obj); } }
字节流:
代码示例:
/* * 字节流的方法使用 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { // writeFile(); // readFile_1(); // readFile_2(); readFile_3(); } //使用字节数组的方式,读取Demo.java文件中的内容,并打印到控制台。这时字节数组长度正好是Demo.java文件的长度 public static void readFile_3() throws IOException{ FileInputStream fis = new FileInputStream("D:\\java\\javaCode\\Demo\\src\\Demo.java"); // int len = fis.available(); byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区。不再用循环了 fis.read(buf); print(new String(buf)); fis.close(); } //读取Demo.java文件中的内容,并打印到控制台。使用字节数组的方式读 public static void readFile_2() throws IOException{ FileInputStream fis = new FileInputStream("D:\\java\\javaCode\\Demo\\src\\Demo.java"); byte[] buf = new byte[1024]; int len = 0; while((len=fis.read(buf))!=-1){ print(new String(buf, 0, len)); } fis.close(); } //读取Demo.java文件中的内容,并打印到控制台。读一个打印一个 public static void readFile_1() throws IOException{ FileInputStream fis = new FileInputStream("D:\\java\\javaCode\\Demo\\src\\Demo.java"); int ch = 0; while((ch=fis.read())!=-1){ System.out.print((char)ch); } fis.close(); } //往demo.txt文件中写入abcdef public static void writeFile() throws IOException{ FileOutputStream fos = new FileOutputStream("demo.txt"); fos.write("abcdef".getBytes());//将字符串变成字节数组 fos.close(); } public static void print(Object obj) { System.out.println(obj); } }
演示字节流缓冲区复制一个图片
/* * 通过缓冲区,演示图片的复制。 * * BufferedOutputStream * BufferedInputStream */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args)throws IOException{ long start = System.currentTimeMillis(); copy(); long end = System.currentTimeMillis(); print(end - start); } //通过字节流的缓冲区完成复制 public static void copy()throws IOException{ BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("C:\\1.png")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("C:\\2.png")); int len = 0; while((len = bufis.read())!=-1){ bufos.write(len); } bufos.close(); bufis.close(); } public static void print(Object obj) { System.out.println(obj); } }
自定义BuffreaderInputStream类
代码示例:/* * 模拟BuffreaderInputStream, * 定义一个自己的MyBufferedInputStream */ import java.io.*; import java.util.*; class MyBufferedInputStream { private FileInputStream fis; private byte[] buf = new byte[1024]; private int count = 0, index = 0; public MyBufferedInputStream(FileInputStream fis) { this.fis = fis; } //一次读一个字节,从缓冲区(字节数组)获取 public int myRead() throws IOException { if (count == 0) { //通过fis对象读取硬盘上数据,存储到bs中 count = fis.read(buf);//抓一批数据存入byte数组 if (count < 0) return -1; index = 0; byte b = buf[index]; //获取数组中角标为index的数值 count--; index++; return b&255; //返回角标为index的字节码。 /* 由于返回值类型是int,byte提升为int,如:byte类型的的负一11111111提升为int类型 还是负一 等于11111111-11111111-11111111-11111111, 这样导致int类型的值是-1,最终导致一开始读取数据时,就读到了-1。而导致没有成功写入文件。 我们希望得到int的值是00000000-00000000-00000000-11111111, 这样数据才不会有错误,所以在byte提升为int之前,先 & 11111111。既:b&255。这样才可以得到我们所希望的值 */ } else if(count>0){ byte b = buf[index]; count--; index++; return b&255; } return -1; } public void myClose() throws IOException{ fis.close(); } } public class Demo { public static void main(String[] args) throws IOException { MyBufferedInputStream mbis = new MyBufferedInputStream(new FileInputStream("d:\\2.png")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d:\\1.png")); int by = 0; while((by=mbis.myRead())!=-1){ bos.write(by); } bos.close(); mbis.myClose(); } public static void print(Object obj) { System.out.println(obj); } }
键盘录入
System.in:对应的标准输入设备:键盘。System.out:对应的标准输出设备:控制台。
System.in:属于InputStream类型
System.out:属于OutputStream类型
代码示例:
import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { //获取键盘录入对象 InputStream in = System.in; //将字节流对象转成字符流对象,使用转换流。InputStreamReader InputStreamReader isr = new InputStreamReader(in); //为了提高效率,对字符串进行缓冲技术的高效操作。使用BufferedReader BufferedReader bufr = new BufferedReader(isr); String str = null; while((str = bufr.readLine())!= null){ if(str.equals("over")) break; print(str.toUpperCase()); } } public static void print(Object obj) { System.out.println(obj); } }
/* * System.out:对应的标准输出设备:控制台。 * System.in:对应的标准输入设备:键盘。 * * 将标准输入设备和输出设备,转换成字符流, * 并将输入的结果打印在控制台上,直到输入over时, * 才结束输入 * */ import java.io.*; import java.util.*; public class Demo { 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 os = System.out; // OutputStreamWriter osw = new OutputStreamWriter(os); // BufferedWriter bufw = new BufferedWriter(osw); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//将标准输出设备转换成字符流 String str = null; while((str = bufr.readLine())!= null){ if(str.equals("over")) break; bufw.write(str.toUpperCase()); bufw.newLine(); bufw.flush(); } bufw.close(); } public static void print(Object obj) { System.out.println(obj); } }
操作流的基本规律
流操作的基本规律:通过三个明确来完成。
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2,明确操作的数据是否是纯文本
是:字符流
不是:字节流
3,当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存、硬盘、键盘。
目的设备:内存、硬盘、控制台。
代码示例:
/* 扩展:想把录入的数据按照指定的编码表(UTF-8),将数据存储到文件中 目的: OutPutStream Writer 明确是不是操作文本文件:是!Writer。 明确设备:硬盘,一个文件。使用FileWriter 但是FileWriter是使用默认编码表。GBK 但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。 所以需要用到的对象是OutputStreamWriter。 而该转换流对象要接收一个字节输出流。而且是可以操作文本的字节输出流。FileOutputStream OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8"); 需要提高效率吗?需要!BufferedWriter BufferedWriter bufw = new BufferedWriter(osw); 所以记住,转换流什么时候使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流 */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { //键盘的最常见写法 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demo.txt"), "UTF-8")); String line = null; while ((line = bufr.readLine()) != null) {//当输入"你好"时,utf-8编码产生的文件中,一个字符占3个字节("你好"共6字节)。 //如果将编码改为GBK,那么一个字符占2个字节("你好"共4个字节)。 //使用FileInputStream方法是无法直接读取utf-8编码中"你好"的,因为默认的是GBK编码,无法读取utf-8编码。 //需要使用转换流,将FileInputStream也指定为utf-8才可以读取 if (line.equals("over")) break; bufw.write(line); bufw.newLine(); bufw.flush(); } bufr.close(); bufw.close(); } public static void print(Object obj) { System.out.println(obj); } }
改变默认输入输出设备
/* 改变标准输入输出设备 System.setIn(InputStream in); System.setOut(PrintStream out); */ import java.io.*; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { //修改输入设备。默认的输入设备是键盘,现在改为一个文件。相当于读取文件中的内容 System.setIn(new FileInputStream("D:\\java\\javaCode\\Demo\\src\\Demo.java")); //修改输出设备。默认的输出设备是控制台,现在改为一个文件。平时是输出到控制台,现在是输出到一个txt文件中 System.setOut(new PrintStream("demo.txt")); //键盘输入最常见的写法。因为默认的输入设备改为了一个文件,所以此处相当于读取一个文件中的内容 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //输出到控制台中。但是因为默认输出设备修改为输出到一个文件中,所以输出的结果会保留在demo.txt文件中 BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while ((line = bufr.readLine()) != null) { if (line.equals("over")) break; bufw.write(line); bufw.newLine(); bufw.flush(); } bufr.close(); bufw.close(); } }
异常日记
/* * 将异常的日记信息。既:把异常给存放到一个文件中 */ import java.io.*; import java.text.SimpleDateFormat; import java.util.*; public class Demo { public static void main(String[] args) throws IOException { try { int[] arr = new int[3]; arr[3] = 3; } catch (Exception e) { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd"); String str = sdf.format(date); PrintStream ps = new PrintStream("D:\\haha.txt");//要打印到haha.txt文件中 System.setOut(ps);//将默认的输出设备(控制台)改为一个haha.txt文件 System.out.println(str);//输出语句,结果不是输出到控制台,而是输出到haha.txt文件中 e.printStackTrace(ps);//方法接收的参数为:printStackTrace(PrintStream) } } } /* 有专门弄日记信息的软件:log4j */
相关文章推荐
- 黑马程序员——Java基础---集合(Collection接口、List接口及其子类、增强for)
- java中一些小知识点(面试)
- SSH框架面试题
- Java面试宝典2015版(绝对值得收藏超长版)(一)
- 本文介绍十五道关于Hibernate的面试题及答案
- Java面试宝典2015版(绝对值得收藏超长版)(二)
- 黑马程序员---2015.6.25java基础笔记---装饰模式--字节流拷贝图片--字节流缓冲--字节字符转化--File类
- 黑马程序员-IOS学习笔记(五)类
- 黑马程序员-IOS学习笔记(四)类、对象和方法
- 程序员果真有前端后端客户端吗
- iOS开发面试题(更新中...)
- 黑马程序员-IOS学习笔记(二)常用关键字和方法
- 从首份“电商职场报告”能看出什么
- 黑马程序员——Java基础:集合类、泛型
- 黑马程序员——Java基础--多态
- 面试有感
- php面试题之五——PHP综合应用(高级部分)
- php面试题之一——PHP核心技术(高级部分)
- php面试题之五——MySQL数据库(基础部分)
- php面试题之四——PHP面向对象(基础部分)