本篇是記錄自己在學習中遇到一些用法(比較泛,只要是我認爲比較奇怪,驚豔的,有價值的都可以)。還有一些我經常會用到的和一些有趣的封裝,先記錄吧,可能後面多了會很亂,到時再說。
字符串
null字符串
這是一個很有意思的現象,我到現在才知道,原來會出現
String name = null;
System.out.println(name+"");
System.out.println(""+null); //"null"
System.out.println(String.valueOf(name)); //"null"
"null"這樣的字符串。無論是""+null還是String.valueOf(name),Object name=null都會形成“null”字符串,而""+null的寫法比較簡潔,不會太長,所以一般我喜歡用前面一種(個人喜好)。
這裏說這個現象的目的是:在判斷字符串的時候,不要只考慮兩種情況,還有第三種:
- 字符串爲null
- 字符串爲空字符串
- 字符串爲"null"(null字符串)
String name = String.valueOf(...從request中獲得請求參數)+""
if(name!=null && !"".equals(name) && "null".equals(name)){
...
}
生成Json數組的兩種方案
生成效果
{
"reportCard":
[
{
"subject":"Math",
"Score":60,
"time":"2018-08-15 08:56:42",
"teacherComment":"no"
}
....
]
}
有兩種方法,
第一種,通過Map和List生成
import.alibaba.fastjson.JSON
Map<String, Object> reportCard= new HashMap<>();
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> elem = new HashMap<>();
elem.put("subject", "Math");
elem.put("score", 60);
elem.put("time", "Math");
elem.put("teacherComment", "no");
list.add(elem);
reportCard.put("reportCard", list);
System.out.println(JSON.toJSONString(reportCard));
另外一種,通過JSONObject和JSONArray和上面一樣,JSONObject相當於map,JSONArray相當於List
加密解密/編碼解碼
32位的md5加密,全部小寫(大寫)
這個加密代碼我是從網上查的,版本感覺都差不多,不過還有另外一種比較簡潔的版本,下面會有說到。
/**
*簽名32位md5加密,全小寫
*
*/
public static String getSign(String account, String pwd, String timestamp)
throws NoSuchAlgorithmException{
String sign = account+pwd+timestamp;
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(sign.getBytes("UTF-8"));
byte b[] = md5.digest();
int i;
StringBuffer sf = new StringBuffer("");
for(int offset=0; offset<b.length; offset++){
i = b[offset];
if(i<0){
i=i+256;
}
if(i<16){
sf.append("0");
}
sf.append(Integer.toHexString(i));
return sf.toString().toLowerCase();
}
}
使用給定的密鑰進行MD5加密,32位,大寫,字符串用GBK編碼。
/**
* 用給定的密鑰對字符串進行32位的md5加密,全大寫,字符集採用GBK
* @param str 待加密字符串
* @param key 密鑰
* @return
*/
public static String getSign(String str, String key){
StringBuffer sf = new StringBuffer("");
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(str.getBytes("GBK"));
byte temp[] = md5.digest(key.getBytes("UTF-8"));
for(int i=0; i<temp.length; i++){
sf.append(Integer.toHexString((temp[i] & 0x000000FF) |
0xFFFFFF00).substring(6));
}
return sf.toString().toUpperCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sf.toString().toUpperCase();
}
文件轉base64字符串
隨機數
生成n位隨機數
這是別人寫的標題是鏈接,簡單說一下原理,一開始現將,[0,1)*9+1=[1,10),然後根據需要將[1,10)*10^(n-1)。
/**
* 生成n位隨機數
* @param n 隨機數的長度
* @return
*/
public static long getNDigitRanNum(int n){
if(n < 1){
throw new IllegalArgumentException("隨機數位數必須大於0");
}
return (long)(Math.random()*9*Math.pow(10,n-1)) + (long)Math.pow(10,n-1);
}
自定義範圍的隨機數
/**
* 生成指定上下界的隨機數
* [0, maxBound)%range=[0, range)+minBound=[minBound, minBound+range)
* @param minBound 下界
* @param maxBound 上界
* @return
*/
public static int getRandomNumInRange(int minBound, int maxBound){
Random random = new Random();
return random.nextInt(maxBound)%(maxBound-minBound+1)+minBound;
}
隨機事件(指定事件發生概率)
大致的需求是:比如說,有三種情況,第一種情況的出現概率爲2/5;第二種情況的出現概率爲2/5;最後一種出現的概率爲1/5。實現的原理比較簡單,複用上面的方法,然後對隨機的區域進行分割,判定最後隨機數落在哪個範圍內,返回所需對應的字符串,具體看代碼註釋,代碼有點亂。之所以寫這個主要是有次寫測試的時候,請求接口會返回三種狀態,所以寫了一個,感覺比較有趣就記下來了。。
/**
* events:key(事件):value(概率),用TreeMap保持加入的順序
* 0.4,0.4,0.2
* 400,800,1000
* @param events 隨機事件
* @return
*/
public static String probabilityEvent(TreeMap<String, Double> events){
double p_total = 1.0;
int random = getRandomNumInRange(0, 1000); //取出隨機範圍爲0~999的一個隨機數
int index = 0;
String result = "";
for(Map.Entry<String, Double> event : events.entrySet()){
p_total = p_total - event.getValue(); //用於檢查事件總概率小於1
if("".equals(event.getKey().trim()) || event.getValue()<=0.0 || event.getValue()>=1.0){
throw new IllegalArgumentException("事件不得爲空,事件概率不能小於0大於1");
}
int range = (int)(event.getValue()*1000); //計算出該概率在1000中所佔的範圍
index = index + range;
//用""來控制不重複獲取事件
if("".equals(result) && random<index){
result = event.getKey();
}
}
if(p_total<0.0){
throw new IllegalArgumentException("事件概率相加必須小於1");
}
return result;
}
文件操作
ZIP壓縮
下面這個是我根據網上代碼來更改的,可以按照目錄結構進行壓縮,跳過空文件和隱藏文件,若已經有壓縮文件存在,則直接返回。後面貼上具體效果。
如果按照正常的方法壓縮在多線程下會出現各種問題:
問題描述1:在壓縮文件的時候,一開始會創建一個zip壓縮包。在壓縮的過程這個壓縮包不斷被寫入,字節數在增加,這時候另外一個線程來壓縮文件,發現已經有這個壓縮文件,刪除。那之前的那個線程壓縮的都白費了,已經被刪了。
問題描述2:同上,只不過這次發現已經有這個壓縮文件,直接返回,省的再壓一次.。所以,同理,這次你會拿到一個無法訪問的破損壓縮文件。
比較兩個問題描述,我覺得直接返回更好一點。
主要解決思路:
- 加鎖,我想到兩種方式,一種是synchronized或手動創建鎖進行同步,另外一種是使用FileChannel中的加鎖方法(我這裏用了後者)
- 通過字節數是否有更新來判定文件是否被使用(網上看到的,有點囉嗦)
/**
* 對路徑下的目錄結構進行zip壓縮(不壓縮隱藏文件和空文件,其他格式可以通過
* 後綴名自定義),適用於多線程下
* @param srcDir 將壓縮文件路徑(文件夾),D:\\新建文件夾
* @param zipPath 輸出zip壓縮包路徑(不帶壓縮名),D:\\新建文件夾
* @param zipName 壓縮文件名 如,6666
* @return true-壓縮成功
*/
public static boolean filesToZip(String srcDir, String zipPath, String zipName){
ZipOutputStream zos = null;
FileChannel fileChannel = null;
FileLock fileLock = null;
try {
File zipFile = new File(zipPath + "/" + zipName + ".zip");
//若已經有這個壓縮包,直接返回
//必須放在這裏,因爲打開輸出流,會自動創建一個zip文件
if(zipFile.exists()){
return true;
}
FileOutputStream fos = new FileOutputStream(zipFile);
//-------------------嘗試獲取鎖------------------
fileChannel = fos.getChannel();
int trytimes = 0;
while (trytimes<10) {
try {
fileLock = fileChannel.tryLock();
break;
} catch (Exception e) {
trytimes++;
Thread.sleep(1000);
}
}
//----------------------------------------------
zos = new ZipOutputStream(fos);
File sourceFile = new File(srcDir);
compress(sourceFile, zos, "");
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if(fileLock != null){
fileLock.release();
}
if (zos != null) {
zos.close();
}
if(fileChannel != null){
fileChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Set<String> fileType = new HashSet<String>();
private static String fileTypeStr = "zip"; //文件類型(後綴名逗號分開)
static{
String[] fileTypeArr = fileTypeStr.split(",");
for(int i=0; i<fileTypeArr.length; i++){
if(!StringUtils.isEmpty(fileTypeArr[i])){
fileType.add(fileTypeArr[i]);
}
}
}
public static void compress(File sourceFile, ZipOutputStream zos, String name) throws Exception{
byte[] buf = new byte[1024*6];
if(sourceFile.isFile() && !sourceFile.isHidden()){
//對文件類型進行控制
String suffix = sourceFile.getName().substring(sourceFile.getName().lastIndexOf(".") + 1);
if(fileType.contains(suffix.toLowerCase())){
return;
}
if(name.indexOf("/") == 0){ //去掉前面多餘的/
name = name.substring("/".length(), name.length());
}
zos.putNextEntry(new ZipEntry(name));
FileInputStream fis = new FileInputStream(sourceFile);
BufferedInputStream bis = new BufferedInputStream(fis, 1024*6);
int len;
while((len = bis.read(buf, 0, 1024*6)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
bis.close();
fis.close();
}else{
if(!sourceFile.isHidden()){
File[] listFiles = sourceFile.listFiles();
if(listFiles !=null && listFiles.length != 0){
for(int i=0 ;i<listFiles.length; i++){
compress(listFiles[i], zos, name + "/" + listFiles[i].getName());
}
}
}
}
}
壓縮前
壓縮後
ZIP解壓縮
/**
* 解壓ZIP文件,並輸出到指定目錄中
* @param zipFilePath zip文件的路徑 D:\新建文件夾\6666.zip
* @param unzipFilePath 輸出的目錄路徑 D:\test
* @return
*/
public static boolean unZipFile(String zipFilePath, String unzipFilePath){
if(!unzipFilePath.endsWith(File.separator)){
unzipFilePath += File.separator;
}
try {
ZipFile zipFile = new ZipFile(zipFilePath); //拿到zip文件對象
ZipEntry zipEntry = null; //壓縮包內的文件對象
String entryName = null; //壓縮包內的文件名
Enumeration<? extends ZipEntry> enumeration = zipFile.entries(); //獲得壓縮包內所有對象
int readBytes = 0;
byte[] buf = new byte[1024*6];
while(enumeration.hasMoreElements()){
zipEntry = enumeration.nextElement();
entryName = zipEntry.getName();
if(zipEntry.isDirectory()){ //是否爲文件夾
//mkdirs可建立多級文件夾/tmp/a/b/c/可以生成四個文件夾,而mkdir會因爲沒有找到返回false
new File(unzipFilePath + entryName).mkdirs();
continue;
}
//創建所在目錄下的多級目錄結構,打開輸出流,生成文件
File file = new File(unzipFilePath + entryName);
file.getParentFile().mkdirs();
OutputStream os = new FileOutputStream(file);
InputStream is = zipFile.getInputStream(zipEntry);
while((readBytes=is.read(buf)) != -1){
os.write(buf, 0, readBytes);
}
os.close();
is.close();
}
zipFile.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
配置文件讀取
這裏配置文件指的是properties文件,一般這類文件放在resources目錄下
public class ServerProperties{
private final Logger log = Logger.getLogger(ServerProperties.class);
private static Properties prop = null;
static{
prop = new Properties();
String filePath = ToolUtil.getClassPath(ServerProperties.class, "server.properties"); //自定義獲取配置文件路徑
InputStream is = null;
try{
is = new BufferedInputStream(new FlieInputStream(filePath));
prop.load(new InputStreamReader(is, "utf-8"));
}catch(Exception e){
log.error(e.getMessage, e);
}
}
public static String getValue(){
String result = "";
if(prop != null){
result = (String)prop.get(key);
}
return result;
}
}
自定義獲取項目配置文件路徑
private static String configPath[] = {"/config/"};
private static String defaultConfigPath = "/src/main/resources/";
/**
* 獲取配置文件路徑
* @param clazz 配置類
* @param fileName 需要讀取的文件名
* @return
*/
public static String getClassPath(Class<?> clazz, String fileName){
String userDir = System.getProperty("user.dir"); //項目路徑
String temp_path = null;
File file = null;
for(int i=0; i<configPath.length; i++){
temp_path = userDir + configPath[i] + fileName;
file = new File(temp_path);
if(file.exists()){
return temp_path;
}
}
//在配置類所在的目錄下
//若是""則可以獲得file:開頭的絕對路徑
//若是"/"則可以獲得target(IDEA),bin(eclipse)目錄路徑 --- classPath根目錄
URL url = clazz.getResource("");
file = new File(url.getPath() + fileName);
if(file.exists()){
return url.getPath() + fileName
}
return userDir + defaultConfigPath + fileName;
}
Redis
基礎配置
根據redis實戰來構建的環境,以後會根據需要更改。
首先要有一個redis的properties配置文件和spring-bean的配置文件。
redis.config.properties,放在resource下
###
dc_deal.ServerIp=127.0.0.1
dc_deal.ServerPort=6379
dc_deal.masterName=mymaster
dc_deal.maxTotal=3000
dc_deal.maxIdle=100
dc_deal.maxWaitMillis=3000
dc_deal.testOnBorrow=true
bean-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:property-placeholder location="redis.config.properties"/>
<context:component-scan base-package="com.xck"/>
<bean id="redisDeal" class="com.xck.adapter.impl.RedisDcAdapter" init-method="initPool">
<property name="redisServerIp" value="${dc_deal.ServerIp}"/>
<property name="redisServerPort" value="${dc_deal.ServerPort}"/>
<property name="masterName" value="${dc_deal.masterName}"/>
<property name="maxTotal" value="${dc_deal.maxTotal}"/>
<property name="maxIdle" value="${dc_deal.maxIdle}"/>
<property name="maxWaitMillis" value="${dc_deal.maxWaitMillis}"/>
<property name="testOnBorrow" value="${dc_deal.testOnBorrow}"/>
</bean>
</beans>
其次得有一個和spring-bean中對應的類,reids適配器RedisDcAdapter
package com.xck.adapter.impl;
import org.apache.log4j.Logger;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* redis適配器,用來初始化redis和獲取redis連接池的工具
*/
public class RedisDcAdapter {
protected Logger log = Logger.getLogger(RedisDcAdapter.class);
private JedisPool pool = null;
private String redisServerIp;
private int redisServerPort;
private String masterName;
private int maxTotal;
private int maxIdle;
private int maxWaitMillis;
private boolean testOnBorrow ;
public synchronized void initPool(){
jedisSinglePool();
}
public synchronized void jedisSinglePool(){
pool = getSinglePool();
log.info("initPool [Single] a pool hashCode:" + pool.hashCode() +
", redisServerIp: " + redisServerIp + ":" + redisServerPort);
}
public synchronized JedisPool getSinglePool(){
if(pool == null){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(maxIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setTestOnBorrow(testOnBorrow);
pool = new JedisPool(config, redisServerIp, redisServerPort, maxWaitMillis);
}
return pool;
}
public Jedis getJedis(){
return getSinglePool().getResource();
}
public void returnJedis(Jedis jedis){
if(jedis != null){
jedis.close();
}
}
//一定要加set方法,因爲spring配置文件使用的是property
public void setRedisServerIp(String redisServerIp) {
this.redisServerIp = redisServerIp;
}
public void setRedisServerPort(int redisServerPort) {
this.redisServerPort = redisServerPort;
}
public void setMasterName(String masterName) {
this.masterName = masterName;
}
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public void setMaxWaitMillis(int maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}
public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
}
隨後,是真正和reids交互的類,這裏使用抽象類,因爲如果有多個不同節點的處理端,這裏可以直接繼承。
公共類CommonRAO,主要定義了基本操作,使用抽象方法是爲了根據需要獲取不同的適配器。
package com.xck.rao;
import com.xck.adapter.impl.RedisDcAdapter;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
/**
* 通過redis適配器獲取reids的連接,和redis進行交互
*/
public abstract class CommonRAO {
protected Logger log = Logger.getLogger(CommonRAO.class);
protected abstract RedisDcAdapter getDcAdapter();
/**
* 字符串操作
* key-value
* @param key
* @param value
*/
public void setString(String key, String value){
Jedis jedis = null;
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
jedis.set(key, value);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("setString too long time keys:" + key + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
}
public String getString(String key){
Jedis jedis = null;
String value = "";
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
value = jedis.get(key);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("getString too long time keys:" + key + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
return value;
}
/**
* set集合(無序,不重複)元素添加(sadd),設置有效期(expire)
* key-(member)
* @param key
* @param seconds 有效期,s
* @param members
* @return
*/
public long addSet(String key, int seconds, String... members){
Jedis jedis = null;
long value = -1;
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
value = jedis.sadd(key, members);
jedis.expire(key, seconds);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("addSet too long time keys:" + key + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
return value;
}
/**
* 有序集合(無重複),元素添加
* key-(member, score)
* @param key
* @param score
* @param member
* @return
*/
public long addSortedSet(String key, double score, String member){
Jedis jedis = null;
long value = -1;
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
value = jedis.zadd(key, score, member);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("addSortedSet too long time keys:" + key + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
return value;
}
/**
* 單個刪除
* @param key
* @return
*/
public long del(String key){
Jedis jedis = null;
long value = -1;
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
value = jedis.del(key);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("del too long time keys:" + key + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
return value;
}
/**
* 批量刪除
* @param keys
* @return
*/
public long dels(String... keys){
Jedis jedis = null;
long value = -1;
try {
jedis = getDcAdapter().getJedis();
long start = System.currentTimeMillis();
value = jedis.del(keys);
long end = System.currentTimeMillis();
if(end - start>2000){
log.warn("dels too long time keys:" + keys + "; " + (end-start)+"ms");
}
} finally {
getDcAdapter().returnJedis(jedis);
}
return value;
}
}
具體實現類DealRAO
package com.xck.rao;
import com.xck.adapter.impl.RedisDcAdapter;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
public class DealRAO extends CommonRAO{
protected Logger log = Logger.getLogger(DealRAO.class);
@Resource(name="redisDeal")
protected RedisDcAdapter redisDcAdapter;
@Override
protected RedisDcAdapter getDcAdapter() {
return redisDcAdapter;
}
}
要使用的時候,直接用@Autowired自動注入即可。最後附上pom.xml配置
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>