使用 spring AOP 监控代码的执行耗时

前言

使用spring提供的aop功能,我们可以很方便的实现动态代理的功能。在使用上,spring提供了两种不同的实现,分别是Spring AOP 和 AspectJ。

提供方式以及对比

spring AOP

概述

  1. 纯java实现,不需要额外的编译流程,不需要引用其他三方包。
  2. 适合集成到Servlet容器或者应用服务中
  3. 仅支持方法级别的代理,不支持成员变量
  4. 设计宗旨是集成IoC,并且有效的解决大部分企业级应用中的需求,不同于AspectJ的细粒度。
  5. 与AspectJ进行互补

非侵略性是spring设计的一个中心原则,一般情况下,不会有spring的代码存在与业务代码中。但是某些情况中,不是这样,比如注解。

实现

默认使用标准JDK中的 动态代理是实现,针对任意接口,都可以实现。

可以配置使用CGLIB进行代理,可以针对类进行代理。CGLIB的使用,对开发者是透明得到,在针对没有实现接口的类进行代理的时候,spring会自动使用CGLIB进行实现。

一般情况下,建议业务类都实现一个接口,是比较好的编程实现

这个另说把,不必要的接口实现了以后,除了繁琐没有其他作用

在一个类实现了多个接口的时候,可以强制使用CGLIB。

很多考虑的地方 spring文档
xml
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>

案例

项目需要统计一下一个service类的核心方法执行时间

但是通常,一个核心方法内部有很多的子方法,如何在做代理的时候,将所所有方法进行代理

本次的实现,使用了AspectJ。

代码如下:

代理配置

package com.xxx.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * Created by xxxx on 17/3/22.
 */
@Aspect
@Component
public class CostTimeAspect {

    @Around("execution(* com.xxx.service.AService.*(..))")
    public Object printTimeMethod(ProceedingJoinPoint pjp) throws Throwable {

        long time = System.currentTimeMillis();
        Object obj = pjp.proceed();
        long cost = System.currentTimeMillis() - time;
        if (cost > 0) {
            System.out.println(pjp.getSignature().toShortString() + "costs mills:" + cost);
        }
        return obj;
    }

}

spring.xml

<aop:aspectj-autoproxy />

pom.xml

需要在 build 节点中增加,并且要适配指定的jdk版本

<plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.4</version>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>1.7.3</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>1.7.3</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <verbose>true</verbose>
                <showWeaveInfo>true</showWeaveInfo>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>

Java线程安全-《深入了解Java虚拟机》读书笔记

Java线程安全

《深入了解Java虚拟机》读书笔记

阿姆达尔定律(Amdahl law)

Amdahl加速定律定义了一个系统进行并行化改造时候可以提升的性能占比

公式大约如下:
$$
S’=\cfrac{1}{1-f+\cfrac{f}{S}}=\cfrac{1}{1-(1-\cfrac{1}{S})f}
$$

其中,

S’=是整体加速比

f=加速的部分占到整体系统的比重

S=加速了的部分的加速比重

举例:假设某个程序中,你优化了80%的代码,对这80%的代码你获得了加速比10,那么对整个程序而言,你的优化获得的加速比为:1/(1–0.8+0.8/10)=3.57,这远小于10。

S无限增大时候,S’逼近
$$
\cfrac{1}{1-f}
$$
也就是说,优化程序80%的代码,最大获得的加速比为5。

线程安全的定义

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的

——Brian Goetz《Java Concurrency In Practice》

Java中各种操作共享数据分类

不可变

Immutable,JDK1.5内存模型被修正以后的Java语言

Java语言中,如果共享数据是一个基本数据类型,只要在定义时使用final关键字修饰,可以保证是不可变的。

如果是一个对象,那就需要保证对象的行为不会对状态产生任何影响,比如java.lang.String中,substring()、replace()、concat()等方法,都不会影响它原来的值

保证对象行为不受影响的途径,最简单的就是把对象中带有状态的变量都声明为final,比如Integer类

Java API中,不可变的类型包括

  • String
  • 枚举类
  • java.lang.Number的部分子类
  • 不含AtomicInteger,AtomicLong,这两个类使用unsafe 的CAS操作 进行实现

绝对线程安全

完全满足Brian Goetz 提出的要求,调用者也不需要额外的同步措施,Java中基本没有类似的实现。

