集合

Java集合框架提供了一套性能优良、使用方便的接口和类,它们位于java.util包中

下面的图可较为简单的说明集合之间的关系 . 左面的collection接口存储的方式类似于数组,存储的是单一元素 , 而map接口存储的是键值对(key-value).

 

一.Collection

Collection 接口存储一组不唯一,无序的对象

常见方法:

  • add(Obj) 增加一个元素    addAll(Coll)  增加一个集合
  • clear() 清空集合   remove(obj) 移除一个元素     retainAll(coll)求2个集合的交集
  • size() 集合的长度    isEmpty()  集合是否为空 
  • contains(obj)  集合是否包含某个元素    containsAll(coll)集合是否包含另一个个元素

collection的迭代方式: (没有索引,所以不能使用for循环)

 

 

  1. 加强for循环

    for(Object obj:coll){
        System.out.println(obj);
    }

     

  2. java.util.Iterator 专门负责对Collection集合进行迭代的迭代器                                                                                java.lang.Iterable: 实现这个接口允许对象成为 "foreach" 语句的目标

          hasNext() 查看是否存在下一个元素
          next(); 移动指针 且获取当前指针指向的元素

/**
* Iterator<E> iterator() 
  返回在此 collection 的元素上进行迭代的迭代器。 
*/
//创建集合对象
Collection coll = new ArrayList();

Iterator it = coll.iterator();//实现类对象
		
while(it.hasNext()){//查看是否存在下一个元素
     Object obj = it.next();//指针向下移动一个  获取指针当前指向的元素
     System.out.println(obj);
}

      此迭代器是可以使用 for 循环的, 所以也可以写成另一种方式(第二种方式Iterator在for循环内, 生命周期短, 占用内存小)

for(Iterator it= coll.iterator();it.hasNext();){
	System.out.println(it.next());
}

二.List

List 接口存储一组不唯一有序索引顺序的对象

底层存储方式是数组。

 

List的实现类

1.ArrayList实现了长度可变的数组,在内存中分配连续的空间。

优点:遍历元素和随机访问元素的效率比较高

缺点:添加和删除需要大量移动元素效率低,按照内容查询效率低

2.LinkedList采用链表存储方式, 在内存中随机分配的空间 , 前一个元素中一部分存放值 ,另一部分存储着下一个元素的索引位置。

优点:插入、删除元素时效率比较高

缺点:遍历和随机访问元素效率低下

这两种list接口的优缺点我们可以从底层实现结构来解释:

       ArrayList  获取元素:索引获取, 0×1101+(索引×元素宽度)=元素地址 ,可以直接定位到元素地址, 所以快

                         添加、删除元素:需要拷贝数组+移动元素+数组拷贝 , 效率低

       LinkedKList  获取元素:获取下一个元素,先找到上一个元素,根据其中存放的下一个位置的索引可以找到下个元素,所以效率低

                             添加、删除元素:只需要改变存储位置就可以了, 效率低

常见方法:  除了collection的方法 , 还多了一些针对于索引的操作  索引位置从0开始

                 sublist(int fromIndex , int toIndex)  截取集合 , 左闭右开  

  •  增加  add(idnex,obj)    add(index,coll)  可添加元素或者集合
  •  删除  remove(index) 
  •  修改  set(index,obj)  
  •  查看  get(index)    indexOf(obj)查看obj出现的索引位置    lastIndexOf()查看最后一次出现的位置
//注意, 修改元素  返回的是要修改的元素值
//1、创建list集合
List ls = new ArrayList();
				
//2、添加元素
ls.add("长生剑");		
ls.add(0, "孔雀翎");
System.out.println(ls);   //打印的结果是[孔雀翎,长生剑]

System.out.println(ls.set(0, "情人箭")); 
//返回的是要修改的元素值---->孔雀翎,而非修改之后的值-->情人箭
System.out.println(ls);     //  [情人箭,长生剑]

          LinkedList 多了一些对于表头和表位的操作:

  • 查看头元素  peek()     peekFirst()   如果集合数据为空 返回null
  • 查看头元素  getFirst()     element()  如果集合数据为null  获取时会报错 java.util.NoSuchElementException
  • 查看尾元素  getLast()     peekLast()
  • 添加头元素  addFirst()    offerFirst()
  • 添加尾元素  addLast()   offer()   offerLast()
  • 删除头元素  removeFirst()  pollFirst()  poll()
  • 删除尾元素  removeLast()  pollLast()

 

  • 常用的遍历方式有三种:相比collection多了可以使用索引的for循环
  • List ls  = new ArrayList();
    //===========普通for==========
    for(int i = 0;i<ls.size();i++){
       System.out.println(ls.get(i));
    }
    		
    //===========增强for==========
    for(Object obj:ls){
       System.out.println(obj);
    }
    				
    //===========迭代器===========
    		
    Iterator it  = ls.iterator();
    while(it.hasNext()){
       System.out.println(it.next());
    }

    此外,对迭代器的补充:

                 比如说, 我们有一个需求:遍历集合对象  遍历过程中查看是否存在java 如果存在 添加元素  javascript

       采用for循环遍历是完全OK的 

