工作记录

一、避免两个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.rarjava文件,或者直接将文件导入本地service工程:

4、配置web.xml,添加一个Filter<filter-class>对应前面中导入的CASFilter.java文件


再给此filter配置参数:


参数说明:

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);



第一个版本 SessionMap使用 HashMap

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();
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值