即使是在全部方法中使用了 synchronized 关键字修饰的 Vector类,在多线程的场景中调用方法,也还是会出现线程问题,虽然每个操作都是原子的,都会线程安全,但是多个原子操作的顺序却可能导致线程问题

相对线程安全

通常意义上的线程安全,保证一个对象单独的操作是线程安全的即可。Java中,大部分线程安全类都是属于这种类型,比如Vector、HashTable、Collections的synchronizedCollection()等

线程兼容

指对象本身不是线程安全的,需要调用者进行正确的同步手段来保证对象在并发环境中可以安全使用。与之前Vector和HashTable对应的ArrayList、HashMap等就是。

Java中绝大多数的API属于这类型

线程对立

指无论调用端是否采取了同步措施,都无法在多线程的环境中并发使用的代码。

Java中此类例子很少,常见的对立的例子如下:

  • Thread类的 suspend()方法以及resume()方法,如果有两个线程同时持有一个线程对象,一个尝试去中断线程,另一个尝试去恢复线程,如果并发进行的话,无论调用时候是否同步,目标线程都有死锁的风险。如果subpend()中断的线程就是即将要执行resume的那个线程,那就肯定要死锁了。由于以上原因,suspend()、resume()方法已经被JDK声明废弃了
  • System.sctIn()
  • System.setOut()
  • System.runFinalizersOnExit()

线程安全实现方式

互斥同步

悲观锁的思路体现、也称阻塞同步

常见的并发正确性方案,同步指的是多个线程并发访问共享数据的时候,保证共享数据在同一时刻只被一个或者一些(信号量时候)线程使用。

互斥是实现同步的一种手段,主要的实现方式如下,互斥是因,同步是果,互斥是方法、同步是目的

  • 临界区
  • 互斥量
  • 信号量

Java中,synchronize关键字是最基本的互斥同步手段。synchronized关键字经过编译以后,会在同步块的前后分别形成monitorenter和 monitorexit两个字节码指令。

字节码需要一个reference类型的参数来指明要锁定和解锁的对象

  • 如果java程序中的synchronized明确指定了对象参数,那就是这个对象的reference
  • 如果没有指定
  • synchronized修饰的是实例方法,取对应的对象实例
  • 修饰静态类方法,取Class对象来作为锁对象

monitorenter 进行锁对象计数器+1,monitorexit进行锁对象计数器-1。

synchronized同步块对于同一线程来说是可重入的,不会出现自己把自己锁死的问题。

阻塞或者唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态转化为和心态,因此状态转换会耗费很多的处理器时间。对于简单的同步块,比如getter方法,状态转换可能比用户代码执行的时间还要长。

synchronized在java中是一个重量级操作,非必要情况下不要使用。当然JVM也会做一些优化

使用ReentryLock实现锁

reentryLock与synchronized很相似,具备一样的线程重入特性,不过增加了一些高级功能

  • 等待可中断
  • 当等待锁的线程等待时间太长,可以中断等待,改为处理其他事情
  • 公平锁
  • 多个线程等待同一个锁时候,必须按照申请的时间顺序来依次获得锁,synchronized是非公平的
  • reentryLock可以通过构造参数选择是否公平
  • 锁绑定多个条件
  • 一个ReentryLock可以同时绑定多个Condition对象,synchronized中,wait\notify\notifyAll方法可以实现一个隐含的条件
  • newCondition可以实现多个条件
  • JDK1.6以上,ReentryLock与synchronized的性能完全持平,没有上述场景的前提下,建议使用原生的synchronized实现功能

非阻塞同步

随着硬件指令集的发展,基于冲突检测的乐观并发策略称为 非阻塞同步,区别于互斥同步,是一种先进行操作,发生冲突以后补偿的乐观锁思路。

这种策略需要操作和冲突检测这两个步骤具有原子性(当然不是使用synchronized。。。),常用指令有:

  • 测试并设置(Test-and-Set)
  • 获取并增加(Fetch-and-Increment)
  • 交换(Swap)
  • 比较并交换(Compare-and-Swap,CAS)
  • 加载连接、条件存储(Load Linked/Store Conditional,LL/SC)

前3条是老的指令,后2条是现代处理器新增加的

CAS

三个操作数,分别是内存位置V、旧的预期值A、新值B

CAS操作的时候,当且仅当V符合旧的预期值A时候,处理器会用新值B更新V值,否则不执行

