Java

谈谈 Java IO

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 0 写在前面 IO 操作是任何编程语言都无法回避的问题,因为 IO 操作是机器获取和交换信息的主要途径。 Java 的 IO 是以流为基础进行输入输出的,所有的数据被串行化写入输出流或从输入流读入。 java.io 提供了全面的 IO 接口,包括文件读写、标准设备输出等。 注:流是一个很形象的概念。当程序需要读取数据时会开启一个通向数据源的流,这个数据源可以是文件、内存或网络连接。同样,当程序需要写入数据时会开启一个通向目的地的流,而数据就在数据源和目的地之间“流动”。 下图为 Java IO 体系的组成结构。由图可知, Java IO 主要分为字节流和字符流,其中字节流以 InputStream 和 OutputStream 向下继承,字符流以 Reader 和 Writer 向下继承。 本文将详细讲解字节流和字符流,并简要介绍随机存取文件 RandomAccessFile 和 Apache 的 IO 类库。 1 预备知识 在讲解 IO 相关的知识前,我们有必要了解一下字节、字符、编码等概念。毕竟 IO 操作实质上是基于字节、字符来实现的。 1.1 字节 字节是通过网络传输信息或硬盘、内存存储信息的单位,也是计算机用于计量存储容量和传输容量的一种计量单位。一个字节等于 8 位二进制,是一个很具体的存储空间,如 0x01,0x20 。 1.2 字符 字符是人们使用的记号,抽象意义上的一个符号。如“1”,“a”,“¥”,“好”等。实质上,字符是由字节组成的,一个字符可以由一个或多个字节组成。 1.3 字符集 不同国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的字符。汉字标准(GB2312)就是一个字符集,它规定的是汉字的编码标准。 字符集(编码)有两层含义: 使用哪些字符,也就是哪些字符、汉字或符号会被收入标准中,所包含“字符”的集合就叫做“字符集”; 规定每个字符分别用一个字节还是多个字节存储,用哪些字节存储,这个规定就叫做编码。 字符集和编码一般同时制定,字符集除了有“字符集合”含义外,还包含了“编码”的含义。 值得注意的是,为了使国际间信息交流更方便,国际组织制定了 UNICODE 字符集,它为各种语言中的每一个字符设定了统一且唯一的数字编号,以满足跨平台、跨语言进行文本交换、处理的要求,如我们熟悉的 UTF-8 。

谈谈向上转型、向下转型

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 更新说明:本文于 2017-10-01 修改部分内容。 0 写在前面 某天下午,群上抛出了一个关于 Java 向上转型的问题。 于是,我们以此为开端讨论了一个下午,从多态到类加载顺序。讨论结束后想着是不是要写篇文章记录下,所以有了这篇文章。 本文将谈及向上转型、向下转型的概念,后续有时间将扩充多态相关的更详细的知识。 1 向上转型 1.1 概念 向上转型是指子类对象转型为父类类型。通俗的说就是,对象变量是父类类型,但该对象变量引用的却是子类对象。 举个栗子,苹果是水果的一种,但我们有时会说苹果是水果或直接把苹果说成水果。其实,这样的说法就是向上转型,我们把苹果抽象为水果。伪代码表示如下: 水果 对象变量名 = new 苹果(); Apple 是 Fruit 的子类。对象变量 apple 向上转型为 Fruit 类型,但 apple 仍引用的是 Apple 对象。 Fruit apple = new Apple(); //向上转型 向上转型是对父类对象的方法的扩充,即父类对象可访问子类重写父类的方法。也许你会问,既然访问的还是子类对象的方法,为什么不直接声明为子类类型呢? 这就是 Java 抽象编程的奥秘。向上转型是实现多态的一种机制,我们可以通过多态性提供的动态分配机制执行相应的动作,使用多态编写的代码比使用对多种类型进行检测的代码更加易于扩展和维护。 其实我们会经常有意无意的使用转型。比如在我之前写到的关于装饰者模式装饰者模式的文章就用到了向上转型的概念。 两种不同的咖啡 Decaf 、 Espresso 是 Coffee 的子类, Coffee 是 Drink 的子类。因为咖啡店可能会同时售卖不同的饮品,所以我们只需把不同的咖啡抽象为饮品即可,再通过引用不同的具体咖啡品种来表示不同的饮品。这样的场景其实大家都很熟悉,这就是向上转型的应用。也许你已经发现,向上转型可以跨层次。比如 Decaf 是 Coffee 的子类, Coffee 是 Drink 的子类,可以由 Decaf 直接向上转型为 Drink 。

