在之前的java容器文章中,对几种容器有了简要的特点介绍。本文介绍接口中一些基本的函数和其他补充。
List
add(index,element);
remove(index/element);//删除序号对应的元素或者元素本身
set(index);
get(index);//以上是增删改查也是最常用的方法
contains(element);
containsAll(list);//这个不用按顺序
indexOf(element);
//删除 包含 索引函数都是依赖equals方法
subList(start,end);//切片
retainAll(coll);//保留和后者的交集,直接修改自身
clear();
isEmpty();
特别的:
我们知道Collection接口中实现了Iterate接口,因而都实现了iterator()
方法,这个方法获取迭代器拥有hasNext() next() remove()
三个基本的方法。而List有listIterator()
方法可以获取list迭代器。这个迭代器对List进行了量身定做,可以初始化在某个序号下listIterator(int)
。他能对previous进行操作,同时又能获取Index和set值。因而listLterator有这些函数
hasNext();//关于下一个 初始化序号是几,下一个就是几
next();
nextIndex();
hasPrevious();//关于上一个 初始化序号是n,上一个是n-1
previous();
previousIndex();
remove();
set();
用法
ArrayList<Integer> list=new ArrayList(Arrays.asList(1,7,5,2));
ListIterator<Integer> it=list.listIterator(4);
while(it.hasPrevious()){
if(it.previous().equals(5)){
it.remove();
}
}
System.out.println(list);
Set
Set没有像List那样追加了很多自己的方法如get set等,set传承了Collection接口的方法。
Set保存不重复的元素,最常被使用的是测试归属性,你可以很容易的查询某个对象是否在set中,正因如此,查找成为了set中的最重要的操作。通常你会选择一个**HashSet应对快速查找**,**TreeSet应对排序好的遍历或输出**,而L**inkedHashSet应对按照插入顺序排序的遍历或输出**。
Set最常用的方法就是contains add 和containsAll,他们都非常简单
下面是HashSet和LinkedHashSet TreeSet对比,看得出LinkedHashSet是按照插入顺序输出的
Set<Integer> coll1=new LinkedHashSet<Integer>();
Set<Integer> coll2=new HashSet<Integer>();
Set<Integer> coll3=new TreeSet<Integer>();
coll1.add(1);coll2.add(1);coll3.add(1);
coll1.add(3);coll2.add(3);coll3.add(3);
coll1.add(2);coll2.add(2);coll3.add(2);
coll1.add(4);coll2.add(4);coll3.add(4);
System.out.println(coll1);//1324 LinkedHashSet按照插入顺序
System.out.println(coll2);//1234 刚好1234hashcode也是这个顺序
System.out.println(coll3);//1234 TreeSet进行了排序
Map
Map可以返回keySet和values,分别是个Set因为key不可重复和collection。
下面的Demo 展示了HashMap LinkedHashMap和TreeMap的存储特性,并且展示了LinkedHashMap实现LRU的方式。
Map<Integer,Integer> map1=new HashMap<Integer, Integer>();
Map<Integer,Integer> map2=new LinkedHashMap<Integer, Integer>(16,0.75f,true);
//指定第三参数accessOrder被get的就会放到最后
Map<Integer,Integer> map3=new TreeMap<Integer, Integer>();
map1.put(1,1);map2.put(1,1);map3.put(1,1);
map1.put(3,3);map2.put(3,3);map3.put(3,3);
map1.put(2,2);map2.put(2,2);map3.put(2,2);
map1.put(4,4);map2.put(4,4);map3.put(4,4);
System.out.println(map1);//Hashmap 1234 因为刚好1234hashcode也是这顺序
System.out.println(map2);//LinkedHashMap 1324 插入顺序
System.out.println(map3);//TreeMap 1234 自然排序
int a=map2.get(3);
map2.get(2);//LRU:取出元素后就放到最后面
System.out.println(map2);//LinkedHashMap 1432 第三参数设为true实现了LRU
这三种Map和之前三种Set有着相似的特性,其实是因为Set的底层都是Map实现的。但是LRU只能用Map实现。
如果用自己的类当做键的话,要注意两个前提:
Object.equals方法等价于==,是判断的对象引用是否是同一个,而非对象内部字段值。
Object.hashCode方法是对象地址计算散列值
所以如果我们没有重写这俩方法的话,会带来问题,比如两个字段一样的对象被认为是不同的key,另外就是get(xxx)的时候,xxx的字段值都一样但是就是取不出来。这就是因为HashMap中根据hashCode进行存储,通过equals判断是不是这个键。
Map map=new HashMap<Myclass,Integer>();
Myclass m1=new Myclass("frank");
Myclass m2=new Myclass("frank");
Myclass m3=new Myclass("frank");
map.put(m1,2);
map.put(m2,99);
System.out.println(map);
System.out.println(map.get(m3));
class Myclass{
String name;
public Myclass(String name){this.name=name;}
}
输出{org.bjtu.Myclass@74a14482=2, org.bjtu.Myclass@1540e19d=99},null
将类做下修改
class Myclass{
String name;
public Myclass(String name){this.name=name;}
public int hashCode(){
return this.name.hashCode();
}
public boolean equals(Object other){//注意这里的参数要是Object
return this.name.hashCode()==((Myclass)other).name.hashCode();
}
}
打印信息如我们所预期:
{org.bjtu.Myclass@5d2a992=99}
99
Queue
队列,LinkedList是双向链表所以既可以模拟堆栈,又可以模拟队列。而且LinkedList实现了Queue接口。
队列接口中相关方法:
offer(element);//队列追加
peek(); //取出队首 没有为null
poll(); //取出并删除队首 没有为null
注意左侧队首 右侧队尾
String[] a = {"2", "5", "1", "2"};
LinkedList<String> list = new LinkedList<>(Arrays.asList(a));
//Collection的子接口类基本都实现了参数为另一个collection的构造方法
list.offer("dsf");//add to last
list.peek();//return null or first
list.poll();//remove and return first or null
System.out.print(list);//5 1 2 dsf
PriorityQueue这个类的队列不是按照插入顺序排的而是按照Comparator进行排序,即先到不一定先被取出,而是优先级高的先被取出。使用方法:
PriorityQueue<String> queue = new PriorityQueue<>(Collections.reverseOrder());
queue.offer("1");
queue.offer("dsf");//add to last
queue.offer("3");
queue.offer("2");
while (true){
String s=queue.poll();
if(s==null)break;
System.out.print(s);
}//打印dsf321 因为默认的排序中 字符串是这个从大到小顺序的
注意:优先级只是针对poll的时候优先输出 不适用于迭代器等操作。
实用方法
foreach可以用于数组和任何实现了Iterator接口的类,如各种Collection,但是不能用于Map,如果非要用在Map上,则遍历的时候需要操作的不是map而是map.entry,这是键值对的set。
foreach遍历Map的方法:
HashMap<Integer,Integer> map=new HashMap();
map.put(1,33);
map.put(2,44);
for (Map.Entry<Integer,Integer> it:map.entrySet()) {
System.out.print(it.getValue());
}
Collections的静态方法:
max/min(coll,[comparator]);要求coll是泛型集合且实现Comparable接口
reverse(list);反转
shuffle(list);随机改变list顺序
sort(list<>,[comparator]);排序
swap(list,i1,i2);交换i1,i2位置的元素,比自己写的快
fill(list,x);填充全部类似于Arrays.fill
disjoint(coll1,coll2);两集合没有任何交集则返回true
synchronizedXXX();产生线程安全的集合。
初始化或批量添加的方法:
1 conllection1.addAll(collection2)
2 Arrays.asList(arr)
可以接受数组或者一列用,隔开的参数(变长参数),将其转化为只读的List
3 new ArrayList(Arrays.asList(xx))
可以将上述只读list构建为其他list或者set,通过构造方法
list和set都有接收另一个Collection的构造方法,这对转型来说很好用
4 Collections.addAll(collection,arr)
将数组追加到集合,比2中asList灵活一些,是追加
性能评估和使用场景
ArrayList底层用数组实现,读改速度较快,适合频繁get读取的场景
LinkedList底层双向链表,任意位置的写入代价固定,且实现了堆栈和队列,适合随机写入或堆栈队列模型场景
HashSet LinkedHashSet TreeSet的特性已经介绍
HashMap LinkedHashMap TreeMap的特性也已说明
有序的另一个好处遍历起来很快,也就是说TreeSet和LinkedHashSet的遍历一边的时间是比HashSet要短,同样LinkedHashMap和TreeMap的遍历时间也要比HashMap短。
更改参数以提高性能:
HashMap和ArrayList底层都是数组一个是链表数组,一个是对象数组,所以调整初始容量通过构造方法可以提高性能,而ArratList增长每次是0.5倍不能更改,HashMap负载因子是0.75是时间和空间的平衡的值,此值越大,空间占用越少,但时间占用越多,此值越小则空间占用越多,读写时间越少。