JDK1.5以后,可以使用sun.misc.Unsafe类中的对应方法实行,并且限制了只有启动类加载器加载的class才能访问它,因此,如果不采用反射手段,只能通过其他Java的API来间接使用

AtomicLong等就是这样实现的

无同步方案

没有或者不涉及共享数据的前提下,可以采用这种方案。简单介绍2类

  • 可重入代码(Reentrant Code)所有的的可重入代码都是线程安全的。可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源,用到的状态量都是由参数中传入、不调用非可重入的方法等。
  • 一个简单的原则可以判断:如果一个方法,它的返回结果可以预测,只要输入了相同的数据,都能返回相同的结果,那就满足可重入的要求
  • 满足条件:

    (1)可以在执行的过程中可以被打断;

    (2)被打断之后,在该函数一次调用执行完之前,可以再次被调用(或进入,reentered)。

    (3)再次调用执行完之后,被打断的上次调用可以继续恢复执行,并正确执行。

  • 可以结合JVM来分析Java中的可重入代码,如果一个函数的所有访问变量,都是以值传递的方式传入局部变量表,那么这个方法就是可重入的。
  • 线程本地存储(Thread Local Storage):在Java中,就是ThreadLocal这个类。共享数g据的可见范围设置在同一个线程以内
  • ThreadLoca类的实现
  • 如果一个变量要被多线程访问,可以使用 volatile关键字声明
  • 经典Web交互模型中,“一个请求对应一个服务器线程”的处理模型

锁优化

自旋锁与自适应自旋

指的是让等待锁的线程通过空循环(自旋),而不进行让出CPU的操作,避免操作系统进行状态切换带来的时间损失。

  • 物理机器需要有一个以上的处理器,能让两个或以上的线程同时并行执行
  • 自旋等待的时间必须有一定的限度
  • -XX:+UseSpinning 开启自旋,1.6以后默认开启
  • -XX:PreBlockSpin 可以修改自旋次数
  • 1.6以后引入的自适应的自旋锁,通过程序运行和性能监控的不断完善,虚拟机对锁进行状况预测

锁消除

如果一段代码中,堆上所有的数据都不会逃逸出去从而被其他线程访问,那就可以认为数据是线程私有的,相当于栈上数据

锁粗化

有一系列的代码需要加锁,那么虚拟机会把加锁同步的范围扩展,粗化到整个操作序列的外部

轻量级锁

JDK1.6引入,在对象头中指定了一个标记位,加锁时候,通过CAS操作将线程栈与对象联系起来

  • 使用CAS操作替代互斥量操作,如果一个对象,只有一个线程要获取锁,那么使用这种情况
  • “对于绝大多数的锁,在整个同步周期内都是不存在竞争的”
  • 如果存在锁竞争,那么除了互斥量的开销以外,还额外发生了CAS操作

偏向锁

JDK1.6引入,同轻量级锁比较,偏向所就是直接将CAS操作也一并省略掉的技术

  • 一个线程在获取对象的锁同时,对象会在标记位中记录下这个线程的id
  • 后续的执行过程中,如果锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
  • -XX:+UseBiasedLocking JDK1.6默认开启
  • 有的时候禁止偏向锁可以提高性能

2016-58同城面试经历

58同城的办公地点坐落在酒仙桥的电子产业园,毗邻电影博物馆和798艺术中心。附近没有地铁站。
但是有公交车站以及班车。

58在产业园有3栋楼,还有一栋正在大兴土木。

还没有进入园区,就听到了58的slogan,一直在滚动播放。58的主楼是105号楼。一进入大厅,左边是前台,前台的装饰类似饭店的收银台,正面是供给访客的休息区,休息区左侧是由9块屏幕组成的LED阵列,上面显示58一天内的各种访问数据,每秒去刷新一次。休息区右侧则是58获取的所有奖杯。奖杯墙直通2楼,墙壁上是58创始至今的所有重大事件的timeline。

前台姑娘一看我填的表,惊喜的赞叹竟然是老乡

这次一共进行了两轮技术面试

第一轮面试

面试官主要是根据你项目经验以及实现中的技术进行提问

  1. 说说tomcat中classloader的加载过程
  2. 区别一下jetty和tomcat,两者有何不同
  3. tomcat调优是怎么做的
  4. redis的使用,何时做数据备份,何时部署集群
  5. Map接口有多少种实现,TreeMap内部的数据结构是怎样的,HashMap内部数据结构是怎样的
  6. 介绍一下ElasticSearch,和solr,lucene有何不同,适用何种场景
  7. 说说Java的内存结构
  8. 说说看过的技术书籍
  9. 说说位图法,目前的工作中,都适用于哪些场景

