本文主要介绍了阻塞流、非阻塞流、序列化和流工具的使用场景,但不介绍流的实现方式。

io流的结构

io流的层次结构

面向线程的、阻塞式I/O:java io

1.InputStream / Reader

对比InputStream和Reader所提供的方法, 这两个类的功能基本相同, 只是一个提供了字节byte读, 一个提供了字符char读. 除此之外, InputStream和Reader还支持几个方法来移动记录指针以实现跳读, 重复读等操作.

2.OutputStream / Writer

因为字符流直接以字符作为操作单位, 所以Writer可以用字符串来代替字符数组(以String对象作为参数).

如果进行IO是文本内容,则应该考虑使用字符流;如果进行IO的是二进制内容, 则应该考虑使用字节流.

3.处理流与节点流:

处理流与节点流都包含输入流和输出流;

处理流与节点流相比可以隐藏底层节点流的差异,对外提供更加统一的IO方法,让开发人员只需关心流的操作(使用更加简单, 执行效率更高).关闭最上层的处理流时, 系统会自动关闭该处理流包装的节点流;

4.内存操作流:

字节流以字节数组为节点:ByteArrayInputStream/ByteArrayOutputStream,

字符流以字符数组为节点:CharArrayReader/CharArrayWriter

将数据写入到内存中:ByteArrayInputStream、CharArrayReader

从内存中读取数据:ByteArrayOutputStream、CharArrayWriter

内存操作流一般在生成一些临时信息时才使用,而这些临时信息不需要文件存储起来,而且操作完就需要删除的信息。

5.缓冲流

BufferedInputStream 、BufferedOutputStream 、BufferedReader、BufferedWriter

四个缓冲流增加了缓冲功能, 可以提高IO效率, 但是需要使用flush()才可以将缓冲区的内容写入实际的物理节点.

6.转换流

java.io.InputStreamReader将字节输入流转换成字符输入流

java.io.OutputStreamWriter将字节输出流转换成字符输出流

7.管道流:

用于两个线程间的通信

PipedOutputStream、PipedInputStream

connection()将两个线程管道连接在一起,线程启动后自动进行管道的输入、输出操作。

8.打印流:

PrintStream、PrintWriter

输出信息,可以打印任何的数据类型,如小数、整数、字符串等。处理流

System.out(Eclipse控制台输出是普通颜色的字体)、System.err(Eclipse控制台输出是红色的字体)是PrintStream的对象,PrintStream又是OutputStream的子类。

System.in是InputStream类型的对象,完成从键盘读取数据

9.数据操作流

DataOutputStream、DataInputStream

平台无关的数据操作流,对数据按照一定的格式输入和输出,且输入和输出的格式要对应。

10.合并流

SequenceInputStream

将两个文件的内容合并成一个文件

11.压缩流

将文件或文件夹压缩成zip、jar、gzip格式的文件

zip: java.util.zip(ZipFile、ZipOutputStream、ZipInputStream、ZipEntry)

jar: java.util.jar(JarOutputStream、JarInputStream、JarFile、JarEntry)

gzip: java.util.zip(GZIPOutputStream、GZIPInputStream)

12.回退流

PushbackInputStream 、PushbackReader

如果在输入流中某个不需要的内容被读取进来,可以通过回退流把读取进来的某些数据重新退回到输入流的缓冲区中,需要设置退回缓冲区的大小(默认为1)。

13.对象流

ObjectOutputStream 、ObjectInputStream

一般结合序列化使用,对象的类名, 实例变量(基本类型/数组/引用对象)都会被序列化; 而方法/类变量(static)/transient实例变量都不会序列化.

面向块、非阻塞I/O:java nio

1.数据缓冲操作类:

ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer

缓冲区只能容纳特定的数据类型

2.通道Channel

多路复用的非阻塞 I/O 比面向线程的阻塞 I/O的可伸缩性更好,所有的内容都是先读取或写入到缓冲区中,再通过缓冲区读取或写入到内存。

FileChannel:用于读取、写入、映射和操作文件的通道,可以同时完成输出和输入数据

Pipe.SinkChannel, Pipe.SourceChannel:一个可写入的单向sink通道和一个可读取的单向 source通道组成的通道对

ServerSocketChannel,SocketChannel,DatagramChannel: 针对面向流的侦听套接字的可选择通道

3.FileLock