for(int i = 0;i<ls.size();i++){
   if("java".equals(ls.get(i))){
       ls.add("javascript");
   }
}

      但当使用foreach和Iterator迭代器循环时,就都会报错 

      Exception in thread "main" java.util.ConcurrentModificationException
      当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常

while(lt.hasNext()){
   Object obj = lt.next();
   String str = (String)obj;
   if("java".equals(str)){
      lt.add("javascript");
   }
}

 为什么呢?

因为  :  ls 对象 指向了 list对象的地址   而 it 对象 指向了list对象地址, 一个通过指针迭代元素 一个要通过指针遍历元素 造成了一个共享资源被多个对象争抢, 这种情况就跟我们在修改文件的同时去修改文件名, 系统禁止我们操作是一样的. 此时如果出现该情况list集合的处理办法是对外抛出并发修改异常
解决方法是  :  通过ListIterator解决该问题 , 可以保证 并发修改 并且还可以做逆向遍历

ListIterator lt = ls.listIterator();
   while(lt.hasNext()){
      Object obj = lt.next();
      String str = (String)obj;
      if("java".equals(str)){
         lt.add("javascript");
      }
   }

 至于正向迭代和逆向迭代,下面的代码可以对比一下:

//正向遍历(默认的迭代方向)
ListIterator lt = ls.listIterator();
   while(lt.hasNext()){
      System.out.println(lt.next());
   }
				
//逆向遍历
while(lt.hasPrevious()){
   System.out.println(lt.previous());
}

注意:当我们在使用List独有的迭代器ListIterator中的previous()方法时,不能在最开始就逆向遍历。我们在遍历一个集合的时候,相当于有一个指针,最开始是指着集合的最前端,如果我们最开始就逆向遍历,虽然不会报错,但是会输出空值。

 

  • 3.Vector

上述的两种集合实现类都可以vector来替换,vector是同步的,是线程安全的,但是运行太慢了,如果想用一个线程安全且效率比vector高的有很多,所以基本上不用vector. 

三.Set

Set接口存储一组唯一,无序的对象

(存入和取出的顺序不一定一致)

 

HashSet:采用Hashtable哈希表(数组+链表, 1.8之后是数组+红黑树)存储结构

    HashSet的底层是由一个HashMap实例支持的.  

    线程不安全

      •优点:添加速度快,查询速度快,删除速度快

      •缺点:无序