第二轮面试

这一轮也不是纯粹的技术面试了,面试官还会介绍一些其他信息

  1. 介绍一下职业规划
  2. 介绍一下之前公司的离职原因
  3. 之前面试了哪些公司,进入了哪些阶段
  4. 白纸写一个字符串包含的程序,其实是kmp算法

【转载】PhantomReference and Finalization

本文翻译至:PhantomReference and Finalization

软引用,弱引用以及虚引用

软引用(SoftReferences),比较典型的应用是在内存缓存的场景中。JVM会尽可能地将对象保留在内存中,当JVM内存不足的时候,才会从最早的references开始清除。根据javadoc中的描述,整个清除过程是没有保障的。

弱引用(WeakReferences)是我最经常使用的类型。典型的用途是在创建一些弱引用的监听器(Listener),或者是想收集某个对象的额外信息(使用WeakHashMap)的场景中。非常有助于降低类耦合度

其实笔者读到这里的时候,是产生了一些疑问的。为何使用weakHashMap可以降低类耦合度?设想一下使用了WeakHashMap的场景,weakhashmap可以优雅的解决内存释放的问题,但是如果没有WeakHashMap的话,那么实现就会复杂许多。可以在对象不在使用的时候,将它从Map中移除。这就需要容易管理者构造一个清理的函数给对象调用者使用,或者使用一个监听器模式。比如在编写一些使用者非常广泛的api类型的代码时候(比如jdk的api),添加这样的函数可能会使使用者的api变得非常复杂。

虚引用(Phantom references)则适用于在垃圾回收之前进行的预处理,比如需要释放一些资源的场景。遗憾的是,很多开发者会使用finalize()方法去执行这些操作,这并不是一个好的方式。finalize方法如果没有小心的使用在恰当的线程,恰当的时机,那么很可能会对应用造成可怕的性能影响,甚至会影响应用的数据完整性。

在虚引用的构造方法中,开发者需要显式的指定一个ReferenceQueue去将已经标记为“phantom reachable”的对象加入ReferenceQueue队列中。“phantom reachable”代表连虚引用本身都引用不到的对象。最令人迷惑的是即使Phantom references继续保持着私有对象的引用(区别于软引用以及弱引用),get方法也会返回一个null。这样一来,一旦进入这个状态的对象就无法再一次获得强引用。

开发者可以一次一次的对ReferenceQueue调用poll()方法,检测是否有新的PhantomReferences进入“phantom reachable”状态。正常的写法中,可以使类继承于java.lang.ref.PhantomReference,以保证引用的对象只垃圾回收一次,然后无法继续被获取。

PhantomReference 以及 finalization的细节

对PhantomReference 来说,最常见的误解会认为它是被设计用来“修复”finalizers 带来的对象逃逸问题。举个例子来说,我们常常会这么说:

虚引用可以避免finalize()带来的基础问题:finalize()方法可以通过创建一个新的强引用,使自身免于垃圾回收而进行“逃逸”。所以,重写了finalize()方法的对象,需要至少在两条分别的垃圾回收链中,才会被正确的回收。

然而,使用了虚引用,也有可能使对象出现逃逸,请看以下的代码

由此可见,表面上看,引用类型非常有可能是通过成员变量 Reference#referent 指向那些已经失去引用的对象。但是实际上,垃圾回收器对对特定的对象产生了一个意外。这一现象也直接对上文中的结论产生了冲突:

虚引用只用对象在实际的内存空间中被移除时候,才会执行enqueued操作。

到底哪种说法是正确的, javadoc是这样说明的:

Phantom references are most often used for scheduling pre-mortem cleanup

所以,如果虚引用并不是设计用来修正finalize逃逸的问题(这个问题非常严肃,曾经被Lazarus、Jesus 以及许多其他学者指出),那么虚引用究竟有什么作用?

finalize()方法实际是通过垃圾回收线程去执行的,即使在简单的单线程应用中,考虑到潜在因素,也可能出现并发问题(比如错误的将共享状态放入同步方法中等)。但是使用了虚引用的话,你可以制定执行出队操作的线程(在单线程程序中,指定的线程会周期性的做这个任务)

使用 WeakReference 的话,会如何?