一个线程操作一个文件时不希望其它线程进行访问,可以通过FileLock锁定这个文件

4.编码转换操作

CharsetEncoder、CharsetDecoder

字节和 Unicode 字符之间转换的解码器和编码器

对象序列化

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流, 从而可以把这种二进制流持久的保存在磁盘上, 或通过网络将这种二进制流传输到另一个网络节点. 其他程序一旦获取到了这个二进制流, 都可以将他恢复成原先的Java对象.

像Date BigInteger这样的值类应该实现Serializable接口,大多数的集合也应该如此. 但代表活动实体的类, 如线程池(Thread Pool), 一般不应该实现Serializable.

  • Externalizable

实现Externalizable接口以实现对象序列化, 这种序列化方式完全由程序员通过writeExternal()和readExternal()决定存储和恢复对象数据的机制。实现Externalizable接口序列化可以带来一定的性能提升,而且还可以完全自己的自定义序列化规则, 因此只有当默认的序列化形式(Serializable)能够合理地描述对象的逻辑状态时,才能使用默认的序列化形式。

文件操作

  • File

Java使用java.io.File来提供对底层文件的抽象, File能够新建/删除/重命名文件和目录,但不能访问文件内容本身(需要使用IO流).

  • java访问文件内容的4种方法

RandomAccessFile:io,随机读取数据,此种访问速度较慢

FileInputStream:io,文件输入流,此种访问速度较慢

缓冲读取(例如:BufferedReader):io,此种访问速度较块

内存映射(MappedByteBuffer):nio,此种访问速度最快

  • RandomAccessFile

RandomAccessFile与普通的Java I/O流不同的是, 他可以支持随机访问文件, 程序可以直接跳转到文件的任意地方读写数据(因此如果只是访问文件的部分内容,或向已存在的文件追加数据, RandomAccessFile是更好的选择).但RandomAccessFile也有一个局限就是只能读写文件, 不能读写其他IO节点.

  • MappedByteBuffer

FileChannel提供了map方法来把文件影射到内存中(这里的内存指的是虚拟内存,并不是物理内存): MappedByteBuffer map(int mode,long position,long size),该方法可以把文件的从position开始的size大小的区域映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer。但同时有个内存占用的问题就是被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,在这之前会一直占用内存。

Scanner

jdk1.5+专门的扫描数据工具类,也可以读取和校验从输入流、文件、字符串、指定源、指定信道为入口的数据。nextline()是通过正则表达式获取每行的内容,RandomAccessFile、BufferedReader是比较每个字符是否是换行符才获取每行的内容。

参考资料

1.Java I/O

概述

两个相似的javaBean之间的复制,简单的可以使用get/set方法实现复制,其效率也是最高的,但过于繁琐,所以本文探讨一些比较流行的实现javaBean对象间复制的开源插件。

工具类介绍

开源的工具类:DozerMapper、Apache BeanUtils和 Apache PropertyUtils、Spring BeanUtils、Jodd BeanUtils、Cglib

以下简单介绍开源工具:

DozerMapper

XML/API/Annotation的方式,支持简单形式的转换、映射,从而更好的处理一些字段不一样的情况,用意就是一个Mapper搞定一切。

Apache BeanUtils和Apache PropertyUtils

两个工具类的使用方式差不多,都是专门用于操作Bean的工具类,目前很多流行的框架基本都离不开他。

Spring BeanUtils

BeanUtils提供对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。

Jodd BeanUtils

Jodd的BeanUtil也提供了相当方便的操作Bean的API,允许读写属性。

Cglib

cglib是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。很多熟悉的框架如spring、hibernate也使用了它。

测试比较

通过测试类来比较各个开源工具实现复制简单的javaBean的性能差异:

以下是代码的依赖包:可以到maven repository官网下载

maven依赖包

<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 <version>3.3.2</version>
</dependency>

<dependency>
 <groupId>log4j</groupId>
 <artifactId>log4j</artifactId>
 <version>1.2.17</version>
</dependency>

<dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-log4j12</artifactId>
 <version>1.6.1</version>
</dependency>

<dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-api</artifactId>
 <version>1.7.12</version>
</dependency>

<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.11</version>
 <scope>test</scope>
</dependency>

<dependency>
 <groupId>com.google.guava</groupId>
 <artifactId>guava</artifactId>
 <version>16.0.1</version>
</dependency>

