用radix tree简单实现java.util.Map的功能
2018-01-28 21:09
811 查看
代码地址:https://github.com/kaikai-sk/MyRadixTree
RadixTreeNode.java
package com.sk.radixtree; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.TreeSet; import javax.xml.soap.Node; public class RadixTreeNode <V extends Serializable> implements Iterable<RadixTreeNode<V>>, Comparable<RadixTreeNode<V>> { //在节点中的前缀 private String prefix; //存储在节点中的value private V value; //该节点是否存储一个值。 这个值主要由RadixTreeVisitor用来判断是否应该访问这个节点。 private boolean hasValue; //这个节点的孩子节点。请注意,因为我们在这里使用了{@link TreeSet},所以RadixTree的遍历将按字典顺序排列。 private Collection<RadixTreeNode<V>> children; /** * 打印node节点的信息 */ public void printInfo() { if(children!=null) { System.out.println("[prefix="+prefix +",value="+value+ ",hasValue="+hasValue+ ",numOfChildren"+children.size()+"]"); } else { System.out.println("[prefix="+prefix +",value="+value+ ",hasValue="+hasValue+ "]"); } } /** * 根据给出的prefix构造节点 * @param prefix */ public RadixTreeNode(String prefix) { this.prefix=prefix; this.hasValue=false; } /** * Constructs a node from the given prefix and value. * * @param prefix the prefix * @param value the value */ public RadixTreeNode(String prefix,V value) { this.prefix=prefix; this.value=value; this.hasValue=true; } /** * Gets the value attached to this node. * * @return the value, or <code>null</code> if an internal node */ public V getValue() { return value; } /** * Sets the value attached to this node. * * @param value the value, or <code>null</code> if an internal node */ public void setValue(V value) { this.value=value; } /** * Gets the prefix associated with this node. * * @return the prefix */ String getPrefix() { return prefix; } /** * Sets the prefix associated with this node. * * @param prefix the prefix */ void setPrefix(String prefix) { this.prefix=prefix; } /** * Gets the children of this node. * * @return the list of children */ Collection<RadixTreeNode<V>> getChildren() { //延迟创建children以节约内存 if(children==null) { children=new TreeSet<RadixTreeNode<V>>(); } return children; } /** * Whether or not this node has a value attached to it. * * @return whether or not this node has a value */ boolean hasValue() { return hasValue; } /** * Sets whether or not this node has a value attached to it. * * @param hasValue <code>true</code> if this node will have a value, * <code>false</code> otherwise. If <code>false</code>, * {@link #getValue()} will return <code>null</code> * after this call. */ void setHasValue(boolean hasValue) { this.hasValue=hasValue; if(!hasValue) { //this.hasValue=(Boolean) null; } } @Override public int compareTo(RadixTreeNode<V> node) { return prefix.toString().compareTo(node.getPrefix().toString()); } @Override public Iterator<RadixTreeNode<V>> iterator() { if(children==null) { return new Iterator<RadixTreeNode<V>>() { @Override public boolean hasNext() { return false; } @Override public RadixTreeNode<V> next() { return null; } }; } return children.iterator(); } }
RadixTreeUtil.java
package com.sk.radixtree; import java.io.Serializable; /** * Radix tree utility functions * @author root * */ public class RadixTreeUtil { private RadixTreeUtil() { } static <V extends Serializable> void dumpTree(RadixTreeNode<V> node,String outputPrefix) { if(node.hasValue()) { System.out.format("%s{%s : %s}%n", outputPrefix,node.getPrefix(),node.getValue()); } else { System.out.format("%s{%s}%n", outputPrefix,node.getPrefix(),node.getValue()); } for (RadixTreeNode<V> child:node) { dumpTree(child, outputPrefix+"\t"); } } /** * Prints a radix tree to <code>System.out</code>. * * @param tree the tree */ public static <V extends Serializable> void dumpTree(RadixTree <V> tree) { dumpTree(tree.root,""); } /** * Finds the length of the largest prefix for two character sequences. * * @param a character sequence * @param b character sequence * * @return the length of largest prefix of <code>a</code> and <code>b</code> * * @throws IllegalArgumentException if either <code>a</code> or <code>b</code> * is <code>null</code> */ public static int largestPrefixLength(CharSequence a,CharSequence b) { int length=0; for(int i=0;i<Math.min(a.length(), b.length());i++) { if(a.charAt(i)!=b.charAt(i)) { break; } length++; } return length; } }
RadixTreeVisitor.java
package com.sk.radixtree; public interface RadixTreeVisitor<V,R> { /** * Visits a node in a radix tree. * * @param key the key of the node being visited * @param value the value of the node being visited */ public abstract void visit(String key,V value); /** * An overall result from the traversal of the radix tree. * * @return the result */ public abstract R getResult(); }
RadixTree.java
package com.sk.radixtree; import java.util.List; import java.io.Serializable; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import com.sk.radixtree.*; /** * A radix tree. Radix trees are String -> Object mappings which allow quick * lookups on the strings. Radix trees also make it easy to grab the objects * with a common prefix. * * @see <a href="http://en.wikipedia.org/wiki/Radix_tree">Wikipedia</a> * * @param <V> the type of values stored in the tree */ public class RadixTree <V extends Serializable> implements Map<String, V>,Serializable { public static final String KEY_CANNOT_BE_NULL="key cannot be null"; public static final String KEYS_MUST_BE_STRING_INSTANCES="keys must be String instaces"; //radix tree 的根节点 RadixTreeNode<V> root; /** * Gets a list of values whose associated keys have the given prefix. * * @param prefix the prefix to look for * * @return the list of values * * @throws NullPointerException if prefix is <code>null</code> */ public List<V> getValuesWithPrefix(String prefix) { RadixTreeVisitor<V,List<V>> visitor=new RadixTreeVisitor<V, List<V>>() { List<V> result=new ArrayList<V>(); @Override public void visit(String key, V value) { result.add(value); } @Override public List<V> getResult() { return result; } }; visit(visitor,prefix); return visitor.getResult(); } /** * Gets a list of keys with the given prefix. * * @param prefix the prefix to look for * * @return the list of prefixes * * @throws NullPointerException if prefix is <code>null</code> */ public List<String> getKeysWithPrefix(String prefix) { RadixTreeVisitor<V, List<String>> visitor=new RadixTreeVisitor<V, List<String>>() { List<String> result=new ArrayList<String>(); @Override public void visit(String key, V value) { result.add(key); } @Override public List<String> getResult() { return result; } }; visit(visitor, prefix); return visitor.getResult(); } /** * Gets a list of entries whose associated keys have the given prefix. * * @param prefix the prefix to look for * * @return the list of values * * @throws NullPointerException if prefix is <code>null</code> */ public List<Map.Entry<String,V>> getEntriesWithPrefix(String prefix) { RadixTreeVisitor<V, List<Map.Entry<String,V>>> visitor=new RadixTreeVisitor<V, List<Map.Entry<String,V>>>() { List<Map.Entry<String,V>> result=new ArrayList<Map.Entry<String,V>>(); @Override public void visit(String key, V value) { result.add(new AbstractMap.SimpleEntry(key,value)); } @Override public List<java.util.Map.Entry<String, V>> getResult() { return result; } }; visit(visitor, prefix); return visitor.getResult(); } /** * Visits the given node of this tree with the given prefix and visitor. Also, * recursively visits the left/right subtrees of this node. * * @param node the node * @param prefix the prefix * @param visitor the visitor */ public void visit(RadixTreeNode<V> node,String prefixAllowed,String prefix,RadixTreeVisitor<V, ?>visitor) { if(node.hasValue() && prefix.startsWith(prefixAllowed)) { visitor.visit(prefix, node.getValue()); } for(RadixTreeNode<V> child:node) { final int prefixLength=prefix.length(); final String newPrefix=prefix+child.getPrefix(); /** * 还需要继续查找的条件 */ if(prefixAllowed.length()<=prefixLength || newPrefix.length()<=prefixLength || newPrefix.charAt(prefixLength) == prefixAllowed.charAt(prefixLength)) { visit(child, prefixAllowed, newPrefix, visitor); } } } /** * Traverses this radix tree using the given visitor. Only values with * the given prefix will be visited. Note that the tree will be traversed * in lexicographical order. * * @param visitor the visitor * @param prefix the prefix used to restrict visitation */ public void visit(RadixTreeVisitor<V, ?> visitor,String prefix) { visit(root,prefix,"",visitor); } /** * Traverses this radix tree using the given visitor. Note that the tree * will be traversed in lexicographical order. * * @param visitor the visitor */ public void visit(RadixTreeVisitor<V, ?> visitor ) { visit(root,"","",visitor); } /** * 缺省的构造函数 */ public RadixTree() { this.root=new RadixTreeNode<V>(""); } @Override public int size() { RadixTreeVisitor<V, Integer> visitor=new RadixTreeVisitor<V, Integer>() { int count=0; @Override public void visit(String key, V value) { count++; } @Override public Integer getResult() { return count; } }; visit(visitor); return visitor.getResult() ; } @Override public boolean isEmpty() { return root.getChildren().isEmpty(); } @Override public boolean containsKey(final Object keyToCheck) { if (keyToCheck==null) { throw new NullPointerException(KEY_CANNOT_BE_NULL); } if(!(keyToCheck instanceof String)) { throw new ClassCastException(KEYS_MUST_BE_STRING_INSTANCES); } RadixTreeVisitor<V, Boolean> visitor=new RadixTreeVisitor<V, Boolean>() { boolean found =false; @Override public void visit(String key, V value) { if(key.equals(keyToCheck)) { found=true; } } @Override public Boolean getResult() { return found; } }; visit(visitor, (String)keyToCheck); return visitor.getResult(); } @Override public boolean containsValue(final Object val) { RadixTreeVisitor<V, Boolean> visitor=new RadixTreeVisitor<V, Boolean>() { boolean found=false; @Override public void visit(String key, V value) { if(val==value || (value!=null) && value.equals(val)) { found=true; } } @Override public Boolean getResult() { return found; } }; visit(visitor); return visitor.getResult(); } @Override public V get(final Object keyToCheck) { if(keyToCheck==null) { throw new NullPointerException(KEY_CANNOT_BE_NULL); } if(!(keyToCheck instanceof String)) { throw new ClassCastException(KEYS_MUST_BE_STRING_INSTANCES); } RadixTreeVisitor<V, V> visitor=new RadixTreeVisitor<V, V>() { V result=null; @Override public void visit(String key, V value) { if(key.equals(keyToCheck)) { result=value; } } @Override public V getResult() { return result; } }; visit(visitor, (String)keyToCheck); return visitor.getResult(); } @Override public V put(String key, V value) { if(key==null) { throw new NullPointerException(KEY_CANNOT_BE_NULL); } return put(key, value,root); } /** * Remove the value with the given key from the subtree rooted at the * given node. * * @param key the key * @param node the node to start searching from * * @return the old value associated with the given key, or <code>null</code> * if there was no mapping for <code>key</code> */ private V put(String key, V value, RadixTreeNode<V> node) { V ret =null; final int largestPrefixLength=RadixTreeUtil.largestPrefixLength(key, node.getPrefix()); //key就是node的情况 if(largestPrefixLength==node.getPrefix().length() && largestPrefixLength==key.length()) { ret=node.getValue(); node.setValue(value); node.setHasValue(true); } //key所在节点在node的子节点中 else if(largestPrefixLength==0 || (largestPrefixLength<key.length()) && largestPrefixLength>=node.getPrefix().length() ) { // Key is bigger than the prefix located at this node, so we need to see if // there's a child that can possibly share a prefix, and if not, we just add // a new node to this node final String leftoverKey=key.substring(largestPrefixLength); boolean found=false; for(RadixTreeNode<V> child:node) { if(child.getPrefix().charAt(0)==leftoverKey.charAt(0)) { found=true; ret=put(leftoverKey, value, child); break; } } if(!found) { // No child exists with any prefix of the given key, so add a new one RadixTreeNode<V> n=new RadixTreeNode<V>(leftoverKey,value); node.getChildren().add(n); } } // Key and node.getPrefix() share a prefix, so split node else if(largestPrefixLength<node.getPrefix().length()) { final String leftoverPrefix=node.getPrefix().substring(largestPrefixLength); node.printInfo(); //创建一个新的节点 final RadixTreeNode<V> n=new RadixTreeNode<V>(leftoverPrefix,node.getValue()); n.setHasValue(node.hasValue()); n.getChildren().addAll(node.getChildren()); n.printInfo(); //原来的节点升级了(喜当爹) node.setPrefix(node.getPrefix().substring(0,largestPrefixLength)); node.getChildren().clear(); node.getChildren().add(n); node.printInfo(); // The largest prefix is equal to the key, so set this node's value if(largestPrefixLength==key.length()) { ret=node.getValue(); node.setValue(value); node.setHasValue(true); } //// There's a leftover suffix on the key, so add another child else { final String leftoverKey=key.substring(largestPrefixLength); final RadixTreeNode<V> n1=new RadixTreeNode<V>(leftoverKey,value); node.getChildren().add(n1); node.printInfo(); node.setHasValue(false); } } else { // node.getPrefix() is a prefix of key, so add as child final String leftoverKey=key.substring(largestPrefixLength); RadixTreeNode<V> n=new RadixTreeNode<V>(leftoverKey,value); node.getChildren().add(n); } return ret; } @Override public V remove(Object key) { if(key==null) { throw new NullPointerException(KEY_CANNOT_BE_NULL); } if(!(key instanceof String)) { throw new ClassCastException(KEYS_MUST_BE_STRING_INSTANCES); } final String sKey=(String)key; if(sKey.equals("")) { final V value=root.getValue(); root.setHasValue(false); return value; } return remove(sKey,root); } /** * Remove the value with the given key from the subtree rooted at the * given node. * * @param key the key * @param node the node to start searching from * * @return the value associated with the given key, or <code>null</code> * if there was no mapping for <code>key</code> */ private V remove(String key,RadixTreeNode<V> node) { V ret =null; final Iterator<RadixTreeNode<V>> iterator=node.getChildren().iterator(); while(iterator.hasNext()) { final RadixTreeNode<V> child=iterator.next(); final int largestPrefixLen=RadixTreeUtil.largestPrefixLength(key, child.getPrefix()); // Found our match, remove the value from this node if(largestPrefixLen==key.length() && largestPrefixLen==child.getPrefix().length()) { if(child.getChildren().isEmpty()) { ret=child.getValue(); iterator.remove(); break; } else if(child.hasValue()) { //internal node ret=child.getValue(); child.setHasValue(false); if(child.getChildren().size()==1) { // The subchild's prefix can be reused, with a little modification final RadixTreeNode<V> subChild=child.getChildren().iterator().next(); final String newPrefix=child.getPrefix()+subChild.getPrefix(); // Merge child node with its single child child.setValue(subChild.getValue()); child.setHasValue(subChild.hasValue()); child.setPrefix(newPrefix); child.getChildren().clear(); } break; } } else if(largestPrefixLen>0 && largestPrefixLen<key.length()) { // Continue down subtree of child final String leftoverKey=key.substring(largestPrefixLen); ret=remove(leftoverKey, child); break; } } return ret; } @Override public void putAll(Map<? extends String, ? extends V> map) { for(Map.Entry<? extends String, ? extends V> entry:map.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public void clear() { root.getChildren().clear(); } @Override public Set<String> keySet() { // TODO documentation Of Map.keySet() specifies that this is a view of // the keys, and modifications to this collection should be // reflected in the parent structure // RadixTreeVisitor<V, Set<String>> visitor=new RadixTreeVisitor<V, Set<String>>() { Set<String> result=new TreeSet<String>(); @Override public void visit(String key, V value) { result.add(key); } @Override public Set<String> getResult() { // TODO Auto-generated method stub return result; } }; visit(visitor); return visitor.getResult(); } @Override public Collection<V> values() { // TODO documentation Of Map.values() specifies that this is a view of // the values, and modifications to this collection should be // reflected in the parent structure // RadixTreeVisitor<V, Collection<V>> visitor=new RadixTreeVisitor<V, Collection<V>>() { Collection<V> values=new ArrayList<V>(); @Override public void visit(String key, V value) { values.add(value); } @Override public Collection<V> getResult() { return values; } }; visit(visitor); return visitor.getResult(); } @Override public Set<java.util.Map.Entry<String, V>> entrySet() { // TODO documentation Of Map.entrySet() specifies that this is a view of // the entries, and modifications to this collection should be // reflected in the parent structure // RadixTreeVisitor<V, Set<Map.Entry<String, V>>> visitor=new RadixTreeVisitor<V, Set<Map.Entry<String, V>>>() { Set<Map.Entry<String, V>> result = new HashSet<Map.Entry<String, V>>(); @Override public void visit(String key, V value) { result.add(new AbstractMap.SimpleEntry(key,value)); } @Override public Set<java.util.Map.Entry<String, V>> getResult() { return result; } }; visit(visitor); return visitor.getResult(); } }
RadixTreeUtilTest.java
package com.sk.radixtree; import static org.junit.Assert.*; import org.junit.Test; public class RadixTreeUtilTest { @Test public void testDumpTreeRadixTreeNodeOfVString() { } @Test public void testDumpTreeRadixTreeOfV() { } @Test public void testLargestPrefixLength() { assertEquals(5, RadixTreeUtil.largestPrefixLength("abcdefg", "abcdexyz")); assertEquals(3, RadixTreeUtil.largestPrefixLength("abcdefg", "abcxyz")); assertEquals(3, RadixTreeUtil.largestPrefixLength("abcdefg", "abctuvxyz")); assertEquals(0, RadixTreeUtil.largestPrefixLength("abcdefg", "")); assertEquals(0, RadixTreeUtil.largestPrefixLength("", "abcxyz")); assertEquals(0, RadixTreeUtil.largestPrefixLength("xyz", "abcxyz")); } }
TestRadixTree.java
package com.sk.radixtree; import static org.junit.Assert.*; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; import com.sk.radixtree.*; public class TestRadixTree { private SecureRandom rng=new SecureRandom(); @Test public void testManyInsertions() { RadixTree<BigInteger> tree = new RadixTree<BigInteger>(); int n = rng.nextInt(401) + 100; // n in [100, 500] List<BigInteger> strings = generateRandomStrings(n); for(BigInteger x : strings) tree.put(x.toString(32), x); assertEquals(strings.size(), tree.size()); for(BigInteger x : strings) assertTrue(tree.containsKey(x.toString(32))); assertEqualsWithSort(strings, new ArrayList<BigInteger>(tree.values())); } private List<BigInteger> generateRandomStrings(int n) { List<BigInteger> strings = new ArrayList<BigInteger>(); while(n-- > 0) { final BigInteger bigint = new BigInteger(20, rng); if(!strings.contains(bigint)) strings.add(bigint); } return strings; } @Test public void testRealData() throws IOException { BufferedReader br = null; try { br=new BufferedReader(new InputStreamReader(new FileInputStream("data/dict.dat"))); final RadixTree<String> tree = new RadixTree<String>(); // Read data into a list first, because we want to see how long it // takes to construct the radix tree but don't want to include // I/O reads in that time int numLines = 0; String line = null; ArrayList<String[]> lines = new ArrayList<String[]>(); while((line = br.readLine()) != null) { line = line.trim(); if(line.length() > 0) { String pieces[] = line.split("\\s+", 2); if(pieces.length == 2) lines.add(pieces); } } // Construct tree and time the construction for(String [] pieces : lines) { // Just in case the DAT files has multiple of the same key if(tree.put(pieces[0], pieces[1]) == null) ++numLines; } // Perform tests assertEquals(numLines, tree.size()); assertEqualsWithSort(tree.getValuesWithPrefix("ACCI").toArray(new String[0]), new String[] { "a269c0caf5ed6edaaf0dfe6af2756256", // ACCIDENT "cd465f695982b126e418243260f60f9b", // ACCIDENTAL "e7fdb552e28b0fa234e7f3363d2577ed", // ACCIDENTALLY "9300fe6261f9198d86879c0925ca652c", // ACCIDENTLY "6524df69cc016e5d9a3cd03cb8a58815", // ACCIDENTS "edad4ecb40b0d95df72d3e587692f601", // ACCION "7868257ae2c6e06f8ccc6841cdc64ff1", // ACCIVAL }); assertEqualsWithSort(tree.getValuesWithPrefix("OSTEN").toArray(new String[0]), new String[] { "1eef84bdf3b7307e72b99c0ed0327165", // OSTEN "5011b15cd7e790336d23b70a48f7bed7", // OSTENDORF "4ed1b43b743a0dee269d4184880bbffd", // OSTENSIBLE "d318060019c4f987986e383cf700e7f9", // OSTENSIBLY "d32a6e75341b4c1797485622bb388c8a", // OSTENSON "4df4a85a0a9a345a03493c484090002d", // OSTENTATION "4849c1ab49637b63ba1570b5abf33d1c", // OSTENTATIOUS "d65f9261a233810f51c73cd38ab1398e", // OSTENTATIOUSLY }); assertEqualsWithSort(tree.getKeysWithPrefix("OSTEN").toArray(new String[0]), new String[] { "OSTEN", "OSTENDORF", "OSTENSIBLE", "OSTENSIBLY", "OSTENSON", "OSTENTATION", "OSTENTATIOUS", "OSTENTATIOUSLY" }); } finally { if(br != null) br.close(); } } @Test public void testRemoval() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("test", 1); tree.put("tent", 2); tree.put("tank", 3); assertEquals(3, tree.size()); assertTrue(tree.containsKey("tent")); tree.remove("key"); assertEquals(3, tree.size()); assertTrue(tree.containsKey("tent")); tree.remove("tent"); assertEquals(2, tree.size()); assertEquals(1, tree.get("test").intValue()); assertFalse(tree.containsKey("tent")); assertEquals(3, tree.get("tank").intValue()); } @Test public void testSpook() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("pook", 1); tree.put("spook", 2); assertEquals(2, tree.size()); assertEqualsWithSort(tree.keySet().toArray(new String[0]), new String[]{"pook","spook"}); } @Test public void testPrefixFetch() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("test", 1); tree.put("tent", 2); tree.put("rest", 3); tree.put("tank", 4); assertEquals(4, tree.size()); assertEqualsWithSort(tree.getValuesWithPrefix(""), new ArrayList<Integer>(tree.values())); assertEqualsWithSort(tree.getValuesWithPrefix("t").toArray(new Integer[0]), new Integer[]{1,2,4}); assertEqualsWithSort(tree.getValuesWithPrefix("te").toArray(new Integer[0]), new Integer[]{1,2}); assertEqualsWithSort(tree.getValuesWithPrefix("asd").toArray(new Integer[0]), new Integer[0]); } @Test public void testMultipleInsertionsOfTheSameKey() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("test", 1); tree.put("tent", 2); tree.put("tank", 3); tree.put("rest", 4); assertEquals(4, tree.size()); assertEquals(1, tree.get("test").intValue()); assertEquals(2, tree.get("tent").intValue()); assertEquals(3, tree.get("tank").intValue()); assertEquals(4, tree.get("rest").intValue()); tree.put("test", 9); assertEquals(4, tree.size()); assertEquals(9, tree.get("test").intValue()); assertEquals(2, tree.get("tent").intValue()); assertEquals(3, tree.get("tank").intValue()); assertEquals(4, tree.get("rest").intValue()); } @Test public void testMultipleInsertions() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("test", 1); tree.put("tent", 2); tree.put("tank", 3); tree.put("rest", 4); assertEquals(4, tree.size()); assertEquals(1, tree.get("test").intValue()); assertEquals(2, tree.get("tent").intValue()); assertEquals(3, tree.get("tank").intValue()); assertEquals(4, tree.get("rest").intValue()); } @Test public void testSingleInsertion() { RadixTree<Integer> tree=new RadixTree<Integer>(); tree.put("test", 1); assertEquals(1, tree.size()); assertTrue(tree.containsKey("test")); } @Test public void testEmptyTree() { RadixTree<Integer> tree=new RadixTree<Integer>(); assertEquals(0, tree.size()); } public static <T extends Comparable<? super T>> void assertEqualsWithSort(T[] a,T[] b) { Arrays.sort(a); Arrays.sort(b); assertArrayEquals(a, b); } /** * A variation of <code>assertEquals</code> that sorts its lists before comparing them. * * @param a one of the lists * @param b one of the lists */ public static <T extends Comparable<? super T>> void assertEqualsWithSort( List<T> a,List<T> b) { Collections.sort(a); Collections.sort(b); assertEquals(a, b); } }
相关文章推荐
- 简单组合java.util.Map<K,V>实现Map<K,P,V>
- 简单组合java.util.Map<K,V>实现Map<K,P,V>
- 简单组合java.util.Map<K,V>实现Map<K,P,V>
- 使用java.util.concurrent实现的线程池、消息队列功能
- android droiddraw 这么简单的功能都实现不了么?JAVA真不是人写的
- JAVA用List实现Map的基本功能
- Java简单实现视频录制播放功能
- Java二维数组实现简单Map(梁健-原创)
- Java语言实现简单FTP软件------>FTP软件效果图预览之下载功能(二)
- Java多线程-一个简单的线程,实现挂起和恢复的功能
- 使用Java实现简单的server/client回显功能的方法介绍
- 【java】IO流 实现简单的复制功能
- java中简单的翻页功能的实现(PageManager)
- Java语言实现简单FTP软件------>辅助功能模块FTP站点管理的实现(十二)
- log4j JAVA 简单日志功能 实现 实例 教程 代码
- Java多线程-一个简单的线程,实现挂起和恢复的功能
- 字符串处理是许多程序中非常重要的一部分,它们可以用于文本显示,数据表示,查找键和很多目的.在Unix下,用户可以使用正则表达式的强健功能实现这些 目的,从Java1.4起,Java核心API就引入了java.util.regex程序包,它是一种有价值的基础
- Java语言实现简单FTP软件------>FTP软件效果图预览之下载功能(二)
- java实现简单留言板功能的代码实例
- 关于java打印功能的最简单实现的学习笔记