Redis是一個基於Key-value結構的Nosql資料庫,它支援各種常見的資料結構以及非常方便的操作,與其說它是一個資料庫,不如說它是一個儲存各種資料結構的伺服器。今天閑來沒事,用Java集合類實現了Redis的一些準系統,算是溫習下Java了。
1.Redis入門
Redis的Key索引值為字串,但是Value值支援許多種類型,如String字串,List鏈表,Set無序集合,SortedSet有序集合,甚至是Hash表。
各種資料結構通過不同的存取方法來區分。如Set/Get直接將值存為String,LPush/LPop/LRange將值存到一個鏈表中,SAdd/ZAdd則區分了無序和有序集合。
下面我們來看下在Java中使用基本的集合類如何?這些簡單而方便的操作。
2.Java版的Redis
代碼的組織圖如:
package com.cdai.studio.redis;import java.util.HashSet;import java.util.LinkedList;import java.util.TreeSet;@SuppressWarnings("unchecked")public class RedisDB {private Persistence persistence = new Persistence();private Serializer serializer = new Serializer();private static final Object[] NULL = new Object[0];// =================================================//String value// =================================================public void Set(String key, Object value) {persistence.put(key, serializer.marshal(value));}public Object Get(String key) {return serializer.unmarshal(persistence.get(key));}public Object[] MGet(String... keys) {Object[] values = new Object[keys.length];for (int i = 0; i < keys.length; i++)values[i] = Get(keys[i]);return values;}public int Incr(String key) {Object value = Get(key);Integer valueRef = (value == null) ? 1 : (Integer) value;Set(key, valueRef + 1);return valueRef;}// =================================================//List value// =================================================public void LPush(String key, Object... values) {Object list = persistence.get(key);if (list == null)list = new LinkedList<Object>();elselist = serializer.unmarshal(list);LinkedList<Object> listRef = (LinkedList<Object>) list;for (Object value : values)listRef.addFirst(value);persistence.put(key, serializer.marshal(list));}public void RPush(String key, Object... values) {Object list = persistence.get(key);if (list == null)list = new LinkedList<Object>();elselist = serializer.unmarshal(list);LinkedList<Object> listRef = (LinkedList<Object>) list;for (Object value : values)listRef.addLast(value);persistence.put(key, serializer.marshal(list));}public Object[] LRange(String key, int start, int end) {Object list = persistence.get(key);if (list == null)return NULL;LinkedList<Object> listRef = (LinkedList<Object>) serializer.unmarshal(list);if (end > listRef.size())end = listRef.size();return listRef.subList(start, end).toArray();}// =================================================//Unsorted Set value// =================================================public void SAdd(String key, Object... values) {Object set = persistence.get(key);if (set == null)set = new HashSet<Object>();elseset = serializer.unmarshal(set);HashSet<Object> setRef = (HashSet<Object>) set;for (Object value : values)setRef.add(value);persistence.put(key, serializer.marshal(set));}public Object[] SMembers(String key) {Object set = persistence.get(key);if (set == null)return NULL;set = serializer.unmarshal(set);return ((HashSet<Object>) set).toArray();}public Object[] SInter(String key1, String key2) {Object set1 = persistence.get(key1);Object set2 = persistence.get(key2);if (set1 == null || set2 == null)return NULL;HashSet<Object> set1Ref = (HashSet<Object>) serializer.unmarshal(set1);HashSet<Object> set2Ref = (HashSet<Object>) serializer.unmarshal(set2);set1Ref.retainAll(set2Ref);return set1Ref.toArray();}public Object[] SDiff(String key1, String key2) {Object set1 = persistence.get(key1);Object set2 = persistence.get(key2);if (set1 == null || set2 == null)return NULL;HashSet<Object> set1Ref = (HashSet<Object>) serializer.unmarshal(set1);HashSet<Object> set2Ref = (HashSet<Object>) serializer.unmarshal(set2);set1Ref.removeAll(set2Ref);return set1Ref.toArray();}// =================================================//Sorted Set value// =================================================public void ZAdd(String key, Object... values) {Object set = persistence.get(key);if (set == null)set = new TreeSet<Object>();elseset = serializer.unmarshal(set);TreeSet<Object> setRef = (TreeSet<Object>) set;for (Object value : values)setRef.add(value);persistence.put(key, serializer.marshal(set));}public Object[] SRange(String key, Object from) {Object set = persistence.get(key);if (set == null)return NULL;set = serializer.unmarshal(set);return ((TreeSet<Object>) set).tailSet(from).toArray();}}
package com.cdai.studio.redis;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Arrays;class Serializer {Object marshal(Object object) {if (object == null)return null;return new BytesWrapper((Serializable) object);}Object unmarshal(Object object) {if (object == null)return null;return ((BytesWrapper) object).readObject();}}class BytesWrapper {private byte[] bytes;<T extends Serializable> BytesWrapper(T object) {writeBytes(object);}<T extends Serializable> void writeBytes(T object) {try {ByteArrayOutputStream buffer = new ByteArrayOutputStream();ObjectOutputStream output = new ObjectOutputStream(buffer);output.writeObject(object);output.flush();bytes = buffer.toByteArray();output.close();}catch (IOException e) {e.printStackTrace();throw new IllegalStateException(e);}}Object readObject() {try {ObjectInputStream input = new ObjectInputStream(new ByteArrayInputStream(bytes));Object object = input.readObject();input.close();return object;}catch (Exception e) {e.printStackTrace();throw new IllegalStateException(e);}}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + Arrays.hashCode(bytes);return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;BytesWrapper other = (BytesWrapper) obj;if (!Arrays.equals(bytes, other.bytes))return false;return true;}}
package com.cdai.studio.redis;import java.util.HashMap;class Persistence {private HashMap<String, Object> storage =new HashMap<String, Object>();void put(String key, Object value) {storage.put(key, value);}Object get(String key) {return storage.get(key);}}
3.簡單的用戶端
package com.cdai.studio.redis;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.List;public class RedisServer {private RedisDB redis;public RedisServer(RedisDB redis) {this.redis = redis;}@SuppressWarnings("unchecked")public void start() {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(1234);while (true) {Socket socket = serverSocket.accept();ObjectInputStream input = new ObjectInputStream(socket.getInputStream());List<Object> request = (List<Object>) input.readObject();Object response = null;if ("Set".equals(request.get(0))) {redis.Set((String) request.get(1), request.get(2));}else if ("Get".equals(request.get(0))) {response = redis.Get((String) request.get(1));}ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());output.writeObject(response);input.close();output.close();socket.close();}} catch (Exception e) {e.printStackTrace();}finally {if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {}}}}}
package com.cdai.studio.redis;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.net.Socket;import java.util.Arrays;import java.util.List;public class RedisClient {public <T extends Serializable> void Set(String key, Object value) {sendRequest(Arrays.asList("Set", key, value));}public Object Get(String key) {return sendRequest(Arrays.<Object>asList("Get", key));}private Object sendRequest(List<Object> payload) {Socket socket = null;try {socket = new Socket("localhost", 1234);ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());output.writeObject(payload);output.flush();ObjectInputStream input = new ObjectInputStream(socket.getInputStream());Object response = input.readObject();output.close();input.close();return response;} catch (Exception e) {e.printStackTrace();} finally {if (socket != null) {try {socket.close();} catch (Exception e) {}}}return null;}}
4.實現簡單的Twitter
package com.cdai.studio.redis;import java.util.Arrays;public class RedisTest {public static void main(String[] args) {RedisDB redis = new RedisDB();// 1.Create user follow relationshipredis.SAdd("users", "A", "B", "C");// User A follows B, Credis.SAdd("users:A:following", "B", "C");redis.SAdd("users:B:followers", "A");redis.SAdd("users:C:followers", "A");// User C follows B redis.SAdd("users:C:following", "B");redis.SAdd("users:B:followers", "C");// 2.1 B send tweetint tid = redis.Incr("tweets:next_id");redis.Set("tweets:" + tid, "B publish hello");redis.LPush("global:timeline", tid);redis.LPush("users:B:timeline", tid);for (Object follower : redis.SMembers("users:B:followers"))redis.LPush("users:" + follower + ":timeline", tid);// 2.2 C send tweet tid = redis.Incr("tweets:next_id");redis.Set("tweets:" + tid, "C publish world");redis.LPush("global:timeline", tid);redis.LPush("users:C:timeline", tid);for (Object follower : redis.SMembers("users:C:followers"))redis.LPush("users:" + follower + ":timeline", tid);Object[] tids = redis.LRange("global:timeline", 0, 9);String[] tweetids = new String[tids.length];for (int i = 0; i < tids.length; i++)tweetids[i] = "tweets:" + tids[i];System.out.println(Arrays.toString(redis.MGet(tweetids)));}}
5.需要注意的問題
byte數組的equals和hashcode預設實現比較對象地址的,要藉助於Arrays的equals和hashcode方法。
String字串序列化和還原序列化時要注意編碼格式的問題,編碼解碼時應該使用相同的編碼。
HashSet上的操作,removeAll補集,retainAll交集,addAll並集。
6.更加強大的Redis
Redis自己實現了各種資料結構,可以非常方便地增刪改查,並且效率很高。這裡我們只是用Java來簡單的學習了下Redis準系統,其實Redis還支援很多其他的進階功能,如訊息訂閱、資料到期設定、事務、資料持久化。想要進一步學習的話可以試著用Java實現它們。