弱引用看起来也会满足垃圾回收之前的内存清理场景。区别在于合适进行引用的入队操作。PhantomReference会在执行finalization之后入队,而WeakReference会在之前。对于finalize()方法中没有关键实现的对象来说,不受影响。

但是对于那些需要在finalize()方法中执行一些清理的对象,就会有些许不同

(PhantomReference’s get() 方法总是返回null)。开发者需要存储尽可能多的状态信息,去进行清理操作。举个例子,清理array中的对象,设置为null以后,开发者需要记录下来array中对象的下标,方便后续跟踪查看。对于这类型操作,可以将类继承于PhantomReference,然后创建这个类的实例。

下面更进一步的说说。

想象一个场景:一名开发者准备在某个对象中编写一段清除钩子的代码(通过 finalize()或者是通过[Weak|Phantom]Reference),当这个对象仅仅有属于线程栈空间的强引用(比如局部变量)的时候,开发者调用了一个方法,那么这时,可能发生这样的事情:

出于性能的考虑,JVM会检测是否这个对象有失去引用的可能。所以,在执行方法的过程中,finalization 可能被并发的执行。这样可能导致一些不可预料的结果(finalization 可能修改了一些类内部的状态,比如其他方法也会使用这些状态)。这种情况非常罕见,可以采取以下的方式修复:

这种情况仅仅适用于那些仅仅在线程栈中持有的对象:
– 对象重写了finalize()方法。
– 有一个[Weak|Soft|PhantomReference]引用指向这个对象,同时已经进入了ReferenceQueue,有另外一个线程进行dequeue的操作

总结一下,最安全的清理机制,是通过PhantomReference以及ReferenceQueue,在同一个线程下进行清理。如果是启用了另一个线程,那么就需要使用同步方法快

Java知识探究一:关于IO类库

经过组织考察,令我忽然发觉自己在最常用的Java中也有很多不明白的地方,实为平身一大憾事,今天特意抽时间将这些点滴记录下来,与大家一起分享

第一批想整理的知识点如下:

  1. Java的IO探究,IO的整个结构与发展,顺带附上公司某小工写的断点续传代码学习。
  2. Java的异常机制,关于编译时异常和运行时异常的探究。
  3. JavaCommon包的理解,尤其是collection包的一些小看法,其实容器嘛,什么样的Utils也逃不出一些基本的范畴,比如存、取、排序、安全性、校验等等等。

闲话不多说,先开始今天的主题,研究一下IO的整个结构

从体系结构上划分,IO系统总共分为两大模块, IO和NIO(非阻塞),IO诞生于JDK1.4之前,JDK1.4时,产生了NIO,并且借用NIO重构了部分IO的代码,比如FileInputStream中增加了对NIO进行支持的getChannel()方法,再比如Reader和FileReader基本用nio全部重写了。

一、Think in IO

IO从实现上,大致分为字节流和字符流两种:

  1. 字节流。对文件的读写操纵以字节为单位,说的直白一点,就是操作byte,byte数组。对应无符号整数的话,就是read方法的正常返回值范围在[0,255]之间,范围有限的返回值有很多优点,比较有代表性的一个就是可以流来做一个简单的zip实现,算法的话,采用huffman树。当然,一个一个字节操作的话,效率不高,利用Buffer则效率提高不少。但是字节流有个问题,那就是在操作文本文件的时候,对于编码会有很多多余的代码,例子如下
    FileInputStream is = new FileInputStream("F:\\books\\base\\vim常用指令.txt");
            byte[] buff = new byte[BUFFER_SIZE];
            int readSize = 0;
            while ((readSize = is.read(buff)) != -1)
            {
                System.out.println(readSize);
                if(readSize<1024){
                    byte[] tmp = new byte[readSize];
                    System.arraycopy(buff, 0, tmp, 0, readSize);
                    System.out.print(new String(tmp, "GBK"));
                }else{
                    System.out.print(new String(buff, "GBK"));
                }
            }
    
  2. 字符流。以字符作为单元进行操作,Reader内部实现其实就是以char或者char数组作为缓存容器的。操作文本文件时候方便许多。编码采用系统默认的编码格式。找了好久才找到代码的说+_+,代码隐藏的很深,从Reader找到ImputStreamReader,再到StreamDecoder再到nio包中的Charset,最终是优先获取系统中的环境变量,System.getProperties()也可以获取,windows7中文版的话,获取到的是“ file.encoding=GB18030”
    /**
         * Returns the default charset of this Java virtual machine.
         *
         * <p> The default charset is determined during virtual-machine startup and
         * typically depends upon the locale and charset of the underlying
         * operating system.
         *
         * @return  A charset object for the default charset
         *
         * @since 1.5
         */
        public static Charset defaultCharset() {
            if (defaultCharset == null) {
            synchronized (Charset.class) {
            java.security.PrivilegedAction pa =
                new GetPropertyAction("file.encoding");
            String csn = (String)AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
                    else 
                defaultCharset = forName("UTF-8");
                }
        }
        return defaultCharset;
        }
    

