为了保存数目不定的对象(如学校的学生), JDK提供了一系列特殊的类(统称为集合), 这些类可以存储任意类型的对象, 并且长度可变(数组的长度不可变). |
●泛型
集合可以存储任何类型的对象, 但是当把一个对象存入集合后, 集合会"忘记"这个对象的类型, 将该对象从集合取出时, 这个对象的编译类型就变成了Object类型. 也就是说, 在程序中无法确定一个集合中的元素到底是什么类型的, 那么在取出元素时, 如果进行强制类型转换就很容易出错. 为了解决这个问题, 在Java中引入了"参数化类型(parameterized type)"这一概念, 即泛型. 具体格式如下: ArrayList<参数化类型> list=new ArrayList<参数化类型>(); 例如: ArrayList<String> list=new ArrayList<String>(); //创建集合对象并指定泛型为String |
●加载因子(load factor)
加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.
冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.
因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷. |
● Comparable 和 Comparator比较
Comparable 是排序接口;若一个类实现了 Comparable 接口,就意味着 "该类支持排序"。 Comparator 接口是比较器;我们若需要控制某个类的次序,可以建立一个 "该类的比较器" 来进行排序。
前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。 可以说前者属于 "静态绑定",而后者可以 "动态绑定"。 我们不难发现:Comparable 相当于 "内部比较器",而 Comparator 相当于 "外部比较器"。 |
用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码,
用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了,
并且在Comparator 里面, 用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。 |
Comparable:
实现Comparable接口要覆盖compareTo方法, 在compareTo方法里面实现比较: public class Person implements Comparable { String name; int age public int compareTo(Person another) { int i = 0; i = name.compareTo(another.name); // 使用字符串的比较 if(i == 0) { // 如果名字一样,比较年龄, 返回比较年龄结果 return age - another.age; } else { return i; // 名字不一样, 返回比较名字的结果. } } } 这时我们可以直接用 Collections.sort( personList ) 对其排序了.
Comparator:
实现Comparator接口需要覆盖 compare 方法: public class Person{ String name; int age }
class PersonComparator implements Comparator { public int compare(Person one, Person another) { int i = 0; i = one.name.compareTo(another.name); // 使用字符串的比较 if(i == 0) { // 如果名字一样,比较年龄,返回比较年龄结果 return one.age - another.age; } else { return i; // 名字不一样, 返回比较名字的结果. } } } Collections.sort( personList , new PersonComparator()) 可以对其排序 |
● 多态, 类型转换同样适用于集合类
Collection books = new HashSet(); //多态, 向上转型, 父类引用指向子类对象. //HashSet是Collection接口的具体实现
就像生产usb 设备的厂商, 厂商是不同的,但是你只要按照usb2.0的标准来生产, 那么你生产出来的产品就能在支持usb2.0的设备上使用。可能你生产的具体方法不一样,但是只要符合接口定义的规范就行了
Collection 是一个规范,它下面的set ,list, vector遵循了Collection的规范,所以说可以充当Collection 来使用。 |
●foreach
foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。 foreach语句是for语句的特殊简化版本,但是foreach语句并不能完全取代for语句,然而,任何的foreach语句都可以改写为for语句版本。 foreach并不是一个关键字,习惯上将这种特殊的for语句格式称之为"foreach"语句。从英文字面意思理解foreach也就是"for 每一个"的意思。实际上也就是这个意思。 |
foreach的语句格式: for(元素类型t 元素变量x : 遍历对象obj){ 引用了x的java语句; } |
List<String> list=new ArrayList<String>(); for(String str : list){ } //在每一轮循环中, 将list的的每一个元素赋值给String类型引用类型变量str |
● 泛型的案例
● compareTo( )方法
大于0表示前一个数据比后一个数据大,0表示相等,小于0表示第一个数据小于第二个数据 |
●java.util.Collections.sort()方法的声明
public static <T> void sort(List<T> list, Comparator<? super T> c)
参数 list--该系统的设立是要排序的列表。 c--该系统的设立是比较器,以确定列表中的顺序。
详见: http://blog.sina.com.cn/s/blog_3c62c21f0100citz.html |
-
<? extends T> 和 <? super T>分别是什么意思?
-
Java中Class<T>与Class<?>之间有何区别?★
<? extends T>首先你很容易误解它为继承于T的所有类的集合,这是大错特错的,相信能看下去你一定见过或用过List<? extends T>吧?为什么我说理解成一个集合是错呢?如果理解成一个集合那为什么不用List<T>来表示?所以<? extends T>不是一个集合,而是T的某一种子类的意思,记住是一种,单一的一种,问题来了,由于连哪一种都不确定,带来了不确定性,所以是不可能通过add()来加入元素。你或许还觉得为什么add(T)不行?因为<? extends T>是T的某种子类,能放入子类的容器不一定放入超类,也就是没可能放入T。
<? super T>这里比较容易使用,没<? extends T>这么多限制,这里的意思是,以T类为下限的某种类,简单地说就是T类的超类。但为什么add(T)可以呢?因为能放入某一类的容器一定可以放入其子类,多态的概念。 |
Class<T>在实例化的时候,T要替换成具体类 Class<?>它是个通配泛型,?可以代表任何类型 |
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。(实例代表类或接口) Instances of the class Class represent classes and interfaces in a running Java application.
在Java中,每个class都有一个相应的Class类||类型的对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class类||类型对象,用于表示这个类的类型信息 |
Class Class<T> java.lang.Object java.lang.Class<T> Type Parameters: T - the type of the class modeled by this Class object. For example, the type of String.class is Class<String>. Use Class<?> if the class being modeled is unknown.
T - 由此 Class 对象建模的类的类型。例如,String.class 的类型是 Class<String>。如果将被建模的类未知,则使用 Class<?>。 |
public class ClassTest { public static void main(String [] args)throws Exception{ String str1="abc"; Class cls1=str1.getClass(); Class cls2=String.class; Class cls3=Class.forName("java.lang.String"); System.out.println(cls1==cls2); System.out.println(cls1==cls3); } } |
<? extends Employee>与<T extends Employee>有什么区别? You define a generic as such:
If you want a generic on Foo to always extend a class Bar you would declare it as such:
The ? is used when you declare a variable.
OR is used as a parameter
|
都表示Employee的某一种子类的意思, 但是 用<T extends Employee>, 之后的代码可以直接用T表示 用<? extends Employee>,之后的代码还是要写<? extends Employee>
T 后面要用的话 必须都是 T ? 后面要用 就不会有限制 但是对于泛型本身而言。没有啥本质区别。
List<? extends Object> lst = new ArrayList<Object>(); // This works List<T extends Object> lst = new ArrayList<Object>(); // Compilation fails |
●如何理解public TestSuite (Class<?>... classes) {
public TestSuite (Class<?>... classes) { for (Class<?> each : classes) addTest(new TestSuite(each.asSubclass(TestCase.class))); } |
Class<?>代表类型,中间的"..."表示可变参数,classes表示该无限参数的名字 调用可以这样写 TestSuite (String.class,Integer.class),但是如果写成数组就不能这样写。必须这样写 TestSuite (new Class<?>[]{String.class,Integer.class}); |
※ 在Java5 中提供了可变参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用,例如下面的add(2, 3)、add(2, 3, 5): public class Varable { public static void main(String[] args) { System.out.println(add(2, 3)); System.out.println(add(2, 3, 5)); }
public static int add(int x, int... args) { //这里的形参表明至少有两个int类型的参数, //如果是add(int... args) 表明至少有一个int类型的参数 int sum = x; for (int i = 0; i < args.length; i++) { sum += args[i]; } return sum; } } |
●包装类
JAVA是一种面向对象语言,java中的类把方法与数据连接在一起,构成了自包含式的处理单元。 但在JAVA中不能定义基本类型(primitive type)对象,为了能将基本类型视为对象进行处理,并能连接相关的方法,java为每个基本类型都提供了包装类,如int型数值的包装类integer,boolean型数值的包装类boolean等,这样便可以把这些基本类型转换为对象来处理了。 |
● 浅拷贝&深拷贝
在C/C++中--简单的来说就是,在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误! |
浅复制(浅克隆): 浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 深复制(深克隆):深复制把要复制的对象所引用的对象都复制了一遍。 |
●Map.put(K key, V value)
Map.put(K key, V value) 获取Map集合的所有键名,并存放在一个Set集合对象中。
key:是要保存到Map集合中的键名。 value:是要保存到Map集合中对应键名的键值对象。 |
●get(Object key))
get(Object key)) 该方法返回指定键所映射(map)的值。 如果此映射不包含该键的映射关系,则返回null。 |
● static public与public static
static public与public static是等价的,这两者只是两个修饰符的顺序不同。
根据oracle.com教材中第八章的描述,如果两个或两个以上的(不同的)字段修饰符出现在字段声明,它们出现的顺序需与FieldModifier一致,这只是习惯,但不是必需的。
Field Modifier(字段修饰符)的顺序: "public protected private static final transient volatile" |
●Q: 为何C++不提供"finally"结构?
A: 因为C++提供了另一种机制,完全可以取代finally,而且这种机制几乎总要比finally工作得更好:就是——"资源获得即初始化"(resource acquisiton is initialization)。(见《The C++ Programming Language》14.4节)基本的想法是,用一个局部对象来封装一个资源,这样一来, 局部对象的析构函数就可以自动释放资源。这样,程序员就不会"忘记释放资源"了。 |
● try catch的形象解释
男的try, 女的把花throw了,逼男的catch ,最后finally 推倒! |
●import后可接包名或包名.类名或包名.*
import 包名; import 包名.类名; import 包名.*; |
●getCanonicalPath
public String getCanonicalPath()throws IOException返回标准路径名; canonical adj. 权威的; |
●File类的mkdir方法
File类的mkdir方法: 先切换到指定的文件夹路径下,之后直接通过mkdir方法进行文件夹创建。 |
●FileFilter
FileFilter是接口, 不是类 |
●java.io.File.isFile()
java.io.File.isFile() 检查表示此抽象路径名的文件是否是一个正常的文件。 |
● 对象的序列化
如果你想在网络上传输一个对象,那你只能通过序列化,因为网络传输不是对象放进方法里,网络传输只能通过字节流,不能直接传输对象,对象被从一段传输到另外一端,然后进行反序列化,还原成发送端的状态。 如果只是把文件保存到本地文件里,也可以用序列化。
※形象理解: 一座大厦好比一个对象,你要把这座大厦搬到另外一个地方去,你想直接挪肯定不行吧?(一般来说,只有传值,没有传对象)但我可以搬砖头吧,一个一个搬,然后搬到目的地重新组合成一个大厦,而序列化就起到了将大厦分成砖头的作用。 |
●实现多线程方式有两种:
实现多线程方式有两种:继承Thread,或定义一个实现Runnable的类。
两种情况都需要重写在run方法中运行的线程代码。Run方法的声明如下: public void run(){ }
JVM的这种设计源自于这样一种理念:"线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。" 基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。
优点是:线程将从不会向应用程序的其他部分抛出可检测异常,可以确保异常不关闭当前正在运行的线程。 缺点是:您必须在代码中处理更大的异常集。
※Java中的异常分为受检查的异常和不受检查的异常。 (1)受检查的异常:这种在编译时被强制检查的异常称为"受检查的异常"。即在方法的声明中被声明(被throw)的异常。 (2)不受检查的异常:在方法的声明中没有声明,但在方法的运行过程中发生的各种异常被称为"不被检查的异常"。这种异常是错误,会被自动捕获。 |
Java采用的是单线程编程模型, 即在我们自己的程序中如果没有主动创建线程的话, 只会自动创建一个线程, 通常称为主线程。 |
一、定义线程 1、扩展java.lang.Thread类。 此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。
Thread的子类应该重写该方法。
2、实现java.lang.Runnable接口。 void run() 使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。 方法run的常规协定是,它可能执行任何所需的操作。
二、实例化线程 1、如果是扩展java.lang.Thread类的线程,则直接new即可。 2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法: Thread(Runnable target) Thread(Runnable target, String name) Thread(ThreadGroup group, Runnable target) Thread(ThreadGroup group, Runnable target, String name) Thread(ThreadGroup group, Runnable target, String name, long stackSize)
三、启动线程
在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
※ 对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。 |
● 基本线程
package demo;
public class demo1{ public static void main(String[] args){ Person1 p1=new Person1(); Person2 p2=new Person2(); p1.start(); p2.start(); System.out.println("main over!"); }
}
class Person1 extends Thread{ public void run(){ for(int i=0; i<20; i++){ System.out.println("你是谁呀?"); } } }
class Person2 extends Thread{ public void run(){ for(int i=0; i<20; i++){ System.out.println("修理水管的!"); } } } |
|
你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? main over! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 修理水管的! 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? 你是谁呀? |
|
main线程在main()函数执行完之后就退出了,但它并不一定是最先执行的线程 |
|
如果在Person1和Person2的run方法里加一个Thread.yield(), 有可能轮流输出: 你是谁呀? 修理水管的! 你是谁呀? 修理水管的! 你是谁呀? 修理水管的! ……
但yield()只是让CPU进行轮换,并不保证下次轮换到的不是本线程, 因为也有可能输出: 你是谁呀? 修理水管的! 修理水管的! 你是谁呀? 修理水管的! 你是谁呀? 修理水管的! 修理水管的! …… …… 你是谁呀? 你是谁呀? 你是谁呀? 修理水管的! |
|
通过测试, 可以写成super.yield(); (因为super代表父类) ; 但似乎没必要, 因为子类里面没有重写run()方法. |
●printStackTrace()
printStackTrace()方法的意思是:在命令行 打印 异常信息在程序中出错的位置及原因 |
●子类覆盖父类时
老毕的视频中有特别说明过: 1.在子类覆盖父类方法的时候,子类抛出的异常必须是和父类异常一致,或者是父类异常的子类。 2.如果父类或者接口没有异常抛出,子类覆盖父类时出新异常,只能try不能throws。 在Thread类中的run()方法是没有申明抛出异常的,所以继承Thread类,只能自己处理异常,不可以抛出来。 |
●线程的工作原理
首先要明白线程的工作原理,jvm有一个main memory,而每个线程有自己的working memory,一个线程对一个variable进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory。多个线程同时操作同一个variable,就可能会出现不可预知的结果。根据上面的解释,很容易想出相应的scenario。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据. |
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。 如果每次运行结果和单线程运行的结果是一样的,而且其它的变量的值也和预期的是一样的,就是线程安全的。
如何做到线程安全: 四种方式 sychronized关键字 1. sychronized method(){} 2. sychronized (objectReference) {/*block*/} 3. static synchronized method(){} 4. sychronized(classname.class) 其中1和2是代表锁当前对象,即一个对象就一个锁,3和4代表锁这个类,即这个类的锁。要注意的是sychronized method()不是锁这个函数,而是锁对象,即:如果这个类中有两个方法都是sychronized,那么只要有两个线程共享一个该类的reference,每个调用这两个方法之一,不管是否同一个方法,都会用这个对象锁进行同步。 注意:long 和double是简单类型中两个特殊的咚咚:java读他们要读两次,所以需要同步。 |
●线程的优先级
线程的优先级越高, 只是获取CPU的执行时间片多一些,并不是比别的线程先执行。 |
●临界资源。
一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。 属于临界资源的硬件有打印机、磁带机等, 软件有消息缓冲队列、变量、数组、缓冲区等。 诸进程间应采取互斥方式,实现对这种资源的共享。 |
●线程对象和线程的区别
线程对象不是线程本身,线程本身是代码的一次执行过程,在这个执行过程中,线程可以执行代表它的线程对象所定义的方法, 也可以执行其它的线程对象或者一般对象中的方法。
线程对象是可以被不同线程操作的对象. |
●死锁&锁
死锁(deadlock): 两个对象互相争夺某一资源 具体来说: 一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。 |
活锁(livelock): 两个对象互相谦让某一资源 |
活锁有一定几率解开。而死锁(deadlock)是无法解开的。 |
Java中的锁(lock)即C++中的互斥锁/互斥量(mutex) |
当多个线程同时访问一块内存, 就有可能出现数据不完整的问题. 此时, 我们需要一种机制来"同步"各线程对它的访问. (所谓的"同步", 是指协调、安排, 使之步调一致). 这种机制就是"互斥锁"机制, 即线程A获取"锁(lock)"之后, 线程A可访问和线程B共享的内存区,线程的B就不能获得这个锁, 直到这个锁被释放(unlocked), 然后线程B获取锁, 才能访问与A共享的内存区. 互斥锁使用原则: 当一个线程占有锁(lock)时, 应该尽快地完成对共享数据的访问. 因为别的线程还在等待这个锁呢.
一般策略: 直接把数据拷贝一份出来, 然后再做处理.(假设处理数据需要较长时间) |
●互斥锁与信号量之间的区别
实现线程间的同步和互斥的主要机制是:信号量和互斥锁
互斥锁与信号量之间的区别: 互斥量用于线程的互斥,信号量用于线程的同步(以互斥为基础)。 这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥(Mutual Exclusion):是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。 例如: 任务A需要使用打印机,任务B也需要使用打印机,那么只有在任务A使用结束的情况下B才能使用打印机,所有A和B之间是间接关系,那么实现这种间接关系的机制就是互斥.但是互斥无法限制访问者对资源的访问顺序,即访问的无序的.(这里就是指无法限制A和B哪个先访问打印机)
同步(Synchronization):是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的;在少数情况是下, 同步是指可以允许多个访问者同时访问资源(即不是互斥)。 例如:任务A对某个缓冲区进行写操作,任务B从这个缓冲区进行读操作,那么A和B就是直接的关系,那么实现这种直接关系的机制就是同步.大多数在建立在互斥的基础上,通过机制实现访问者对资源的有序访问.(这就是指先让A写入数据到缓冲区, 再让B从缓冲区中读数据)
例如: 生产者, 消费者 问题 Producer()—可视为一个线程, 每隔几秒生成一个物品, 放到缓冲区里 Consumer()—也可视为一个线程, 一旦发现缓冲区里有物品, 即刻取走
这两个线程会发生互斥, 因为它们共享一个缓冲区, 如何避免一个商品还没生产完整就被取走?→互斥量(lock) 如何保证商品的完整性,并且缓冲区的物品被及时取走(一旦商品放入缓冲区, 立刻被取走)?→信号量(semaphore, 用于实现线程间的通知机制) ※ 信号量的原理类似PV操作, P表示通过的意思,V表示释放的意思。 |
●JAVA反射机制:
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 |
●尽管Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。 |
●transient关键字
Java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient(短暂的),序列化对象的时候,这个属性就不会序列化到指定的目的地中。 在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段(属性)的生命周期仅存于调用者的内存中而不会写到磁盘里持久化. 我们知道在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。 http://www.cnblogs.com/lanxuezaipiao/p/3369962.html |
●按照流的角色分,可以分节点流和处理流
可以从/向一个特定的IO设备(磁盘 网络)读/写数据流,称为节点流,节点流也被称为低级流(Low Level Stream). 使用节点流进行输入/输出时,程序直接连接实际的数据源,和实际的输入输出节点连接.
处理流则用于对一个已经存在的流进行连接或封装,通过封装后流来实现读/写功能.处理 流也称为高级流. 当使用处理流来进行输入/输出时,程序并不会连接到实际的数据源,没有和实际的输入/输出连接. 使用处理流明显的好处是: 只要使用相同的处理流,程序就可以采用完全的输入/输出代码来访问不同的数据源; 随着处理流所包装的节点流改变,程序实际所访问的数据源也相应发生改变。
Java使用处理流来包装节点是一种典型的装饰器设计模式,通过处理流来包装不同节点,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能 |