Java編程記錄 --- 工具箱(填充中...)

本篇是記錄自己在學習中遇到一些用法(比較泛,只要是我認爲比較奇怪,驚豔的,有價值的都可以)。還有一些我經常會用到的和一些有趣的封裝,先記錄吧,可能後面多了會很亂,到時再說。

字符串

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的寫法比較簡潔,不會太長,所以一般我喜歡用前面一種(個人喜好)。

這裏說這個現象的目的是:在判斷字符串的時候,不要只考慮兩種情況,還有第三種:

  1. 字符串爲null
  2. 字符串爲空字符串
  3. 字符串爲"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:同上,只不過這次發現已經有這個壓縮文件,直接返回,省的再壓一次.。所以,同理,這次你會拿到一個無法訪問的破損壓縮文件。

比較兩個問題描述,我覺得直接返回更好一點。

主要解決思路:

  1. 加鎖,我想到兩種方式,一種是synchronized或手動創建鎖進行同步,另外一種是使用FileChannel中的加鎖方法(我這裏用了後者)
  2. 通過字節數是否有更新來判定文件是否被使用(網上看到的,有點囉嗦)
    /**
     * 對路徑下的目錄結構進行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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章