下面详细叙述一下字节流

一、InputStream 和 OutputStream 是两个 abstact 类,对于字节为导向的 stream 都扩展这两个鸡肋(基类 ^_^ ) ;

040832408755959

  1. FileInputStream,打开本地文件的流,常用,有3个构造方法
    public FileInputStream(File file)
    public FileInputStream(String name)
    public FileInputStream(FileDescriptor fdObj) 值得强调,这个构造是不能直接用的,FileDescriptor 相当于打开文件的句柄,可以用一个文件流创建另一个,这样创建的流相当于是一个。一个流关闭的话, 另一个也不能读取。
  2. PipedInputStream,必须与PipedOutputStream一起使用,必须是两个或者多个线程中使用,类似生产者消费者模型, PipedOutputStream将数据写到共享的buffer数组中,通知PipedInputStream读取。有两点注意事项:a) 使用PipedInputStream的read方法时候要注意,如果缓冲区没有数据的话,会阻塞当前线程,在主线程中运行的话,会卡住不动。b)PipedOutputStream所在的线程如果停止,那么PipedOutputStream所使用的资源也会回收,会造成pipe 的“broken”,PipedInputStream的read方法也会报错。“A pipe is said to be broken if a thread that was providing data bytes to the connected piped output stream is no longer alive. ”
  3. FilterInputStream,本身是不能被实例化的,是BufferedInputStream等的父类,其实不创建这个类也可以实现它的子类,这个类内部的方法几乎全部都是复用父类的方法。其实它存在的意义更多是代表一个抽象,意思是在InputStream的基础之上对返回数据进行了重新包装或者处理,处理原因可能各不相同,于是又了各不相同的子类。
  4. LineNumberInputStream,这个类是字节流和字符流转换中的失败产物,已经确定为被废弃,废弃的理由是在字节流中强制的判断读取换行,不考虑编码方面的问题。先不管功能能不能实现,首先从抽象层次上面就有欠缺。挪到字符流里面就皆大欢喜。对应的有LineNumberReader这个类可以使用。具体参见LineNumberReader详解。
  5. DataInputStream,直接读取目标文件的byte,拼接或转化byte为其他基本类型,比如下面方法
    public final int readInt() throws IOException {
            int ch1 = in.read();
            int ch2 = in.read();
            int ch3 = in.read();
            int ch4 = in.read();
            if ((ch1 | ch2 | ch3 | ch4) < 0)
                throw new EOFException();
            return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
        }
    

    对于基本类型可以这样转化,但是对于float和double,各自用了Float类和Double类中的native方法进行转化,想来与操作系统底层有关系。

    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
        }
    

    唯一实现的比较复杂的是readUTF方法,需要读取全部数据,必须是符合格式的,需要用DataOutputStream的writeUTF进行对应的写。DataInputStream在实际运用中,还是应该与DataOutputStream一起使用,不然的话,意义不是十分大。

  6. BufferedInputStream,初始化一个8192大小的缓存,提高效率用,调用API上面没有任何不同,只是减少了直接读取系统数据的次数。内部持有一个普通的inputStream,只有缓冲区空了以后,才真正调用inputStream的read去写满缓冲区,所以直接用BufferedInputStream的read方法可以提高效率。
    有点意思的是这个类里面用了一个AtomicReferenceFieldUpdater对象来进行对volatile类型缓冲byte数组的更新和替换,这个类的compareAndSet方法带有原子性质的比较和更新。

    /**
         * Atomic updater to provide compareAndSet for buf. This is
         * necessary because closes can be asynchronous. We use nullness
         * of buf[] as primary indicator that this stream is closed. (The
         * "in" field is also nulled out on close.)
         */
        private static final 
            AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = 
            AtomicReferenceFieldUpdater.newUpdater
            (BufferedInputStream.class,  byte[].class, "buf");// 创建原子更新器
    ...
    /**
         * Fills the buffer with more data, taking into account
         * shuffling and other tricks for dealing with marks.
         * Assumes that it is being called by a synchronized method.
         * This method also assumes that all data has already been read in,
         * hence pos > count.
         */
        private void fill() throws IOException {
            byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;        /* no mark: throw away the buffer */
        else if (pos >= buffer.length)    /* no room left in buffer */
            if (markpos > 0) {    /* can throw away early part of the buffer */
            int sz = pos - markpos;
            System.arraycopy(buffer, markpos, buffer, 0, sz);
            pos = sz;
            markpos = 0;
            } else if (buffer.length >= marklimit) {
            markpos = -1;    /* buffer got too big, invalidate mark */
            pos = 0;    /* drop buffer contents */
            } else {        /* grow buffer */
            int nsz = pos * 2;
            if (nsz > marklimit)
                nsz = marklimit;
            byte nbuf[] = new byte[nsz];
            System.arraycopy(buffer, 0, nbuf, 0, pos);
                    if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {//进行更新比较, 如果buf对象和buffer相同, 那么进行更新,不同的话,不更新
                        // Can't replace buf if there was an async close.
                        // Note: This would need to be changed if fill()
                        // is ever made accessible to multiple threads.
                        // But for now, the only way CAS can fail is via close.
                        // assert buf == null;
                        throw new IOException("Stream closed");
                    }
                    buffer = nbuf;
            }
            count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
            if (n > 0)
                count = n + pos;
        }
    
  7. PushBackInputStream,特点是unread()方法,作用是在读取流的过程中自行添加入字节或者字节数组,进行重新读取,小说中随机插入的广告url倒是可以用这个实现,冷不丁的在读取过程中插入一个urlbyte数组,倒也方便。
  8. ByteArrayInputStream,特点是内存操作,读取的数据全部都在缓存数组中,构造方法如下
    public ByteArrayInputStream(byte buf[])
    public ByteArrayInputStream(byte buf[], int offset, int length)
    
  9. StringBufferInputStream,这个类已经被废弃,原因是错误的对字节流进行向字符流的转化,忽略了编码问题。值得一提的是, 这个类里基本所有部分方法都是线程安全的。swing的某个类中还引用了这个方法。
  10. ObjectInputStream,这个类可以说的比较多
    1. 实现了两个接口,ObjectInut:定义了可以read到的类型,ObjectStreamConstants:定义了读取文件类型的常量,使用readObject时候,区分读取到的对象是什么类型,从序列化的对象进行读取时候,需要通过标志位来判断读取到的是什么对象,这个常量里面定义了这些值, 都是short的。
    2. 拥有一个内部类BlockDataInputStream,这个类的作用是读取基本类型数据时候进行缓存,以提高效率,但是也产生了问题,http://www.tuicool.com/articles/v6RNNr 反序列化和序列化一定注意,建议使用read(byte[],start,end) 替代简单的read(byte[]),使用后者的话, 可能出现读取乱码,内容错误等问题,尤其是音视频, 可能出现杂音,因为ObjectInputStream是根据单个字节来判断数据类型的,所以一定要准确。
  1. OutputStream, 基本每个InputStream都有一个对应的OutputStream,来实现对应的功能,基本全都是抽象方法。
    1. FileOutputStream,FileDescriptor相当于句柄, 既然是句柄, 就会有多个流可能使用之, 所以FileDescriptor有incrementAndGetUseCount方法, 用来线程安全的进行引用计数器+1的操作。另外值得注意的是,FileOutputStream还有追加写入的构造方法
      public FileOutputStream(File file, boolean append)
              throws FileNotFoundException
          {
              String name = (file != null ? file.getPath() : null);
          SecurityManager security = System.getSecurityManager();
          if (security != null) {
              security.checkWrite(name);
          }
              if (name == null) {
                  throw new NullPointerException();
              }
          fd = new FileDescriptor();
              fd.incrementAndGetUseCount();
              this.append = append;
          if (append) {
              openAppend(name);
          } else {
              open(name);
          }
          }
      
    2. PipedOutputStream,需要与InputStream进行配合使用,不在赘述

