一、避免两个double类型数值直接相加
工作中遇到一个问题java中double+double 经常得出一些意想不到的的结果:
package test;
public class Test {
public static void main(String[] args) {
double a = 1.1;
double b = 2.45;
double c = a+b;
System.out.println(c);
}
}
结果为:
3.5500000000000003
查阅了一些信息发现原因其实很简单:
我们回忆一下1.1在计算机中是如何以二进制保存的,我们知道数字在计算机中是以二进制保存,比如IEEE754标准
double是属于长实数的类型,那么带有小数的十进制数是怎么转换成二进制数呢?我们回忆一下:
比如0.8125转换为二进制
小数乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列。
0.8125x2=1.625 取整1,小数部分是0.625
0.625x2=1.25 取整1,小数部分是0.25
0.25x2=0.5 取整0,小数部分是0.5
0.5x2=1.0 取整1,小数部分是0,结束
按照这种方法我们会发现0.1转换为二进制是一个无限循环
我们来看一下0.1的二进制代码
public class Test {
public static void main(String[] args) {
double a = 0.1;
long l = Double.doubleToLongBits(a);
System.out.println(Long.toBinaryString(l));
}
}
结果为按照IEEE754的标准显示如下:
11111110111001100110011001100110011001100110011001100110011010
加上一位隐藏的符号位0在最开始
刚好是64位
因为double的长度是64位的 所以后面的地位精度就丢失了
我们再来看看0.25.用上面计算二进制的方法可以得出一个确切的数
package test;
public class Test {
public static void main(String[] args) {
double a = 0.25;
long l = Double.doubleToLongBits(a);
System.out.println(Long.toBinaryString(l));
}
}
结果如下:
11111111010000000000000000000000000000000000000000000000000000
我们发现没有精度丢失。
所以我们发现了问题所在。有些小数并不能用IEEE754完整的表示,所以会照成上述的问题。
那么联系到实际中,在商业的计算中 一般不能用double+double
需要用bigdecimal类来解决
BigDecimal volumn = new BigDecimal("1.1");
volumn = volumn.add(new BigDecimal("2.45"));
得到结果:3.55
二、iText生成pdf byte流
项目需要在socket之间传递一个pdf文件
决定使用iText进行开发
iText写一个pdf一般分以下几步:
// 1.创建 Document 对象
Document _document = new Document();
// 2.创建书写器
PdfWriter _pdfWriter = PdfWriter.getInstance(_document, OutputStream);
// 3.打开文档
_document.open();
// 4.向文档中添加内容
_document.add(new Paragraph("Hi"));
// 5.关闭文档
_document.close()
看到网上的例子都是直接生成一个pdf到硬盘中:
try {
PdfWriter.getInstance(doc, new FileOutputStream("c:/hello.pdf"));
doc.open();
doc.add(new Paragraph("HelloWorld"));
doc.close();
} catch (DocumentException e) {
e.printStackTrace();
}
项目只需要把生成的pdf对象保存在内存流中然后直接socket发送给服务器
其实在第二部输出流输出到内存中就可以了,
PdfWriter _pdfWriter = PdfWriter.getInstance(_document, OutputStream);
第二个参数是一个输出流,用ByteArrayOutputStream就可以做到我们想要的
try {
ByteArrayOutputStream ba = new ByteArrayOutputStream();
PdfWriter.getInstance(doc, ba);
doc.open();
doc.add(new Paragraph("HelloWorld"));
doc.close();
//得到byte数组
Byte[] = ba.toByteArray();
} catch (DocumentException e) {
e.printStackTrace();
}
三、iText生成pdf byte流
单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。CAS(Central Authentication Service)是一款不错的针对 Web 应用的单点登录框架,本文介绍了 CAS 的原理、协议、在 Tomcat 中的配置和使用,对于采用 CAS 实现轻量级单点登录解决方案的入门读者具有一定指导作用。
参考链接:
http://www.ibm.com/developerworks/cn/opensource/os-cn-cas/
http://www.blogjava.net/security/archive/2006/10/02/sso_in_action.html
http://www.coin163.com/java/cas/cas.html
读以下内容前 请先大概浏览一下参考链接的内容
简单实例,利用CAS搭建简单SSO系统:
1.部署SSO认证SERVER端,认证所有的请求,如果用户没有登录,会重定向到一个登录界面。
下载 cas-server-4.0.0.rar (可以在本人资源中找到) ,解压拿到cas-server-uber-webapp-4.0.0.war部署在服务器tomcat或其他server容器上
2.下载CAS开源包,clientFilter.rar(可以在本人资源中找到)
3.建立一个CASClient工程,导入clientFilter.rar中java文件,或者直接将文件导入本地service工程:
4、配置web.xml,添加一个Filter,<filter-class>对应前面中导入的CASFilter.java文件
参数说明:
edu.yale.its.tp.cas.client.filter.loginUrl:CAS server认证端的 登陆地址
edu.yale.its.tp.cas.client.filter.validateUrl:CAS server认证端 ticket验证地址
edu.yale.its.tp.cas.client.filter.serverName:自己部署的应用的service地址
5.创建一个servlet验证一下sso效果
6.三种方式可以获得sso登录后的用户名
1.访问本地service
2.由于用户未登陆,跳转cas server验证
2.输入用户名密码登陆
2.重定向到用户访问的service,并且3种方式都成功打印出了用户信息
四、用户超时机制
一个session管理机制
1.用户登录时,系统随机生成一个字符串作为sessionId 用户相关信息生成User对象. 插入到全局对象SessionMap<String,User>中。User对象中记录用户的最后活动时间
String sessionId = ""+UUID.randomUUID()+new Random().nextLong()+cal.getTimeInMillis();
Long nowTime = cal.getTimeInMillis();
Long expiredTime = nowTime + Constants.Timeout_hour*60*60*1000;
u.setActiveTime(nowTime);
u.setExpiredTime(expiredTime);
this.sessionMap.put(sessionId, u);
2.添加Servlet Filter 截获任何操作。请求中需要带上sessionID.从SessionMap<String,User>中根据sessionId取出User对象。从User对象中取出用户最后活动时间。
如果 当前时间 > 系统设定超时时间+用户最后活动时间
则用户超时。
如果用户没有操作,就把当前时间作为参数更新用户的最后活动时间
Long currentTime=cal.getTimeInMillis();
User u = this.sessionMap.get(sessionId);
Long expireTime = u.getExpiredTime();
if(currentTime>=expireTime){
this.sessionMap.remove(sessionId);
}else{
u.setActiveTime(currentTime);
u.setExpiredTime(currentTime + Constants.Timeout_hour*60*60*1000);
}
3.添加一个Thread执行定时任务,从SessionMap中清除超时的Entry
Runnable runnable = new Runnable() {
public void run() {
Long now = cal.getTimeInMillis();
Iterator<Entry<String, User>> iter = sessionMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,User> entry = (Map.Entry<String,User>)iter.next();
String key = (String)entry.getKey();
long expiredTime = entry.getValue().getExpiredTime();
if (expiredTime <= now) {
sessionMap.remove(key);
}
}
};
}
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(runnable, 24, 1, TimeUnit.HOURS);
HashMap为不安全对象
第二个版本改成线程安全的ConcurrentHashMap
ConcurrentHashMap虽然是线程安全的类。但不能保证完整的一个业务操作的线程安全
如果 一个用户还差1s过期。
线程1:该用户登录系统 filter判断未过期。执行到此 系统调度开始执行线程2
线程2:1s过后 定时清除器执行任务判断此用户过期。从SessionMap中清除此用户相关信息。执行到此 系统调度开始执行线程1
线程1:用当前时间 更新用户时间的时候发现 该用户对于User对象不存在 被线程2清除。出错
所以需要在最外层的方法上是实现同步。
由于定时清除器是令起一个线程执行任务。所以使用一个ReentrantLock对象作为锁
最终完整代码如下:
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import bean.User;
import constant.Constants;
public class SessionManager {
private static SessionManager instance = new SessionManager();
private Map<String,User> sessionMap = new HashMap<String,User>();
private Calendar cal = Calendar.getInstance();
private ReentrantLock lock = new ReentrantLock();
private static Logger log = Logger.getLogger("SessionManager");
public static SessionManager getInstance(){
return instance;
}
private SessionManager(){
Runnable runnable = new Runnable() {
public void run() {
try {
if(lock.tryLock(2, TimeUnit.SECONDS)){
try{
Long now = cal.getTimeInMillis();
log.info("execute the cleaning expired sessionId action in "+ cal.getTime());
Iterator<Entry<String, User>> iter = sessionMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,User> entry = (Map.Entry<String,User>)iter.next();
String key = (String)entry.getKey();
long expiredTime = entry.getValue().getExpiredTime();
if (expiredTime <= now) {
sessionMap.remove(key);
}
}
}finally{
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(runnable, 24, 1, TimeUnit.HOURS);
}
public String createNewSessionId(User u){
lock.lock();
try{
String sessionId = ""+UUID.randomUUID()+new Random().nextLong()+cal.getTimeInMillis();
Long nowTime = cal.getTimeInMillis();
Long expiredTime = nowTime + Constants.Timeout_hour*60*60*1000;
u.setActiveTime(nowTime);
u.setExpiredTime(expiredTime);
this.sessionMap.put(sessionId, u);
return sessionId;
}finally{
lock.unlock();
}
}
public User getUser(String sessionId){
lock.lock();
try{
Long currentTime=cal.getTimeInMillis();
User u = this.sessionMap.get(sessionId);
if(u != null){
Long expireTime = u.getExpiredTime();
if(currentTime>=expireTime){
this.sessionMap.remove(sessionId);
return null;
}else{
u.setActiveTime(currentTime);
u.setExpiredTime(currentTime + Constants.Timeout_hour*60*60*1000);
return u;
}
}else{
return null;
}
}finally{
lock.unlock();
}
}
public void removeSession(String sessionId){
lock.lock();
try{
sessionMap.remove(sessionId);
}finally{
lock.unlock();
}
}
}