《改善java代码》第五章:数组与集合代码优化
码云地址:https://gitee.com/yaohuiqin/codeimprove/tree/master/src/per/yhq/objectimprove/listorarrayprove
性能方面优先考虑数组:
对基本类型进行求和运算时,数组的效率是集合的10倍。- 对数组扩容方法:
public class expandCapacity { public static void main(String[] args) { String arrays[] = new String[10]; System.out.println(arrays.length); arrays = expandCapacity(arrays,13); System.out.println(arrays.length); } public static <T> T[] expandCapacity(T[] datas,int newlen){ newlen = newlen < 0 ? 0 : newlen; return Arrays.copyOf(datas,newlen); } }
- 警惕数组的浅拷贝:
public class shallowCopy { public static void main(String[] args) { int balloonNum=7; Balloon[] box = new Balloon[balloonNum]; for (int i=0;i<balloonNum;i++){ box[i]=new Balloon(Color.values()[i],i); } Balloon[] copybox = Arrays.copyOf(box,box.length); copybox[6].setColor(Color.blue); //打印: for (Balloon ballon:box){ System.out.print(ballon.getColor()+"id:"+ballon.getId()+" "); } System.out.println(); System.out.println("copybox数组的值:"); for (Balloon ballon:copybox){ System.out.print(ballon.getColor()+"id:"+ballon.getId()+" "); } } public static class Balloon{ Color color; int id; @Override public String toString() { return "Balloon{" + "color='" + color + '\'' + ", id=" + id + '}'; } public Balloon(Color color, int id) { this.color = color; this.id = id; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } public int getId() { return id; } public void setId(int id) { this.id = id; } } public static enum Color{ Red,Orange,yellow,green,indigo,blue,violet; } }
输出的值是:
- 在明确场景下为集合指定初始化容量:
集合在add操作时,会自动给集合扩容,例如,ArrayList就按1.5倍扩容,每次扩容会执行Arrays.copyOf(elementData, newCapacity),影响性能。
- 获取次大于最大值的元素:
注意,最大值可能是多个,所以排序后取次大值的元素的方式不可取。首先需要去重复然后再排序,去重不需要自己写,数组不能自动去重,但是Set集合却可以,而且Set的子类TreeSet还能自动排序。
public static int getSecond(Integer[] data){ List<Integer> dataList = Arrays.asList(data); TreeSet<Integer> ts = new TreeSet<Integer>(dataList); return ts.lower(ts.last()); }
- 注意,原始数据类型不能作为aslist参数,否则会引起程序混乱。
asList转换后的arraylist 是Array类下的子类ArrayList,不能进行add操作,会报错:
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108) at per.yhq.objectimprove.listorarrayprove.GetSecondMax.main(GetSecondMax.java:21)
- 对数据排序时,推荐使用Comparator 进行排序:
在java中,想要给数据排序,有两种实现方式,一种是实现Comparable接口,一种是实现Comparator接口。
先看一个例子:给公司职员排序时的代码,先按员工工号排序,代码如下:
public class Compareobjectoflist { public static void main(String[] args) { List<Employee> list = new ArrayList<Employee>(5); list.add(new Employee(1001,"张三",Position.Boss)); list.add(new Employee(1342,"李四",Position.Manage)); list.add(new Employee(990,"姚慧芹",Position.Staff)); list.add(new Employee(1003,"位喜会",Position.Boss)); list.add(new Employee(890,"张三",Position.Staff)); Collections.sort(list); //改善代码,利用实现了接口的Comparator类重写compare方法,改变排序规则,无需改变Employee类的代码 //Collections.sort(list,new PositionComparator()); for (Employee e:list){ System.out.println(e); } } private static class Employee implements Comparable<Employee>{ private int id; private String name; private Position position; public Employee(int id, String name, Position position) { this.id = id; this.name = name; this.position = position; } @Override public int compareTo(Employee o) { return new CompareToBuilder().append(id,o.id).toComparison(); } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Position getPosition() { return position; } public void setPosition(Position position) { this.position = position; } } private enum Position{ Boss,Manage,Staff } // static class PositionComparator implements Comparator<Employee>{ // // @Override // public int compare(Employee o1, Employee o2) { // return o1.getPosition().compareTo(o2.position); // } // } }
上面的代码只是根据员工的id进行排序,但我们想要按照职位来排序时,再改Employee时已不合适了,Employee是一个稳定类,为了排序而修改它显然不是好办法,而实现
Comparator接口再重写compare()方法时,改Collections.sort(list) 为 Collections.sort(list,new PositionComparator())时,可以更好的修改排序方式。
如果想修改成先根据员工的职位排序,再根据员工id排序时,可以用apache的工具类简化处理://改成职工排序和根据工号排序 static class PositionComparator2 implements Comparator<Employee>{ @Override public int compare(Employee o1, Employee o2) { return new CompareToBuilder().append(o1.position,o2.position).append(o1.id,o2.id).toComparison(); } }
- 实现了compareTo方法,就应该覆写equals方法,确保两者同步。
indexOf依赖equals方法查找,binarySearch则依赖compareTo方法查找
equals是判断元素是否相等,compareTo是判断元素在排序中的位置是否相同。 - 推荐用两个集合的运算:交集,并集,差集。
如果用对两个集合进行遍历来计算,并不优雅。
交集:list.retainAll(list2);
并集:list.addAll(list2);
差集:list.removeAll(list2);
无重复的并集 : list.removeAll(list2); list.addAll(list2) - 利用Collections.shuffle(lists); 方法打乱lists的排序,可以用来抽奖,也可以用在安全传输方面。
- 查询集合类中是否包含某元素,推荐用hashMap而不是用list集合 ,注意,HashMap中的HashCode应避免冲突。
public class QuickFindElementByHashMap { public static void main(String[] args) { int size = 10000; List<String> list = new ArrayList<String>(size); for(int i =0;i<size;i++){ list.add("value"+i); } long start = System.nanoTime(); list.contains("value"+(size-1)); System.out.println("list执行时间:" + (System.nanoTime()-start)+"ns"); Map<String,String> map = new HashMap<String, String>(size); for (int i=0;i<size;i++){ map.put("key"+i,"value"+i); } start = System.nanoTime(); map.containsKey("value"+(size-1)); System.out.println("hashmap执行时间:" + (System.nanoTime()-start)+"ns"); } }
执行结果:
原因分析:list.contains("value"+(size-1)); 需要循环遍历,源代码:public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
而 map.containsKey("value"+(size-1)); 无需循环遍历源代码:
public boolean containsKey(Object key) { return getNode(hash(key), key) != null; }
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
解析: tab[(n - 1) & hash] 根据hash值和table的长度获取数组的下标, 先讲解HashMap中的table是如何存储元素的,首先要说明以下三点:1、table数组的长度永远是2的n次幂。2、table数组中的元素是Entry类型。3、table数组中的元素位置是不连续的。
- Vector是ArrayList的多线程版本,HashTable是HashMap的多线程版本
- Set 的元素不可重复,List的元素是可以重复的,这里的重复是equals方法的返回值相等。
Set有一个常用的类:TreeSet。改类实现了默认排序为升序的Set集合。如果插入一个元素,默认会按照升序排列。但是这仅限于集合加入元素时对其排序,但是并不试用于可变量的排序。
解决方式: 修改元素后:Set = new TreeSet<Person> (new ArrayList<Person> (Set)) 不可用方法Set = new TreeSet(set),造成浅拷贝。或者不用treeset,改用List - 总结:
1、List: 实现了List接口的集合主要有:ArrayList、LinkedList、Vector、Stack,其中ArrayList是一个动态数组,LinkedList是一个双向链表,Vector是一个线程安全的动态数组,Stack是一个对象栈,遵循先进后出的原则。
2、Set是不包含重复元素的集合,其主要的实现类有:EnumSet、HashSet、TreeSet。其中EnumSet是枚举类型的专用Set,所有元素都是枚举类型;HashSet是以哈希码决定其元素位置的Set,其原理与HashMap相似,它提供快速的插入和查找方法; TreeSet是一个自动排序的Set,它实现了SortedSet接口。3、Map可以分为排序Map和非排序Map。排序Map主要是TreeMap类,它根据Key值进行排序。非排序Map主要包括:HashMap、HashTable、Properties、EnumMap等,其中Properties是HashTable的子类。它的主要用途是从Property文件中加载数据,并提供方便的读写操作。EnumMap则要求其Key必须是某一个枚举类型。
4、Queue,队列,分为两类,一类是阻塞式队列,队列满了以后再插入元素则会抛出异常。主要包括:ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingQueue。其中ArrayBlockingQueue是一个以数组方式实现的有界阻塞队列。PriorityBlockingQueue是依照优先级组建的队列,LinkedBlockingQueue是通过链表实现的阻塞队列。 另一类是非阻塞队列,无边界的,只要内存允许,都可以持续追加元素。我们经常使用的是PriorityQueue类。
5、数组:数组与集合最大的区别就是数组能够容纳基本类型。 所有的集合底层存储都是数组。
6、工具类:数组的工具类是java.util. Arrays 和 java.lang.reflect.Array 集合的工具类:java.util.Collections