【设计模式】4. 观察者模式

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 1 观察者模式 1.1 简介 观察者模式是对象之间实现多对一依赖的一种设计方案,其可让多个观察者同时监听某一个主体对象(被观察者)。当主体对象的状态发生变化时,可通知所有观察者对象,使它们能自动更新状态。同时,观察者对象的增加或减少不会影响主体对象,也不会影响其它观察者。观察者模式对象之间的低耦合非常有利于项目功能扩展以及后期维护。 1.2 思路 观察者模式主要可以分为四种角色: 主体对象:此角色可以是接口,也可以是抽象类或具体类。主体对象必须包含三种方法,分别是添加观察者对象、移除观察者对象、通知观察者对象。 观察者:此角色一般是接口,里面一般只有一个 update 方法,在被观察者的状态发生变化时, update 方法就会被触发调用。 具体的主体对象:此角色是为了便于扩展,可以定义被观察者的一些具体的业务逻辑。 具体的观察者:此角色是观察者接口的具体实现,可以定义被观察者对象发生变化时,观察者所要处理的业务逻辑。 1.3 使用场景 观察者模式适用于在软件系统的某一个对象发生变化时,依赖它的其它对象也要做出相应改变的场景。 举个实际应用场景: 气象站发布气象数据,天气软件A、天气软件B都要获取气象站发布的气象数据。那么,气象站就可以作为主体对象(被观察者),而天气软件A、天气软件B就可以作为观察者。天气软件A、天气软件B都可以监听气象站的数据,气象站每次发布气象数据,就可以通知天气软件A、B更新的数据。 当然,如果天气软件B不想再获取气象站的数据,就退出观察者队列。而天气软件A依然在观察者队列,还可以继续获取气象数据,气象站和天气软件A都不受影响。 观察者模式正是满足这个场景的设计方案中比较重要的一种。 1.4 优缺点 优点: 观察者与被观察者之间耦合度低; 观察者模式可以实现广播; 观察者模式符合“开闭原则”的要求; 实现表示层与数据逻辑层的分离,定义了稳定的消息更新传递机制。 缺点: 观察者和被观察者之间若存在循环依赖,会使它们循环调用,可能导致系统崩溃; 若主体对象有很多直接或间接的观察者时,通知所有观察者将花费大量时间。 2 实例 还是拿前面提到的气象站的栗子。我们通过程序模拟气象站发布气象数据,天气软件A和天气软件B获取数据。 2.1 普通的解决方案 气象站将数据保存在局部变量,通知天气软件A获取,天气软件A将数据保存并显示出来。 (1)气象站 /* WeatherData.java */ public class WeatherData { //气象站 private float mTemperature; private float mPressure; private float mHumidity; private WeatherSoftwareA wfa; public WeatherData(WeatherSoftwareA wfa){ this.

String相关特性

1 字符串实例化两种方式的区别 String 的实例化方式有两种,一种是直接赋值,另一种是通过 new 关键字实例化字符串。 1.1 直接赋值 String str1 = "hello"; 使用直接赋值方式,只要以后声明的字符串内容相同,则不会再开辟新的内存空间。这是Java中共享设计的思想,Java 通过创建一个字符串池,字符串池中保存了多个字符串对象。若新实例化的字符串对象已经在对象池中,则不再重新定义,直接从对象池中取出使用。 具体到例子, str1 通过直接赋值,因为字符串池不存在“hello”,所以创建一个新的实例置入字符串池,并在栈中开辟一个内存空间直接指向“hello”。 如果我们声明新的字符串 str2=“hello”, str2 将指向“hello”存放的内存空间,不会再开辟一个新的内存空间。这是因为,JVM会先到字符串池中查找,如果有的话返回字符串池中这个实例的引用,否则才创建一个新的实例并置入字符串对象池。因此,通过直接赋值可以只占用一个内存空间。 1.2 new关键字 String newstr1 = new String("hello"); 使用 new 关键字实例化字符串,不管如何都将在堆中开辟一个新的空间。 new 创建字符串实例时会先在对象池中查找是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址。如果没有,则在堆中创建一份,然后返回堆中的地址。 具体到例子, newstr1 通过 new 关键字实例化,无论对象池是否存在相同值的字符串,都会在堆中开辟一个新的内存空间,赋值后将在栈中开辟一个新的空间存放 newstr1 ,也就是说使用 new 关键字实例化字符串总共开辟了两个内存空间。因为多出了从字符串池拷贝到堆内存的操作。 1.3 字符串池 字符串池的优点是避免了相同字符串的创建,节省内存,省去创建相同字符串的时间;另一方面,字符串池的缺点是牺牲了 JVM 在常量池中遍历对象所需要的时间,不过时间成本相比之下较低。 1.4 小结 直接赋值只需一个内存空间,相同的字符串可以重复指向同一个内存空间; new 创建字符串实例无论如何会在堆中开辟一个新的内存空间,因此总共占用两个内存空间。 综上,使用直接赋值的方法较好,可以避免占用多余的内存空间。 2 字符串比较“==”与 equals 的区别 字符串比较是较为常用的功能, Java 中主要提供了两种字符串比较方式,“==”和 equals ,如下: String str1 = "hello"; String str2 = new String("hello"); System.

抽象类与接口的区别

0 写在前面 抽象类和接口有共同点,也有不同点。在实际应用中,我们有时会不知道什么时候用抽象类,什么时候用接口。因此,我们有必要了解抽象类和接口的相关知识以及它们之间的区别,这样以后在二者之间的选择将更有依据。 1 抽象类 在面向对象中,我们通过类来描绘对象,但并不是所有的类都是用来描绘对象的。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 抽象类是实现多态的一种机制,抽象类可以包含具体方法,也可以包含抽象方法(由继承它的子类实现这些方法)。 抽象类的特性总结如下: 抽象类不能被实例化; 抽象类可以有构造方法; 抽象方法必须由其子类重写(Override); 只要包含有一个抽象方法的类,就必须定义为抽象类; 抽象类可以包含有具体实现的方法,也可以不包含抽象方法; 子类的抽象方法不能与父类的抽象方法重名; abstract 不能与 private 、 static 、 final 、 native 并列修饰同一个方法。 注:关于子类实例化时抽象类是否被实例化的问题 抽象类不能实例化,子类实例化时会初始化父类,因为子类继承了父类的变量、方法等,但初始化不等同于实例化。实例化是指用类创建对象,为对象开辟内存空间。 举个栗子: Duck 是一个抽象类,里面包含构造方法、抽象方法、具体方法。 GreenHeadDuck 继承自抽象类 Duck ,所以它必须重写抽象类 Duck 的抽象方法 display ,而具体方法 Quack 、 Swim 可以不重写。 /* Duck.java */ public abstract class Duck{ //抽象类Duck public Duck(){ //构造方法 } public abstract void display(); //抽象方法:外观 public void Quack(){ //具体方法:嘎嘎叫 System.out.println("--gaga--"); } public void Swim(){ //具体方法:游泳 System.

Java 进程通信(共享内存)

0 写在前面 说到进程通信,我们很轻易就能想到经典的 Socket 通信。但像 Socket 这样的网络通信会增加额外的网络负担,同时也增加了一定的代码量。 共享内存方式是实现进程通信的另外一种方式,其具有数据共享,系统快速查询、动态配置、减少资源耗费等优点。 共享内存特点: 可被多个进程打开访问; 读写操作的进程在执行读写操作时,其他进程不能进行写操作; 多个进程可以交替对某一共享内存执行写操作; 一个进程执行内存写操作后,不影响其他进程对该内存的访问,同时其他进程对更新后的内存具有可见性; Java 进程间的共享内存通过内存映射文件 NIO (MappedByteBuffer)实现,不同进程的内存映射文件关联到同一物理文件。该文件通常为随机存取文件对象,实现文件和内存的映射,即时双向同步。 1 要点 1.1 MappedByteBuffer Java IO 操作的 BufferedReader 、 BufferedInputStream 等相信大家都很熟悉,不过在 Java NIO 中引入了一种基于 MappedByteBuffer 操作大文件的方式,其读写性能极高。 MappedByteBuffer 为共享内存缓冲区,实际上是一个磁盘文件的内存映射,实现内存与文件的同步变化,可有效地保证共享内存的实现。 1.2 FileChannel FileChannel 是将共享内存和磁盘文件建立联系的文件通道类。FileChannel 类的加入是 JDK 为了统一对外设备(文件、网络接口等)的访问方法,并加强了多线程对同一文件进行存取的安全性。我们在这里用它来建立共享内存和磁盘文件间的一个通道。 1.3 RandomAccessFile RandomAccessFile 是 Java IO 体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。 举个栗子: 如果我们要向已存在的大小为 1G 的 txt 文本里末尾追加一行文字,内容如下“ Lucene 是一款非常优秀的全文检索库”。其实直接使用 Java 中的流读取 txt 文本里所有的数据转成字符串后,然后拼接“ Lucene 是一款非常优秀的全文检索库”,又写回文本即可。 但如果需求改了,我们要想向大小为 5G 的 txt 文本里追加数据。如果我们电脑的内存只有 4G ,强制读取所有的数据并追加,将会报内存溢出的异常。显然,上面的方法不再合适。

重写、重载的区别

0 写在前面 重写和重载是面向对象程序设计中十分重要的两个概念,其中重写又称为覆盖。 通俗的理解就是,重写就是将一个方法的实现过程再写一遍,覆盖原有方法;重载就是增加一个方法的不同实现,而不覆盖原有的方法。 1 重写(覆盖) 重写是子类对父类方法的实现过程的重新编写。重写的方法应与被重写的方法具有完全一致的返回值、方法名称、参数列表,也就是外壳完全一样,实现过程发生改变。 重写规则: 参数列表必须与原有方法完全相同; 返回类型必须与原有方法相同; 访问权限不能比父类中被重写的方法的访问权限更低; 父类成员方法只能被其子类重写; 声明为 final 的方法不能被重写; 声明为 static 的方法不能被重写,但能够被再次声明; 构造方法不能重写。 2 重载 重载与重写不同之处在于重载与原有方法具有同样的名称,但参数列表可以不同。重载的方法虽然名称和原有方法一样,但是可以认为它们是不同的方法。 重载规则: 重载的方法必须改变参数列表; 重载的方法可以改变返回类型; 重载的方法可以改变访问修饰符; 方法能在同一个类中或在一个子类中被重载。 3 对比 参考资料:http://www.runoob.com/java/java-override-overload.html

【设计模式】3. 装饰者模式

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 1 装饰者模式 1.1 简介 装饰者模式可动态地向对象添加额外的属性或行为。装饰者模式是类继承的另外一种选择,类继承在编译时候增加行为,而装饰模式是在运行时增加行为。在对象功能扩展方面,装饰者模式比继承更有弹性。 1.2 思路 通过增加一个装饰类包裹原来的类,包裹的方式一般是通过将原来的对象作为装饰类的构造函数的参数,从而在原有对象上添加新的功能。 1.3 使用场景 在不影响其它对象的情况下,想要动态地给单个对象添加新的属性或功能。 1.4 优缺点 优点: 可用一个或多个装饰者包装一个对象,扩展性强; 对象可以在任何时候被修饰,即动态修饰对象; 非继承,避免子类爆炸。 缺点: 出现额外的装饰类,使程序变得复杂。 2 实例 模拟咖啡店: 假设,咖啡店有两种咖啡可选: Decaf 和 Espresso ,两种配料可选: Chocalate 和 Milk 。 不同的咖啡和配料可以搭配成不同的饮品,比如 Decaf 、 Espresso 、 Decaf + Chocalate 、 Decaf + Milk 、 Espresso + Chocalate 、 Espresso + Milk 六种不同的饮品。 我们要写个程序来计算不同饮品的价格,比如客户点了一杯 Espresso + Chocalate ,输出饮品的价格和描述信息。 2.1 糟糕的解决方案 设计超类 Drink ,继承自超类 Drink 的子类 Decaf 、 Espresso 、 Decaf + Chocalate 、 Decaf + Milk 、 Espresso + Chocalate 、 Espresso + Milk 。

【设计模式】2. 策略模式

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 1 策略模式 1.1 简介 策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。 比如,买东西要付款,但是付款的方式有多种,比如有的人现金支付,有的人网上支付,有的人找人代付。 1.2 思路 策略模式将每一个算法封装起来,实现算法族(业务规则),这些算法可互换代替。 策略模式通过分离变化部分,封装接口,基于接口实现不同的功能。也就是说,对于同一类型的操作,将复杂多样的处理方式分离开来,有选择的实现各自特有的操作。通常,在超类放行为接口对象,在子类设定具体行为对象。 1.3 使用场景 对于同一类型问题的多种处理方式,仅仅具体行为有差别。 需要安全的封装多种同一类型的操作。 出现同一抽象多个子类,又需要使用if或switch选择语句来选择。 1.4 优缺点 优点: 算法可自由切换; 避免使用多重条件判断; 扩展性良好。 可替换继承关系 缺点: 策略类会增多; 所有策略类都需要对外暴露。 2 实例讲解 我们引入模拟鸭子这个栗子(这是极客学院中讲解的栗子,个人觉得简单易懂,推荐)。 2.1 原始需求 我们通过程序模拟鸭子,假设我们的需求是这样的:鸭子有绿头鸭和红头鸭两种,它们都会嘎嘎叫和游泳。 根据需求,借助面向对象思维,我们可以设计如下代码: (1)首先设计一个抽象类:鸭子 /* Duck.java */ public abstract class Duck{ public Duck(){ } public abstract void display(); //外观 public void Quack(){ //嘎嘎叫 System.out.println("--gaga--"); } public void swim(){ //游泳 System.

【设计模式】1. 单例模式

版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。 1 单例模式 1.1 简介 单例模式是一种基本的常用的软件设计模式。单例模式的核心是确保一个类最多只有一个实例,并提供一个全局访问点。 1.2 思路 一个类只能返回对象的一个引用(始终为同一个)和一个获得此实例的方法(静态方法,通常用 getInstance 命名)。当我们调用 getInstance 方法,如果类保持的引用不为空,则返回此引用;如果类保持的引用为空,则创建该类的实例,并将实例的引用赋予该类保持的引用。 同时,我们还将该类的构造函数定义为私有方法,这样其它地方的代码就无法通过该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法得到该类的唯一实例。 1.3 使用场景 需要频繁实例化然后销毁的对象; 创建对象时耗时过多或耗费资源过多,但又经常用到的对象; 有状态的工具类对象; 频繁访问数据库或文件的对象; 1.4 优缺点 优点: 在内存中只有一个对象,节省内存空间; 避免频繁创建销毁对象,可提高性能; 避免对共享资源的多重占用; 可以全局访问。 缺点: 多线程情况下,应注意线程安全问题; 只能通过单例类提供的方法创建单例对象,不能使用反射; 2 经典实现 这是最简单的最经典的单例模式实现方式,又称为懒汉式(线程不安全)。由于该方式存在不少弊端,通常不采用。但是,我们还是有必要了解,因为它是后面几个优化方式的基础。 注: 懒汉式:指全局的单例实例在第一次使用时被构建; 饿汉式:指全局的单例实例在类装载时就被构建。 /* Singleton.java */ public class Singleton { private static Singleton uniqeInstance = null; private Singleton() { }; public static Singleton getInstance() { if(uniqeInstance == null) { uniqeInstance = new Singleton(); } return uniqeInstance; } }

Java相关特性

Java备忘 float 和 double 不能进行精确运算,因为计算机不能表示所有的小数,采用 BigDecimal 来解决(String)。 switch 语句不能作用在 long 和 String 上。 true 、 false 、 null 不是严格意义上的关键字,而是文字常量(literals)。 float f = 2.5;不正确,因为精度不准确。正确写法:float f = (float)2.5; try{ }里面的 return 语句,紧跟着 finally{ }里的代码会在 return 前先执行。

Java关键字this、super、static、final

0 写在前面 本文主要讲讲 Java 中比较常见的关键字,如 this 、 super 、 static 、 final ,它们在程序中出现频率较高。 1 this 表示类中的属性,比如 this.username = username; 强调调用的是本类的方法; 调用本类的构造方法,如 this(); 调用本类的无参构造方法; 表示当前对象(调用方法的对象)。 2 super 调用父类的构造方法; 调用父类的普通方法; 调用父类的属性。 3 static static 修饰类: static 修饰的类称为静态类,静态类作为类的静态成员存在于某个类中,静态成员类可以不创建父类对象直接创建静态类的对象。 static 修饰变量: static 修饰的变量称为静态变量或类变量,而没有被 static 修饰的变量称为实例变量。静态变量在内存中只有一个拷贝, JVM 只为静态变量分配一次内存,且在加载类的过程中完成静态变量的内存分配;而实例变量每创建一个实例就分配一次内存,实例变量在内存中有多个拷贝,互补影响。 static 修饰方法: static 修饰的方法称为类方法,否则为实例方法,实例方法只有生成对象时才分配内存。类方法可以通过类名直接调用,实例方法只能通过类对象调用。 4 final final 修饰类:被 final 修饰的类表示最终类,其不能被继承,不能当父类; final 修饰变量:被 final 修饰的变量为最终变量,即常量,其值不能修改,在定义时必须赋值。值得注意的是,当 final 修饰的不是普通变量而是对象引用时,变量指向的对象是可变的,不可变的是变量对对象的引用; final 修饰方法: final 修饰的方法为最终方法,父类包含最终方法时,子类不能重写该方法。

JSON相关操作(Java)

1 Java中创建JSON import com.google.gson.JsonArray; import com.google.gson.JsonObject; public class CreateJSON { public static void main(String[] args) { JsonObject object = new JsonObject(); object.addProperty("cat", "it"); JsonArray array = new JsonArray(); JsonObject lan1 = new JsonObject(); lan1.addProperty("id", 1); lan1.addProperty("name", "Java"); lan1.addProperty("ide", "Eclipse"); array.add(lan1); JsonObject lan2 = new JsonObject(); lan2.addProperty("id", 2); lan2.addProperty("name", "Swift"); lan2.addProperty("ide", "XCode"); array.add(lan2); JsonObject lan3 = new JsonObject(); lan3.addProperty("id", 3); lan3.addProperty("name", "C#"); lan3.addProperty("ide", "Visual Studio"); array.add(lan3); object.add("languages", array); object.addProperty("pop", true); System.

XML相关操作(Java)

1 XML文件创建示例 import java.io.File; import java.io.StringWriter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; public class CreateXML { public static void main(String[] args) { try { //DOM DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); //创建Languages标签 Element root = document.createElement("Languages"); root.setAttribute("cat", "it"); //设置Languages标签的属性 //-----接下来创建Languages标签下的三个子标签lan1、lan2、lan3 //lan1 Element lan1 = document.createElement("lan"); //lan标签 lan1.setAttribute("id", "1"); Element name1 = document.