Java版的Redis
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>(); else list = 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>(); else list = 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>(); else set = 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>(); else set = 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); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(bytes); return result; } @Override public 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 relationship redis.SAdd("users", "A", "B", "C"); // User A follows B, C redis.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 tweet int 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实现它们。