<dependency>
 <groupId>org.jodd</groupId>
 <artifactId>jodd-bean</artifactId>
 <version>3.4.9</version>
 <scope>test</scope>
</dependency>

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib-nodep</artifactId>
 <version>3.1</version>
</dependency>

<dependency>
 <groupId>commons-beanutils</groupId>
 <artifactId>commons-beanutils</artifactId>
 <version>1.9.0</version>
 <scope>test</scope>
</dependency>

<dependency>
 <groupId>net.sf.dozer</groupId>
 <artifactId>dozer</artifactId>
 <version>5.4.0</version>
 <scope>test</scope>
</dependency>

<dependency>
 <groupId>org.assertj</groupId>
 <artifactId>assertj-core</artifactId>
 <version>1.5.0</version>
</dependency>

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-beans</artifactId>
 <version>3.2.10.RELEASE</version>
</dependency>

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context-support</artifactId>
 <version>3.2.5.RELEASE</version>
</dependency>

源代码

import java.util.Date;
import jodd.bean.BeanCopy;
import net.sf.cglib.beans.BeanCopier;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;
import org.junit.Test;
/**
*A、B类是普通javaBean类
**/
public class BeanCopierPerformanceTest {
    private A getA() {
        A a = new A();
        a.setF1(1);
        a.setF2(1);
        a.setF3("aaaa");
        a.setF4(new Date());
        a.setF5("bbbb");
        return a;
    }
    @Test
    public void testSet() throws Exception {
        A a = getA();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
            B b = new B();
            b.setF1(a.getF1());
            b.setF2(a.getF2());
            b.setF3(a.getF3());
            b.setF4(a.getF4());
            b.setF5(a.getF5());
        }
        long t2 = System.currentTimeMillis();
        System.out.println("get/set time takes " + (t2 - t1));
    }
    @Test
    public void testJodd() throws Exception {
        A a = getA();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
            B b = new B();
            BeanCopy beans = BeanCopy.beans(a, b);
            beans.copy();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("Jodd time takes "+(t2-t1));
    }
    @Test
    public void testCglib() throws Exception {
        A a = getA();
        long t1 = System.currentTimeMillis();
        BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false);
        for (int i=0;i<1000000;i++){
            B b = new B();
            beanCopier.copy(a,b,null);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("Cglib time takes " + (t2 - t1));
    }
    @Test
    public void testBeanUtils() throws Exception {
        A a = getA();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
            B b = new B();
            BeanUtils.copyProperties(a, b);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("BeanUtils time takes " + (t2 - t1));
    }
    @Test
    public void testDozerMapper() throws Exception {
        A a = getA();
        Mapper mapper = new DozerBeanMapper();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
            B b = mapper.map(a, B.class);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("Dozer time takes " + (t2 - t1));
    }
    @Test
    public void testPropertyUtils() throws Exception{
        A a = getA();
        B b =new B();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
            PropertyUtils.copyProperties(b, a);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("PropertyUtils time takes " + (t2 - t1));
    }
    @Test
    public void testSpringUtils(){
        A a = getA();
        B b =new B();
        long t1 = System.currentTimeMillis();
        for (int i=0;i<1000000;i++){
         org.springframework.beans.BeanUtils.copyProperties(a,b);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("spring beanutils time takes " + (t2 - t1));
    }
}

测试结果

结果是以毫秒为单位

复制次数 get/set DozerMapper Apache BeanUtils Apache PropertyUtils Spring BeanUtils Jodd BeanUtils Cglib
100次 0 125 16 15 31 110 84
1000次 0 299 47 32 84 99 79
10000次 15 936 203 110 62 156 47
1000000次 32 44842 16880 8503 3298 6320 79

总结

jdk的直接写set/get是最快的,所以在性能要求高的场景下倒是不妨自己写。另外这样写也是对重构比较友好,这是其他几个工具都做不到的。性能其次是用了字节码生成的cglib,然后将其他的库远远甩在后面,接着spring的BeanUtils,相比于其它库性能也不错。而其他的库性能相差不大,大约1000次拷贝会消耗数毫秒时间,对于性能敏感的应用,特别是一些批量请求,消耗还是比较大的。

参考资料

1.dozer-初识 http://lishaorui.iteye.com/blog/1151513

2.关于BeanCopier的一些思考 http://my.oschina.net/flashsword/blog/404288?fromerr=JmwWOwf4