针对hashset无序的缺点 , 在此基础上又有了LinkedHashSet

      • 采用哈希表存储结构,同时使用链表维护次序

      •有序(添加顺序

Java 1.7的hash表的底层实现方式:

         1.通过求当前存储对象的hash值

         2.通过hash算法获取到对应的元素位置

         3.如果该位置上没有元素, 直接填充到该位置

         4.如果该位置上有元素, 在该位置上形成一个链表, 将需要添加的元素追加到元素后(两个元素互不相等)

         5.如果两个元素相等, 则会调用equals方法, 比较相同位置上的元素, 如果不相等, 重复第四步, 追加元素, 如果相等, 不追加

 

HashSet存储自定义对象

       如果通过HashSet存储自定义对象时 想要通过属性的值确定当前对象是一个对象 , 那么需要重写hashcode 方法计算位置,并且重写equals 比较内容.

 

 

TrssSet(基于红黑树的平衡树)

      TrssSet的底层是由一个TreeMap实例支持的. 

       •优点:有序(排序后的升序)  查询速度比List快(按照内容查询)

       •缺点:查询速度没有HashSet

     常用方法: 多了一些关于顺序的方法 , 添加时将数据进行排序 默认升序  唯一

  • 获取最大值中的最小值[等于]  ceiling()   
  • 获取最大值中的最小值  higher()  
  • 获取最小值中的最大值[等于]  floor()  
  • 获取最小值中的最大值  lower()  
  • 删除第一个元素  pollFirst()  删除时先获取在删除
  • 删除最后一个元素  pollLast()

TreeSet的两种比较器 

   1.内部比较器

       TreeSet存储自定义对象, 自定义对象必须实现java.lang.Comparable 接口  重写compareTo方法  [正整数 大----0 相等----负整数 小], 否则会报异常.

   java.lang.ClassCastException: 
   com.java1001.collection.Car cannot be cast to java.lang.Comparable  类型转换异常

//自定义对象实现java.lang.Comparable接口
class Car implements Comparable{
	private String color;
	private String type;

//重写compareTo方法
      @Override
      public int compareTo(Object o) {
         Car c = (Car)o;
         return this.color.compareTo(c.color);	//按照color升序排序			
      }
}

   2.外部比较器

     创建TreeSet集合时  指定比较器 java.util.Comparator接口 编写内部类 或者是其他方式完成该类的实现类对象 . 在创建对象时 将实现类对象传入TreeSet集合中. 然后往集合中添加数据,数据的排序方式按照 指定的外部比较器进行排序.

//创建时,指定一个实现了comparator接口的实现类对象
TreeSet ts = new TreeSet(new Comparator() {
	@Override
	public int compare(Object o1, Object o2) {	
		Car c1 = (Car)o1;
		Car c2 = (Car)o2;
		return c1.getColor().length()-c2.getColor().length(); //按照color的字符串长度升序排序
	}	
});

 

四.Map

特点key-value映射

它有2个子类 , HashMap 和 TreeMap

常用方法:

  • 创建map集合  Map<String,Integer> mps = new HashMap<>();
  • 添加  put()   如果添加了重复键 值会覆盖  而且添加时会返回 前一次键对应的值
  • 查看  size()   isEmpty()   get(key)   containsKey()  是否包含key   containsValue()  是否包含value
  • 删除  remove(key)  删除时 返回对应键 的值  删除

迭代方法: 

   1.通过map集合提供的ketSet 方法返回key的set集合 , 再通过get(key) 方法获取到value

Set<String> keys = mps.keySet();
for(String key:keys){
	System.out.print(key+"-->"+mps.get(key)+",");
}
System.out.println();

   2.通过map集合提供的entrySet 方法 返回set集合  这个集合中存放了Entry对象  在Entry中对象中, 维护了 map集合中的一个k-v.
      遍历set集合获取每一个Entry对象 , 通过对象提供的getKey , getVlaue 获取当前对象维护的键值映射

   Set <Map.Entry<String, String>>  entrys =  map.entrySet()

Set<Entry<String, Integer>> entrys = mps.entrySet();

for(Entry<String,Integer> entry:entrys){
	System.out.println(entry.getKey()+"-->"+entry.getValue());
}	

   3.只遍历键或者值

//=================所有key==================
Set<String> keys = mps.keySet();
for(String key:keys){
	System.out.print(key+",");
}

//=================所有value==================
Collection<Integer> values = mps.values();
Iterator<Integer> it = values.iterator();
while(it.hasNext()){
	System.out.print(it.next()+",");
}

  

HashMap

    •Key无序 唯一(Set

    •Value无序 不唯一(Collection)

 

    HashMap 和 HashTable 的区别: 

  1. HashMap不同步, 线程不安全 , 运行效率高点  而HashTable同步, 线程安全 ,效率低
  2. HashMap允许使用null 作为键值 ,而HashTable不可以

    若想HashMap同步 , 最好在创建时完成这一操作,以防止对映射进行意外的非同步访问

    Map m = Collections.synchronizedMap(new HashMap(...));

    除了上述区别外, 其他的使用基本相同 . 底层存储结构是hash表  按照key值 进行位置判定以及内容比较

    容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

    迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。默认的初识容量是16 . 默认的加载因子是0.75 .如果加载因子过高 , 虽然会节省空间 , 但相应的会使查询成本增加 . 如果过低 , 会经常重复rehash 操作 , 降低性能 .

   HashMap存储自定义对象要重写hashcode以及equals方法

     重要的子类有:  LinkedHashMap , 它是有序的HashMap  速度快

 

TreeMap

     •有序 速度没有hash

     •底层存储结构是  二叉树   默认有序  存储数据是会自动排序  按照key 键值

     TreeMap<Integer,String> mps = new TreeMap<>();

   常用方法:

 

  迭代方法: 

  • firstEntry()   firstKey()   第一个(键最小的)
  • lastEntry()   lastKey()    最后一个(键最大的)
  • ceilingEntry()    ceilingKey()  大值中求小值 包含等于
  • higherEntry()   higherKey()   大值中求小值 不包含等于
  • floorEntry(22)   floorKey(22)  小值中求大值 包含等于
//用Iterator迭代器
Set<Map.Entry<Integer,String>> sets= mps.entrySet();
for(Iterator<Map.Entry<Integer, String>> it= sets.iterator();it.hasNext();){
	Map.Entry<Integer, String> entry = it.next();
	System.out.println(entry.getKey()+"-->"+entry.getValue());
}

 

 

TreeMap存储自定义对象作为键:

      com.java1001.map.Goods cannot be cast to java.lang.Comparable

     TreeMap 中的键作为自定义对象时要注意  :

             要么对象实现内部比较器  实现java.lang.Comparable 接口  重写compareTo方法
             要么创建TreeMap对象时 指定了外部比较器  指定比较器 java.util.Comparator接口 编写内部类 或者是其他方式完成该类的实现类对象

 

问题:SetMap有关系吗?

采用了相同的数据结构,用于mapkey存储数据上是Set

​​​​​ 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值