【转】Myeclipse 查看Jar文件里面源码中文乱码 问题解决

转自

http://blog.csdn.net/skipper_java/article/details/6325221

问题描述:原项目的公共函数都打包成jar 导致中文注释乱码

解决办法:用EditPlus打开代码查看 发现所用字符集为UTF-8;到myeclipse中设置编码字符集,具体步骤如下:

1,Windows–Preferences–General–Content Types–将Java Class File和Text的Default encoding 改为UTF-8—UPDATA

2,Windows–Preferences–General–Workspace—将Text file encoding改为UTF-8

3,右键点击项目–Preferences–Resource–将Text file encoding改为UTF-8

【迁移】Think In Java 多态学习

    1. 动态绑定与前期绑定
    2. Static对象与Final对象不能覆盖,private 对象(方法的话,自动属于final)也不能进行覆盖。
    3. 复杂对象调用构造器要遵照下面的顺序
      1. 调用基类的构造器,这个过程会递归的调用下去。
      2. 按照声明顺序调用成员的初始化方法。
      3. 调用导出类构造器主体。
    4. 协变返回类型,导出类中被覆盖的方法可以返回基类方法的返回值类型的某种导出类型。
    5. 设计考虑时候优先考虑组合,而非继承
    6. 状态模式,动态的改变类型