1.作用和意义

日志在应用开发中必不可少,日志有记录用户操作、定位应用程序的故障根源,进一步来分析用户的行为、应用的性能、执行效果等作用。

2.不同类型的日志

主要分为系统致命错误日志,系统可控错误日志,用户操作日志和程序运行日志。

  • 致命性错误日志:用于记录会影响整个系统正常运行的错误,比如我们在开发过程中的try…catch…模块中抛出的一些未能预料到的系统错误,而且这种错误会导致系统运行失败的信息进行记录

  • 系统可控错误日志:这一类的日志发生之后其实不会导致系统运行出现异常的,可能是对某些数据的初始化深入验证出现的问题

  • 用户操作日志:这一类日志量比较大,同时这一类日志用于跟踪用户的行为分析是非常的重要的应为可以作为用户数据挖掘发现用户的喜好等一些信息

  • 程序运行日志:这一类信息用于记录程序运行的情况。

3.java日志工具

记录日志的开发工具很多,如:java.util.logging、SLF4J、Apache Conmmons Logging、Log4j等,这里不再介绍这些工具。

日志的记录级别也有分:一般情况下,使用得比较多的级别是 FATAL、ERROR、WARN、INFO、DEBUG 和 TRACE 等。这 6 个级别所对应的情况也有所不同:

FATAL:导致程序提前结束的严重错误

ERROR:运行时异常以及预期之外的错误

WARN:预期之外的运行时状况,不一定是错误的情况

INFO:运行时产生的事件

DEBUG:与程序运行时的流程相关的详细信息

TRACE:更加具体的详细信息

4.java日志编码规范

  • 4.1.提交前去除编码帮助日志

一旦你已经完成开发工作,在将代码提交到使用中的 SCM 系统(git、svn……)之前,要去除所有临时的和不必要的日志消息,如:alert、System.out、assert等无用的消息。

  • 4.2 匹配日志等级

致命错误日志的记录使用fatal级别

系统可控错误日志的记录使用error级别

用户操作日志的记录使用debug/info级别

系统运行日志的记录使用trace级别

如果不希望输出错误的日志,那么也可以参考以下方式(以执行环境区分):

成品阶段: 我的代码是 INFO 等级,第三方库是 WARN

测试、集成阶段:我的代码是 DEBUG 等级,第三方库是 WARN

开发阶段:任何有意义的信息

  • 4.3 优化日志的信息

当记录的日志量足够多的时候,如果记录的内容格式不一,杂乱无比,那么对读者来说是很难阅读的和分析的,所以记录的日志最好遵循一定的格式和包含充分的信息:

  1. 发生时间

  2. 出现问题的线程

  3. 日志级别

  4. 出现问题的类文件,类的哪一行,异常栈

  5. 程序入参

  6. 相应的程序员的注释等

  7. 尽量输出一些「不可变的或者唯一」的信息来表示这条日志信息的唯一

  • 4.4 log DEBUG消息之前检查日志等级

这种做法可以阻止代码去创建日志消息和调用 logger,提高产品运行程序的效率:

if ( logger.isDebugEnabled((){
 logger.debug (…….)
}
  • 4.5 了解使用的 logger工具

如果使用SLF4J,记录日志的正确用法应该是:

logger.info(Person name is {}, person.getName());

这里的格式化字符串是常量,不可变消息只有在允许 logging 的情况下才会被创建。

  • 4.6 类中关于Logger等工具的使用

在一个类中通常只使用一个Logger对象且Logger应该是Static和final的,只有在少数需要在构造函数中传递logger的情况下才使用private final。

  • 4.7 注意点
  1. 根据系统使用场景不同,对日志的侧重点就不一样。比如我们做两个系统,一个是「权限管理系统」,另外一个是「信息通知系统」,干一些发邮件,短信等的消息。前者我们需要详细的操作日志,记录「谁在某年某月某日为某人分配了什么权限」,因为审计部门需要。而后者即便发送出错了,重试或者忽略都可以考虑,甚至都不需要记录任何的日志

  2. 重要API、系统间交互等地方需要添加日志,因为都是监控的重点位置

5.谨记

记录日志只是有效地利用日志的第一步,更重要的是如何对程序运行时产生的日志进行处理和分析,但遵循好的java日志编码规范,就能给分析带来更大的便捷和帮助。

参考自:Java日志记录的5条规则