集合输出
在进行集合输出时都利用了数据类型中定义的toString方法,或是利用了List接口中的get方法。集合输出共有四种手段:1.Iterator 2.ListIterator 3.Enumeration 4.foreach
1.Iterator(单向迭代器)
在Collection接口中定义由iterator方法,因此通过集合的实例化对象调用此方法就可以使用迭代器输出集合.
Iterator接口最初的设计里面实际有三个抽象方法:
- 判断是否有下一个元素:
public boolean hasNext();
- 取得当前元素:
public E next();
- 删除元素:
public default void remove();
此方法从JDK1.8开始变为default完整方法
next()方法与hasNext方法成对出现,每当取得一个元素,迭代器的游标后移一个。
不能在遍历集合时修改集合元素,会引发异常。
System.out.println("单向迭代器迭代输出");
Iterator<Integer> iterator1=list.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
}
2.ListIterator(双向迭代器)
- 判断是否有上一个元素:
public boolean hasPrevious();
- 取得上一个元素:
public E previous();
Iterator接口对象是由Collection接口支持的,但是ListIterator是由List接口支持的
//通过迭代方法打印集合,正向遍历(listIterator是双向迭代器)
ListIterator<Integer> iterator=list.listIterator();
while(iterator.hasNext()){
System.out.print(iterator.next());
}
System.out.println(">>>>>>>>>>>");
//通过迭代方法打印集合,反向遍历
while(iterator.hasPrevious()){//此时游标已经移到了最末尾
System.out.println(iterator.previous());
System.out.println(" ->");
}
3. Enumeration枚举输出
只能够依靠Vector子类,因为 Enumeration最早的设计就是为Vector服务的,在Vector类中提供有一个取得Enumeration接口对象的方法:
取得Enumeration接口对象:public Enumeration elements()
判断是否有下一个元素:public boolean hasMoreElements();
取得元素:public E nextElement();
Enumeration<String> enumeration=((Vector<String>) vector).elements();
System.out.println(enumeration);
4.4 foreach输出
for(Integer e:list){
System.out.println(e);
}
Map集合
Collection集合的特点是对单个对象进行保存,而Map集合中会一次性保存两个对象(类型定义为泛型),并且两个对象间的关系是key=value结构,可以通过key值找到value
Map中常用方法:
向Map中追加数据public V put(K key,V value);
根据key取得value,若没有返回null public V get(Object key);
取得key的所有信息,key不能重复 public Set<K> keySet();
取得所有value信息,可以重复 public Collection<V>values;
Map的常用子类
1.HashMap
可在一个key中存放多个value,当根据key取得value时,取得的是最后一次存入此key的value
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
//key值可重复
map.put(1, "一班");
map.put(1, "一班优");
map.put(1, "一班差");
map.put(2, "二班");
map.put(3, "三班");
map.put(4, "四班");
//根据key取得value
System.out.println(map.get(1));
//取得Map中所有Key的信息
Set<Integer> set=map.keySet();
System.out.println(set);
//取得Map中所有value信息
Collection collection=map.values();
System.out.println(collection);
}
输出结果:[1, 2, 3, 4]
[一班差, 二班, 三班, 四班]
HashMap源码分析
HashMap的内部实现可以看做是数组和链表组成的复合结构,数组被分为一个个的桶,通过哈希值决定键值对在这个数组中的寻址,哈希值相同的键值对会以链表的形式存储,而哈希值不同的键值对会新开辟数组空间.
HashMap的存储结构示意图:
HashMap的数组的初始化是按照lazy-load原则,在首次使用时才会初始化,
添加集合元素的put方法中有一个putVal方法显示了HashMap的实现逻辑:如果数组为null,resize()方法负责初始化桶数组,默认桶容量为16个 ,当添加元素数量大于门限值(初始门限值为16,即桶容量)的0.75倍(0.75是默认负载因子,可改变)就会进行扩容,增加门限值,门限值的调整是以2倍数增加 (例如第一次扩容16=10000<<1,即32),扩容后,需要将老的数组中的元素重新放置到新的数组,是扩容主要的开销来源,(容量理论最大极限由 MAXIMUM_CAPACITY 指定,数值为 1<<30,也就是 2 的 30 次方).
键值对在哈希表中的位置(数组下标)运算规则:i=(n-1)&hash,因此存放位置并不仅仅只是依赖于key的哈希值,还需要将高位数据移位到低位进行异或运算 ,是因为有些数据计算出的哈希值差异主要在高 位,而 HashMap 里的哈希寻址是忽略容量以上的高位的,那么这种处理就可以有效避免类似情况下的哈希 碰撞。
当数组中的链表长度大于8(阈值)并且桶超过64个就会树化,图中的链表就会被改造为树形结构(红黑树,利用TreeNode类)。只要一个不满足,就会继续扩容。
2.Hashtable类
Hashtable 本身比较低效,因为它的实现基本就是将 put、get、size 等各种方法加上“synchronized”。简单来说, 这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同步操作时,其他线程只能等待,大大降低了并发操 作的效率
Hashtable与HashMap用法基本类似,其主要区别是:
3.ConcurrentHashMap
是能实现并发的HashMap,其内部实现设计为:里面则是HashEntry 的数组,和 HashMap类似,哈希相同的条目也是以链表形式存放。不同的是它将内部进行分段,当集合进行修改时就会把相应的段锁住,不能被get,其他段未被锁,依然可以get.
在 ConcurrentHashMap中扩容同样存在。不过有一个明显区别,就是它进行的不是整体的扩容,而是 单独对 Segment 进行扩
在构造的时候,Segment 的数量由所谓的 concurrentcyLevel (并发等级)决定,默认是 16,也可以在相应构造函数直接指定
4.TreeMap
TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的
若key是系统定义(如String,Integer等,不能修改其compareTo方法)TreeMap中若想对排序规则改变:通过TreeMap的构造方法传入比较器(Comparator)的实现接口的实现类对象(lambda表达式)
若是自定义的类:1.实现comprable接口,实现接口中的compareTo方法 2.不实现,在使用时指定比较器(Comparator)接口的实现对象(更灵活一些)
采用使用时指定Comparator,方式比较灵活,其具体实现与TreeSet类似
有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode()
Map集合使用Iterator输出
Map接口中没有iterator()方法直接提供迭代器输出,而是通过一个重要的方法public Set<Map.Entry<K, V>> entrySet();
将Map集合转为Set集合,再利用Set集合中的iterator()方法迭代.
//1.将Map集合变为Set集合
Set<Map.Entry<Integer,String>> setMap=map.entrySet();
//获得Iterator对象
Iterator<Map.Entry<Integer,String>> iterator=setMap.iterator();
while(iterator.hasNext()){
//取出每一个Map.Entry对象
Map.Entry<Integer,String> entry=iterator.next();
//取得key value
System.out.println(entry.getKey()+"="+entry.getValue());
}
栈与队列
栈是一种先进后出的数据结构 浏览器的后退、编辑器的撤销、安卓Activity的返回等都属于栈的功能。
栈
在Java集合中提供有Stack类,这个类时Vector的子类。
- 入栈 : public E push(E item)
- 出栈 : public synchronized E pop()
- 观察栈顶元素 : public synchronized E peek()
如果栈已经空了,那么再次出栈就会抛出空栈异常(EmptyStackException)。
队列
Stack是先进后出,与之对应的Queue是先进先出
队列用于高并发访问的情况,利用缓冲队列,将高并发用户的访问顺序排队,降低速度,当程序不能处理大量数据时,可以部署多个节点,成为分布式统称为集群,如果单节点不能满足存储,可以部署多个数据库,采用分布分表。当对数据访问的读写量不一致时,可以采用数据库的读写分离,将读的数据库多部署几个,当要实现读写同步时,部署一个数据库实现读写同步
Queue接口有一个子类LinkedListQueue<V> queue=new LinkedList<>();
使用Queue接口主要是进行先进先出的实现,在这个接口里面有如下的方法:
按照队列取出内容: public E poll();
队列在整个的操作之中可以起到一个缓冲的作用,那么可以利用队列修改之前多线程部分讲解的生产者和消费者程序模型
public static void main(String[] args) {
//生产者队列
Queue<String> queue=new LinkedList<>();
new Thread(new Runnable() {
{
System.out.println("生产者线程启动");
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
//生产数据,放入队列
queue.add("生产"+String.valueOf(Math.random()));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();//将一个线程启动,则与主线程分离,独立于主线程执行,在此线程中可以再定义执行逻辑
//两个线程的执行时间随机
//消费队列
//通过两个线程,实现消费者与生产者的分离解耦
new Thread(new Runnable() {
{
System.out.println("消费者线程");
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
System.out.println("消费队列"+queue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}