//: polymorphism/Transmogrify.java
// Dynamically changing the behavior of an object
// via composition (the &quot;State&quot; design pattern).
import static net.mindview.util.Print.*;</p>

<p>class Actor {
  public void act() {}
}</p>

<p>class HappyActor extends Actor {
  public void act() { print(&quot;HappyActor&quot;); }
}</p>

<p>class SadActor extends Actor {
  public void act() { print(&quot;SadActor&quot;); }
}</p>

<p>class Stage {
  private Actor actor = new HappyActor();
  public void change() { actor = new SadActor(); }
  public void performPlay() { actor.act(); }
}</p>

<p>public class Transmogrify {
  public static void main(String[] args) {
    Stage stage = new Stage();
    stage.performPlay();
    stage.change();// 状态的改变
    stage.performPlay();
  }
} /* Output:
HappyActor
SadActor
*/

    1. 如果必须要执行清理,清理的顺序需要和声明顺序是相反的, 这样的话。
    2. 多态中潜藏的问题

//: polymorphism/PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect.
import static net.mindview.util.Print.*;</p>

<p>class Glyph {
  void draw() { print(&quot;Glyph.draw()&quot;); }
  Glyph() {
    print(&quot;Glyph() before draw()&quot;);
    draw();// 执行这个方法时候,调用的其实是被覆盖以后的方法
    print(&quot;Glyph() after draw()&quot;);
  }
}</p>

<p>class RoundGlyph extends Glyph {
  private int radius = 1;//执行这一步以前, 会先执行父类的构造器, 然后父类的构造器又会直接调用覆盖以后的方法
  RoundGlyph(int r) {
    radius = r;
    print(&quot;RoundGlyph.RoundGlyph(), radius = &quot; + radius);
  }
  void draw() {
    print(&quot;RoundGlyph.draw(), radius = &quot; + radius);
  }
}</p>

<p>public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*/

Kettle 环境变量设置

  1. Java环境变量,可以调用所有的java中System.properties中的相关变量。
  2. 手动定义环境变量,可以通过“Set Variables”步骤进行设置,或者通过编辑作业属性中的Paramaters页签进行设置。
  3. 内部环境变量,Kettle自身的
    1. Internal.Kettle.Build.Date:2007/05/22 18:01:39 创建时间
    2. Internal.Kettle.Build.Version:2045
    3. Internal.Kettle.Version:2.5.0
    4. Internal.Transformation.Filename.Directory:D:\Kettle\samples 当前转换运行目录
    5. Internal.Transformation.Filename.Name:Denormaliser – 2 series of key-value pairs.ktr 当前运行转换文件名称
    6. Internal.Transformation.Name:Denormaliser – 2 series of key-value pairs sample 当前转换名称
    7. Internal.Transformation.Repository.Directory:/  资料库路径
    8. Internal.Job.Filename.Directory:/home/matt/jobs 作业目录
    9. Internal.Job.Filename.Name:Nested jobs.kjb 作业文件名
    10. Internal.Job.Name:Nested job test case 作业名
    11. Internal.Job.Repository.Directory:/ 作业资料库名
    12. Internal.Slave.Transformation.Number:0..<cluster size-1> (0,1,2,3 or 4) 集群模式下有,转化号码
    13. Internal.Cluster.Size:<cluster size> (5) 集训数量