java基础-集合

以下为本人的学习笔记

 

1.集合框架概述

1.1集合框架 的作用

在实际开发中,我们经常会对一组相同类型的数据进行统一管理操作。到目前为止,我们可以使用数组结构,链表结构,二叉树来实现。

数组的最大问题在于数组中的元素个数是固定的,要实现动态数组,还是比较麻烦,

在JDK1.2版本后,java完整提供了类集合的概念,封装了一组强大的,非常方便的集合框架API,让我们在开发中大大的提高了效率。

集合中分为三大接口;

Collection、Map、Iterator

集合框架的接口和类在java.util包中

 

1.2 集合框架结构图:

注:虚线表示接口,实现表示实现类。

 

1.3 Collection接口

Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 SetList)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。

接口的定义:

public interface Collection<E> extends Iterator<E>

 

2集合框架List接口

2.1 List接口

public interface List<E> extends Collection<E>

有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

/**
Collection接口:用于存储单个对象的集合
List接口:
1.有序的,可重复
2.允许多个null元素
3.具体的实现类有常用的:ArrayList,LinkedList,Vector
Set接口
*/
public class ListDemo{
    private static  void arrayList(){
        //使用集合来存储多个不同类型的元素(对象),在处理时会比较麻烦,在实际开发中,不建议这样使用
     // List list = new ArrayList();
        //在集合中存储相同类型的对象,第二个<>里在jdk1.8可以不用写类型Sting
List<String> list = new ArrayList<>();//加泛型约束String类型
            list.add("小米");
            list.add("调度");
            list.add("狗蛋");
            list.add("二毛");
            list.add("旺财");
        //遍历集合
        //for(int i = 0;i<list.size() ;i++),局部变量size会进栈,调用栈会比调用方法快,性能高得多,局部变量size只求一次,而方法要一直调用
        int size = list.size();
        for(int i = 0;i<size ;i++){
            System.out.println(list.get(i))//list.get(int i),获取下标为i 的值
        }
System.out.println(list.contains("小米"))//contains():List是否包含"小米"
        list.remove("小米")//删除"小米"
        String[] array = list.toArray(new String[]{});//toArray(),转换成array数组,参数:定义数组类型
        for(String s: array){
            System.out.println(s);
        }
    }
    public static void main(String[] args){
        arrayList();
    }
    
}

 在实际开发中,我们如何选择list的具体实现?

  1.安全性问题

  2.是否频繁插入,删除操作(LinkedList)

  3.是否是存储后遍历

面试题:怎么实现ArrayList,即ArrayList的原理?

 

2.2ArrayList

public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,Serializable

List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(

/**ArrayList
​
        1.实现原理,采用动态对象数组实现,默认构造方法创建了一个空数组
​
        2.第一次添加元素,扩展容量为10,之后的扩充算法:原来数组大小+原来数组的一半
​
        3.不适合进行删除或插入操作,否则导致位置会变
​
        4.为了防止数组动态扩充次数过多,建议创建ArrayList时,给定初始容量
​
        5.多线程中使用不安全,适合在单线程访问时使用,在单线程下使用效率高
​
        JDK1.2开始
​
*/
​

 

2.3 Vector

Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。

private static void vector(){
/** 
​
Vector
​
1.实现原理,采用动态对象数组实现,默认构造方法创建了一个大小为10的对象数组
​
2.扩充的算法:当增量为0时,扩充为原来大小的2倍,当增量>0时,扩充为原来大小+增量
​
3.不适合删除或插入操作
​
4.为了防止数组动态扩充次数过多,建议创建Vector时,给定初始容量
​
5.线程安全,适合在多线程访问时使用,在单线程下使用效率较低,因为内部方法加了synchronized同步锁
​
*/
​
Vector<String>  vector = new Vector<>();
    vector .add("小米");
    vector .add("调度");
    vector .add("狗蛋");
    vector .add("二毛");
    vector .add("旺财");
for(int i = 0;i<v.size();i++){
​
System.out.println(v.get(i))
}
     public static void main(String[] args){
        vector();
    }
​
}

面试题:Vector与ArrayLIst的区别?

 

2.4 LinkedList

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,Deque<E>,Cloneable,Serializable

List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 getremoveinsert 元素提供了统一的命名方法。

/** 
  LinkedList
      1.实现原理,采用双向链表结构实现
      2.适合插入,删除操作,性能高

*/
private static void linkedList(){
​
LinkedList<String>  list = new LinkedList<>();
            list.add("小米");
            list.add("调度");
            list.add("狗蛋");
            list.add("二毛");
            list.add("旺财");
            //遍历集合
            int size = list.size();
            for(int i = 0;i<size ;i++){
                System.out.println(list.get(i));
            }
}

 

3集合框架Set接口

3.1 set接口

public interface Set<E> extends Collection<E>

一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。

 

3.2HashSet
  • public class **HashSet<E>**extends AbstractSet<E>implements Set<E>, Cloneable, Serializable

此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

/**
​
Set接口
1.无序的(不保证顺序)
2.不允许重复元素
实现类:HashSet,TreeSet,LinkedHashSet,三者底层实现与Map关联
​
选择使用
如果要排序,选择treeSet
如果不要排序,也不用保证顺序选择HashSet
不要排序,要保证顺序,选择LinkedHashSet
​
*/ 
​
public class SetDemo{
​
public static void main(Sting[] args){
​
/** 
HashSet
1.实现原理,基于哈希表(HashMap)实现
2.不允许重复,可以有一个NULL元素
3.不保证顺序恒久不变(例:添加元素后,输出顺序会变)
4.添加元素时把元素作为HashMap的key存储,HashMap的value使用一个固定的Object对象
5.排除重复元素是通过equals来检查对象是否相同
6.判断两个对象是否相同,先判断两个对象的hashCode是否相同,(如果两个对象的hashCode相同,不一定是同一个对象,如果不同,那一定不是同一个对象;整数范围就这么大,有可能重复),如果不同,则两个对象不是同一个对象,如果相同,还要进行equals判断,equals相同则是同一个对象,不同则不是同一个对象。
7.自定义对象要认为属性值都相同时为同一个对象,有这种需求时,那么我们要重写对象所在实体类的hashCode和equals方法
​
小结
(1)哈希表的存储结构:数组+链表,数组里的每个元素以链表的形式存储
(2)如何把对象存储到哈希表中,先计算对象的hashCode值,再对数组的长度求余数,来决定对象要存储在数组中的哪个位置(不同的值放到数组里,相同的值按先后顺序作为链表放在一格数组里,先放进去的就是根,后进去的作为根的next)
(3)解决hashSet中的重复值使用的方式是:参考第六点
*/
​
private static void hashSet(){
Set<String> set = new HashSet<>();
set.add("张飞");
set.add("关羽");
set.add("刘备");
set.add("诸葛亮");
            set.add("曹操");
            set.add("诸葛亮");//把上面的"诸葛亮"替换掉,添加自定义的不同对象,且相同值的对象时不会被替换
​
            String[] names = set.toArray(new String[]{})
            for(String s : names){
            System.out.println(s);
            }
​
​
}
​
}
​
}
​
​
hashCode深入分析

hashCode()方法,在Object类中定义如下:

public native int hashCode();//native本地方法

hashCode是本地方法,它的实现是根据本地机器相关,当然我们可以在自己写的类中覆盖hashCode()方法,比如 String ,Integer,Double......等等这些类都是覆盖了hashCode()方法的。

  • 判断两个对象是否相同:先判断两个对象的hashCode是否相同,([重写hashCode(),根据值来计算hashCode,值相等判定为同一对象,]如果两个对象的hashCode相同,不一定是同一个对象,如果不同,那一定不是同一个对象;整数范围就这么大,有可能重复),如果不同,则两个对象不是同一个对象,如果相同,还要进行equals判断,equals相同则是同一个对象,不同则不是同一个对象。

  • 如何把对象存储到哈希表中:先计算对象的hashCode值,再对数组的长度求余数,来决定对象要存储在数组中的哪个位置(不同的值放到数组里,相同的值按先后顺序作为链表放在一格数组里,先放进去的就是根,后进去的作为根的next)

 

3.3 TreeSet(排序)
  • public class **TreeSet<E>**extends AbstractSet<E>implements NavigableSet<E>, Cloneable, Serializable

基于 TreeMap的 NavigableSet实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。

/**
有序的,基于TreeMap(二叉树数据结构),对象需要比较大小,通过对象比较器来实现,
对象比较器还可以用来去除重复元素,
如果自定义的数据类,没有实现比较器接口,将无法添加到TreeSet集合中。
​
*/
​
private static void treeSet(){
TreeSet<String> tree = new TreeSet<>(new CatComparetor());
    Cat c1 = new Cat("wanwan",2,1) //参数:名字,年龄,编号
    Cat c2 = new Cat("guanguan",3,2)
    Cat c3 = new Cat("wanwan",2,3)
    Cat c4 = new Cat("wanwan",2,1)
    tree.add(c1);
    tree.add(c2);
    tree.add(c3);
    tree.add(c4);
    System.out.println(tree.size() );//如果创建TreeSet实例时没有传入new CatComparetor()比较器的话,对象间无法比较排序,报类型转换异常错误

    for(Cat c : tree){
        System.out.println(c);
    }
    
}
​
public class CatComparetor implements Comparator<Cat>{
    public int compare(Cat o1,Cat o2){
       // return o1.getAge()-o2.getAge()//根据年龄来比较,相同年龄会被判定为同一对象,存不进去
        
        
    }
}

 

3.4LinkedHashSet(顺序)
  • public class **LinkedHashSet<E>**extends HashSet<E>implements Set<E>, Cloneable, Serializable

具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)

/**  
哈希表和链接列表实现
维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set  中的顺序(*插入顺序*)进行迭代
*/
private static void linkedHashSet(){
LinkedHashSet<String> set = new LinkedHashSet<>();//链表来记录位置
Cat c1 = new Cat("wanwan",2,1) //参数:名字,年龄,编号
    Cat c2 = new Cat("guanguan",3,2)
    Cat c3 = new Cat("wanwan",2,3)
    Cat c4 = new Cat("wanwan",2,1)
    set.add(c1);
    set.add(c2);
    set.add(c3);
    set.add(c4);
    
    for(Cat c : set){
        System.out.println(c);
    }
​
}

 

4.集合框架Iterator接口

4.1 集合输出

前面我们已经学习了集合的基本操作,很多情况下,我们需要把集合的内容进行输出,也就是遍历集合

遍历集合的方式有一下几种:

  1. Iterator

  2. ListIterator(一般用得很少)

  3. Enumeration(枚举迭代接口)

  4. foreach(最方便,用得也多)

其中Iterator的使用率最高,在JDK1.5后新增了foreach,也被大量使用。有了Iterator迭代器,不同的集合也可以用相同的方式来迭代,而内部隐藏了不同的具体实现

 

4.2 Iterator
  • public interface **Iterator<E>**

对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。

类型 说明
boolean [hasNext()如果仍有元素可以迭代,则返回true。
E next() 返回迭代的下一个元素。
void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。

 

4.3 ListIterator
  • public interface **ListIterator<E>**extends Iterator<E>

系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。

类型 说明
void [add(E e) 将指定的元素插入列表(可选操作)。
boolean hasPrevious()如果以逆向遍历列表,列表迭代器有多个元素,则返回true。
int nextIndex()返回对next 的后续调用所返回元素的索引。
E previous() 返回列表中的前一个元素。
int previousIndex()返回对previous` 的后续调用所返回元素的索引。
void set(E e)用指定元素替换nextprevious` 返回的最后一个元素(可选操作)。

 

4.4 Enumeration
  • public interface **Enumeration<E>**

实现 Enumeration 接口的对象,它生成一系列元素,一次生成一个。连续调用 nextElement 方法将返回一系列的连续元素。

注:此接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。

类型 说明
boolean hasMoreElements() 测试此枚举是否包含更多的元素。
E nextElement() 如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。

 

/**
​
​集合输出,迭代
​
*/
public class IteratorDemo{
//foreach,JDK1.5后才有
private static void foreach(Collection<Cat>){
for(Cat cat :c ){
System.out.println(cat);
}

}

//iterator,JDK1.5之前统一的迭代集合方式
private static void iterator(Collection<Cat>){
Iterator<Cat> iter = c.iterator();//iterator()以正确的顺序返回该列表中的元素的迭代器
while(iter.hasNext()){
System.out.println(iter.next());
}
        
     //Enumeration,常搭配Vector使用
    private static void enumeration(Collection<Cat>){
   Vector<Stirng> vs = new Vector<>();
        vs.add("tom");
        vs.add("job");
        vs.add("jack");
        vs.add("lily");
        
        Enumeration<String> es = vs.elements();
while(es.hasMoreElements()){
            System.out.println(es.nextElement());
        }

       // ListIterator提供向上遍历的方法previous()
}
​
public static void main(String[] args){
List<Cat> list = new ArrayList<>();
            Cat c1 = new Cat("wanwan",2,1) //参数:名字,年龄,编号
            Cat c2 = new Cat("guanguan",3,2)
            Cat c3 = new Cat("wanwan",2,3)
            Cat c4 = new Cat("wanwan",2,1)
            list.add(c1);
            list.add(c2);
            list.add(c3);
            list.add(c4);
            foreach(list);
            iterator(list);//输出遍历
        enumeration();
}
}

 

4.5 foreach

在前面中,我们使用foreach来输出数组的内容,那么也可以输出集合中的内容。在使用foreach输出时候要注意:创建集合时要指定操作泛型的类型。

List<Integer> numbers = new ArrayList<>();

JDK1.8新特性:(lambda表达式)

forEach(Consumer<? super String>  action)//参数+操作,操作结合lambda表达式

//no.1

numbers.forEach((Integer integer) - > {System.out.prinlnt(integer);});

//no.2

numbers.forEach( integer - > {System.out.prinlnt(integer);});

//no.3

numbers.forEach( integer - > System.out.prinlnt(integer));

//no.4

numbers.forEach(System.out :: prinlnt );

//no.5

numbers.forEach(new MyConsumer());

/** 
JDK1.8新方法z
​
*/
public 
    public static void foreach(){
    List<String> list = new ArrayList<>();
    list.add("ss");
    list.add("jg");
    list.add("er");
    list.add("vb");
​
    //Consumer,一个一个
        list.forEach(s->System.out.println(s););
        list.forEach(System.out::println);//::表示调用,System.out静态属性调用方法println
    }
​
    //foreach(Consumer<? super String>,action);Consumer<? super String>是String的子类   

 

5.JDK1.8新特性

Comsumer<T>接口 消费者接口, foreach()有用到

Function<T,R> 接口 表示接收一个参数并产生结果的函数,T代表类型,R代表返回值

Supplier<T>接口 代表结果供应商,返回一个结果

Predicate<T>接口 断言接口,用于测试

 

//表示接收一个参数并返回结果的函数
private static void functionTest(){
    String s = strToUpp("dfdfds",(str)->{str.toUpperCase();})//str是Function第一个传进去的参数(值是"dfdfds"),str.toUpperCase()是重写了Function的apply()。
    System.out.println(s);
}
​
public static String strToUpp(String str,Function<String,String> f){
    //Function<String,String>,第一个String是传进去参数的类型,规定了strToUpp()的第一个参数类型,第二个String是返回值的类型。
    //在Function里apply是抽象方法;被调用时用lambda重写
    return f.apply(str);
}
//-----------------------------------------------------------------------
//Supplier代表结果供应商,要什么给什么。(自己提前给)
private static void supplierTest(){
     List<Integer> list = getNums(10,()->{(int)(Math.random()*100);});
    list.forEach(System.out::println);
    
}
private static List<Integer> getNums(int num,Supplier<Integer> sup){
    List<Integer> list = new ArrayList<>();
    for(int i = 0;i<num;i++){
        list.add(sup.get());//把结果装进list
    }
    return list;
}
​
​
//---------------------------------------
//断言接口,作测试
private static void predicateTest(){
        List<String> list = ArrayList.asList("tom","carry","curly","larry");
        List<Sting> result = filter(list,(s)->s.contains("o"));
        return forEach(System.out::println);
}
private static List<Sring> filter(List<String> list,Predicate<String> p ){
    List<String> results = new AraryList<>();
    for(String s : list){
        if(p.test(s){//测试是否符合要求
            results.add(s);
        }
    }
    
}
​

 

5.1JDK1.8新特性之Stream

什么是Stream?

Stream是元素的集合,这点让Stream看起来有些类似Iterator

可以支持顺序和并行的对原Stream进行汇聚的操作;

我们可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”,“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!(stream流)

Stream接口提供了强大的集合处理功能,相比较于Iterator迭代器一个一个的处理元素,要好用太多了。

JDK1.8可以在接口添加类方法,成员方法,静态方法

/**
Stream接口:不是存储数据结构,数据源可以是一个集合,为了函数式编程创造,
惰式执行(用到数据才执行),数据只能被消费一次(从集合中关联数据,使用一次数据就没了)
​
两种类型的操作方法:
1.中间操作(生成一个Stream)
2.结束操作(执行计算操作)
*/
​
public class StreamDemo{
    public Static void main(String[] args){
        //foreach方法,Collection接口有stream(),
       Streams<String> stream =  Stream.of("well","done","yep","yep","study")//生成一个Stream
        //forEach属于结束操作
        stream.forEach((str)->{System.out.println(str);})
           
        //filter过滤,属于中间操作,返回一个流 
        stream.filter((s)->s.length()>3).forEach(System.out::println);
        
        //distinct去重复
        stream.distinct().forEach(s->System.out.println(s));
​
        //map映射,属于中间操作,返回一个流 
        stream.map(s->s.toUpperCase()).forEach(System.out::println);
​
        //flatMap平摊,组合多个不同集合的数据源
        Stream<List<Integer>> ss = Stream.of(Arrays.asList(1,2),Arrays.asList(3,4,5))
        ss.flatMap(list->list.stream()).forEach(System.out::println);
​
        //reduce,求聚合的操作都用它
        Optional<Stream>  opt = stream.reduce((s1,s2)->s1.length()>=s2.length()?s1:s2);//比较出最长的元素
        System.out.println(opt.get());//输出:study
        
        //collect,配合Collectors工具类生成不同的集合
        //Collectors里有toList(),toMap,toSet()...方法
       List<String> list =  stream.collect(Collectors.toList());
        list.forEach(s->System.out.println(s));
        
        //:: →方法的引用
        //引用静态方法     Integer::valueOf
        //引用对象的方法   list::add,不用加参数,会从lambda表达式里推断
        //引用构造方法     ArrayList::new     默认构造方法
    }
}

stream.map()图解:

stream.flatMap()图解:

 

6.集合框架Map接口

6.1 Map接口(映射接口)
  • public interface Map<K,V>

    映射键到值的对象。一张Map不能包含重复的键,每个键可以映射到至多一个值。(一对一关系)

Map接口很常用,尤其在复杂数据存储上优势明显

 

6.2 HashMap
  • public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

    基于哈希表的 Map接口的实现。这种实现提供了所有可选的Map操作,并允许 null值和 null键。(除了它是不同步的,允许使用null之外,HashMap类与Hashtable大致相同 。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变

/**
Map接口
1.键值对存储一组对象     (collection是存储一个对象)
2.Key不能重复(唯一),Value可以重复
3.具体实现类;HashMap,TreeMap Hashtable LinkedHashMap
4.HashMap与Hashtable的区别?
5.如何选择使用哪个?
排序且要二叉树就用TreeMap,
多线程下用Hashtable(用的少),HashMap(也可以构建出同步的)
单线程下用HashMap
在HashMap上要保证顺序,就用LinkedHashMap
6.数据结构:数组、链表、二叉树(红黑树)、哈希表(数组+链表)、栈、队列
*/
public class MapDemo{
    
    /**
    HashMap的实现原理:
    1.基于哈希表(数组+链表+二叉树(红黑树,保证树左右两端平衡))[JDK1.8哈希表新增二叉树]
    2.默认加载因子为0.75 (0.75就是数组存到了大小为75%的时候,就说明数组快存满了,是一个标准红线,要重新散列(重新散列就是重新创建数组,或扩充数组)) ,默认数组大小为16(数组位置0-15)
    3.把对象存储到哈希表中,如何存储?
    把key对象通过hash()方法计算hash值,然后用这个hash值对数组长度取余(默认16),来决定该KEY对象在数组中存储的位置,当这个位置有多个对象时,以链表结构存储,JDK1.8后,当链表长度大于8时,链表将转换为红黑树结构存储。
    这样的目的,是为了取值更快 ,存储的数据量越大,性能的表现越明显
    
    4.扩充原理:当数组的容量超过了75%,那么表示该数组需要扩充,如何扩充?
    扩充的算法是:当前数组容量<<1(相当于是乘2),扩大1倍,扩充次数过多,会影响性能,每次扩充表示哈希表重新散列(重新计算每个对象的存储位置),我们在开发中,尽量要减少扩充次数带来的性能问题。
    
    5.线程不安全,适合在单线程中使用
    */
    private static void hashMap(){
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"Tom");//1和"Tom"实际存储在map内部new的entry对象
        map.put(2,"Jack");
        map.put(3,"Vince");
        map.put(4,"Bin");
        map.put(5,"Lily");
​
        System.out.println("size="+map.size());
        //从Map中取值
       System.out.println( map.get(1));//通过key取value
        
        //map的遍历,方式一:遍历key和value
        Set<Entry<Integer,String>> entrySet = map.entrySet();//将entry转为set
        for(Entry e : entrySet){
            Sysetem.out.println(e.getKey() + "->" + e.getValue());
        }
        
        //方式二:遍历键key
        Set<Integer> keys = map.keySet();
        for(Integer i ; keys){
            String value = map.get(i);
            System.out.println(i+"->"+value);
        }
        //方式三:遍历值value
        Collection<String> values = map.values();
        for(String s : values){
            System.out.println(values);
        }
        
        //方式四:foreach
        map.forEach((key,value)->System.out.println(key+"->"+value);
                    
        System.out.println(map.containsKey(7));//false
                    
        //hash      15& 1434 相当于 1434 % 16
        Integer key = 1434;
        System.out.println((key.hashCod()) ^ ((key.hashCod()) >>> 16 ));
    }
​
​
    public static void main(String [] args){
        hashMap();
    } 
​
}
​
额外补充:

<<左移,>>>右移

  • <<:是逻辑左移,右边补0,符号位和其他位一样要移动。

     数学意义:在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。

  计算:3 << 2

    3 << 2,则是将数字3左移2位

    1、首先把3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011

    2、然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,最后在低位(右侧)的两个空位补零。

    3、则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12。

 

  • >>是一个“有符号”右移位运算符;它会将运算符左边的运算对象向右移动运算符右侧指定的位数。>>运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1

  计算 : 11 >>2(11为int型)

    1)、11的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011

    2)、把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。

    3)、最终结果是0000 0000 0000 0000 0000 0000 0000 0010。

    4)、转换为十进制是3。

 

6.3Hashtable

  • public class **Hashtable<K,V>**extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable

此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。

为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

   /**
     JDK1.0开始
     基于哈希表实现(数组+链表)
     默认数组大小为11,加载因子0。75
     扩充方式;原数组大小<<1(即*2)+1
     线程安全的(加了synchronized锁),用在多线程访问时
     
 */
                
private static void hashtable(){
​
Map<String,String> table = new Hashtable<>();
table.put("one","Lily");
table.put("two","Tom");
table.put("three","Bin");
​
table.forEach((key,value)->System.out.println(key+"->"+value);
}
​
​

面试题:HashtMap与Hashtable的区别?

 

6.4LinkedHashMap
  • public class **LinkedHashMap<K,V>**extends HashMap<K,V>implements Map<K,V>

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。

(因为HashMap顺序不可控,所以加入LinkedHashMap控制顺序)

  /**
    LinkedHashMap是HashMap的子类,由于HashMap不能保证顺序恒久不变,LinkedHashMap使用一个双重链表来维护元素添加的顺序
    */
private static void LinkedHashMap(){
    Map<String,String> table = new LinkedHashMap<>();
    table.put("one","Lily");
    table.put("two","Tom");
    table.put("three","Bin");
}

 

6.5TreeMap
  • public class **TreeMap<K,V>**extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, Serializable

基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator进行排序,具体取决于使用的构造方法。

/**
    基于二叉树的红黑树实现
    */
private static void treeMap(){
    Map<String,String> map = new TreeMap<>();
    map.put("one","Lily");
    map.put("two","Tom");
    map.put("three","Bin");
    map.forEach((key,value)->System.out.println(key+"->"+value);
​
    Map<Dog,String> dogs = new TreeMap<>();
    dogs.put(new Dog(1,"2ha",3),"dog1");
    dogs.put(new Dog(2,"wangwang",2),"dog2");
    dogs.put(new Dog(3,"hsq",4),"dog3");
    //Dog实体类要实现Comparable<Dog>接口,再重写compareTo方法(this.id-o.id)用ID做排序依据,才能输出,注意:当两个对象的key相同时,第一个对象与第二个对象被看做同一个对象,输出的value会是第二个对象的value,即key没变,value会被替换
    dogs.forEach((key,value)->System.out.println(key+"->"+value);
​
​
}

未来想用红黑树来存储,单个对象就用TreeSet,键值对对象就用TreeMap

 

6.6 Map接口JDK1.8新特性

在JDK1.8中Map接口提供了一些新的便利的方法。因为在文本中我所提到的所有Map方法都是以默认值方法的方式实现的,所以现有的Map接口的实现可以直接拥有这些在默认值方法中定义的默认行为,而不需要新增一行代码

Method  
getOrDefault(Object,v)  
putIfAbsent(k,v)  
remove(Object key , Object value)  
replace(k,v)  
replace(k,v,v)  
compute(k key,BiFunction<? super k, ? super v, ?extends v>remappingFunction)  
computeIfPresent(k key,BiFunction<? super k,?extends v>remappingFunction)  
merge(k key,v value,BiFunction<? super v,? super v,? extends v> remappingFunction)  
public class MapNewMethodDemo{
​
    public static void main(String[] args){
    
    Map<Integer,String> map = new HashMap<>();
     map.put("one","Lily");
    map.put("two","Tom");
    map.put("three","Bin");
    
    
    //getOrDefault,获取不到值就返回默认值字符串"null",没有这个方法的话,要加个判断其是否为null
    String value = map.getOrDefault(4,"null");
    
    String val = map.put(3."vince");//会覆盖旧值,并返回旧值
    System,out.println(val);
    String val = map.putIfAbsent(3,"vince");//putIfAbsent根据key查找value,如果value有值,就不put,即不覆盖旧值,并返回查到的value值。只会添加不存在相同key的值
    
    //根据键和值都匹配时才删除
    map.remove(key,value);
        
    
    map.replace(3,"vince");
    map.replace(2,"Lily","vince");
        
    map.compute(1,(k,v1)->v1+"1");
    map.computeIfAbsent(5,(value)->val+"test");
        
    //合并
    map.merge(1,"888",(oldVal,newVal)->oldVal.concat(newVal));//"888"是新值
        
    map.forEach((k,v)->System.out.println(k+"-."+v));
    }
​
}

 

7.Collections工具类

7.1排序查找(主要针对List接口相关)
Method 说明
reverse(List list) 反转指定List集合中元素的顺序
shuffle(List list) 对List中的元素进行随机排序(洗牌)
sort(List list ) 对List里的元素根据自然升序排序
sort(List list,Comprarator c) 自定义比较器进行排序
swap(List list,int i,int j) 将指定List集合中 i 处元素和 j 处元素进行交换
rotate(List list,int distance) 将所有元素向右移位指定长度,如果distance等于size那么结果不变
public class collectionsDemo{
​
        public  static void main(String[] args){
​
        List<String>  list  = new ArrayList<>();
        list.add("jack");
        list.add("tom");
        list.add("lily");
        //反转
        Collections.reverse(list);
        //打乱顺序    
        Collections.shuffle(list);
            
        //升序,排序    
        Collections.sort(list);
       // Collections.sort(list,c);//传比较器
​
            
        //交换
        Collections.swap(list,0,2);
            
        //旋转    
        Collections.rotate(list,1);
            
            
        System.out.println(list);
​
}
​
}

 

7.2查找和替换(主要针对Collection接口相关)
Method 说明
binarySearch(LIst list,Object key) 使用二分搜索法,以获取指定对象在List中的索引,前提是集合已经排序
max(Collection coll) 返回最大元素
max(Collection coll , Comparator comp) 根据自定义比较器,返回最大元素
min(Collection coll) 返回最小元素
min(Collection coll, Comparator comp) 根据自定义比较器,返回最小元素
fill(List list , Object obj) 使用指定对象填充
frequency(Collection Object o) 返回指定集合中指定对象出现的次数
replaceAll(List list,Object old,Object new) 替换
public class collectionsDemo{
    public  static void main(String[] args){
        Collections.binarySearch(list,"tom");
        
        System.out.println(Collections.max(list));
     
        System.out.println(Collections.min(list));
        //填充
        Collections.fill(list,"bin");
        
        System.out.println(Collections.frequency(list,"lily"));
        
        Collections.replaceAll(list,"lily","bin");
        
        System.out.println(list);
​
        
    }
}    

 

7.3同步控制

Collections工具类中提供了多个synchronizedXxx方法,该方法返回指定集合对象对应的同步对象,从而解决多线程并发访问时线程的安全问题。HashSet、ArrayList、HashMap都是线程不安全的,如果需要考虑同步,则使用这些方法。这些方法主要有:synchronizedSet , synchronizedSortedSet, synchronizedList, synchronizedMap, synchronizedSorteMap.

特别需要指出的是,在使用迭代方法遍历集合时需要手工同步返回的集合。

public class collectionsDemo{    
    public  static void main(String[] args){       
        List<String> syncList = Collections.synchronizedList(new ArrayList<String>());
    
    }
}    

 

7.4 设置不可变

Collections有三类方法可返回一个不可变集合:

emptyXxx():返回一个空的不可变的集合对象

singletonXxx():返回一个只包含指定对象的,不可变的集合对象

unmodifiableXxx():返回指定集合对象的不可变范围

List<String>  sList =  Collections.emptyList();
​
    sList.add("bin");//会报错,不能添加,因为它是一个空的list

 

7.5 其他
Method 说明
disjoint(Collecition<?> c1,Collection<?> c2) 如果两个指定collection中没有相同的元素,则返回true。
addAll(Collection<? super T> c,T...a) 一种方便的方式,将所有指定元素添加到指定collection中。
Comparator<T> reverseOrder(Comparator<T>cmp) 返回一个比较器,它强行反转指定比较器的顺序。如果指定比较器为null,则此方法等同于reverseOrder()(换句话说,它返回一个比较器,该比较器将强行反转实现Comparable接口那些东西collection的自然顺序。
/反转顺序
Collections.sort(list,Collections.reverseOrder());

 

7.6 Optional容器类(JDK1.8)

Optional容器类(只能放一个对象),主要解决的问题是臭名昭著的空指针异常

例:当一个返回值要作为另一个方法的参数,但是不知道这个返回值是否为空,可以使用Optional解决。

这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Method 说明
of() 为非null的值创建一个Optional。
ofNullable() 为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional
isPresent() 如果值存在返回true,否则返回false
get() 如果Optional有值则将其返回,否则抛出NoSuchElementException
ifPresent() 如果Optional实例有值则为其调用consumer,否则不做处理
orElse() 如果有值则将其返回,否则返回指定的其他值
orElseGet() orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值
orElseThrow() 如果有值则将其返回,否则抛出supplier接口创建的异常
map 如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional
flatMap 如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Function)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装
filter 如果有值并且满足断言条件返回包含该值的Optional,封装返回空Optional
//创建Optional对象的方式
Optional<String> optional = Optional.of("bin");
Optional<String> optional2 = Optional.ofNullable("bin");
Optional<String> optional3 = Optional.empty("bin");
​
System.out.println(optoinal.isPresent());
System.out.println(optoinal.get());
​
optional.ifPresent((value)->System.out.println(value));
​
System.out.println(optoinal.orElse("无值"));
​
System.out.println(optional.orElseGet(()->"default");
​
      try{
           optional3.orElseThrow(Exception::new);                  
      } catch (Exception e){
          e.printStackTrace();
      }
​
      Optional<String> optional4 = optional.map((value)->value.toUppercase());
                   
      System.out.println(optional4.orElse("no found"));
                   
      Optional<String> optional5 = optional.flatMap((value) >Optional.of(value.toUpperCase));
      System.out.println(optional5.orElse("no found"));
​
      Optional<String> optional6 = optional.filter((value)->value.length>3);           System.out.println(optional6.orElse("这个值的长度小于3"));
   

 

8.Queue、Deque接口

队列是一种特殊的线性表,是一种先进先出(FIFO)的数据结构。它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

返回值 Method和说明
boolean add(E e) 将指定的元素插入到此队列中,(如果可以立即执行此操作且不会违反容量限制), 成功后返回true, 如果当前没有可用空间,则抛出IllegalStateException。
E element() 检索,但不删除,这个队列的头。
boolean offer(E e) 将指定的元素插入到此队列( 如果立即执行且不违反容量限制),当使用有容量限制的队列时,此方法通常要优于add(E),后者可能无法插入元素,而只是抛出一个异常
E peek() 检索但不删除此队列的头,如果此队列为空,则返回 null
E poll() 检索并删除此队列的头,如果此队列为空,则返回 null
E remove() 检索并删除此队列的头。
/**
Queue接口:队列,是一种先进先出的线性数据结构(排队)
LinkedList类实现了queue接口
请求队列,消息队列,任务
​
*/
​
private static void queue(){
    Queue<String> queue = new LinkedList<>();
    queue.add("小花");
    queue.add("小黑");
    queue.add("小红");
    queue.add("小黄");
    queue.add("小紫");
    queue.add("小绿");
    
    System.out.println(queue.size());
    System.out.println(queue.peek());
    System.out.println(queue.size());
    System.out.println(queue.poll());
    System.out.println(queue.size());
​
    
​
}

Deque:一个线性collection,支持在两端插入和移除元素。此接口既支持有容量限制的双端对列,也支持没有固定大小限制的双端对列。接口定义在双端对列两端访问元素的方法。提供插入、移除和检查元素的方法。

/**
Deque接口:双端对列
Stack:堆栈:先进后出
*/
public static void deque(){
    Deque<String> deque = new LinkedList<>();
        Deque.add("小花");
        Deque.add("小黑");
        Deque.add("小红");
        Deque.add("小黄");
        Deque.add("小紫");
        Deque.add("小绿");
    System.out.println(deque.getFirst());
    System.out.println(deque.getLast());
}
​
private static void stack(){
    Stack<String> s = new Stack<>();
    //压栈
    s.push("Bin");
    s.push("TOm");
    s.push("Lily");
    System.out.println(s.peek());
    System.out.println(s.pop());
​
    
}

 

9.对象一对多与多对多关系

可以使用集合来表示实际开发中对象的一对多关系和多对多关系

public class Teacher{
//一个老师对应多个学生
......
private HashSet<Student> students = new HashSet<>();
​
......
}
​
public class Student{
//一个学生对应一个老师
......
private Teacher teacher;
​
......
}
​
public class OneToManyDemo{
    public static void main(String[] args){
    
    Teacher t1 = new Teacher("张老师",18,"女");
    Student s1 = new Student("小李",10);
    Student s2 = new Student("小王",12);
    Student s3 = new Student("小赵",13);
    
    //关联关系
    t1.getStudents().add(s1);
    t1.getStudents().add(s2);
    t1.getStudents().add(s3);
​
    s1.setTeacher(t1);
    s2.setTeacher(t1);
    s3.setTeacher(t1);
    print(t1);
    }
​
    private static void print(Teacher t1){
    System.out.println(t1.getName());
    for(Studend s : t1.getStudents()){
        System.out.pritln(s);
    }
    }
}

多对多关系一般拆成两个一对多关系,找个中间件做两个一对多关系中的多

 

10.迭代器设计模式

提供一个方法按顺序遍历一个集合内的元素,而又不需要暴露该对象 的内部表示。

应用场景

1.访问一个聚合的对象,而不需要暴露对象的内部表示

2.支持对聚合对象的多种遍历

3.对遍历不同的对象,提供统一的接口

 

迭代器模式的角色构成

1.迭代器角色(Iterator);定义遍历元素所需要的方法,一般来说会有这么三个方法,取得下一个元素的方法next(),判断是否遍历结束的方法hasNext(),移出当前对象的方法remove()

2.具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代

3.容器角色(Aggregate):一般是一个接口,提供一个Iterator()方法,例如java中的Collection接口,List接口,Set接口等

4.具体容器角色(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkedList,Set接口的哈希列表的实现HashSet等。

 

11.Guava对集合的支持

版本下载:https://repo1.maven.org/maven2/com/google/guava/guava/

下载路径:../→22.0/→guava-22.0-javadoc.jar(解压一下,一个文档)、guava-22.0-sources.jar(源代码)、guava-22.0-jar(要用的,导入项目去)

Guava(番石榴)工程包含了若干被Google的java项目广泛依赖的核心库,例如:集合、缓存、原生类型支持、并发库、通用注解、字符串处理、I/O等等。所有这些工具每天都在被Google的工程师应用在产品服务中。

Guava对JDK集合的扩展,这是Guava最成熟和为人所知的部分。

  1. 不可变集合:用不变的集合进行防御性编程和性能提升。

  2. 新集合类型:multisets,multimaps,tables等

  3. 强大的集合工具类:提供java.util.Collections中没有的集合工具

  4. 扩展工具类:让实现和扩展集合类变得跟容易,比如创建Collection的装饰器,或实现迭代器

 

  1. 只读设置

  2. 函数式编程:过滤器

  3. 函数式编程:转换

  4. 组合式函数编程

  5. 加入约束:非空、长度验证

  6. 集合操作:交集、差集、并集

  7. Multiset:无序可重复

  8. Multimap key 可以重复

  9. BiMap:双向Map(bidirectional Map)键与值不能重复

  10. 双键的Map--->Table--->rowKey+columnkey + value

 

使用JUnit组件测试,在方法上写注解@Test,就可以通过JUnit而不用再写main方法来调用

/**
只读设置    ImmutableList.of()
*/
@Test
public void testGuava1(){
    System.out.println("test guava");
    //在JDK里提供的,创建固定,不可变集合
    /**
    List<String> list = Arrays.asList("jack","tom");
    List.add("bin");//报错
    */
    List<String> list = new ArrayList<>();
    list.add("jack");
    list.add("tom");
    list.add("bin");
    list.add("lily");
    //依然不能添加的,实现只读集合
    List<String> readList = collections.unmodifiableList(list);
    
    //guava提供的只读
    ImmutableList<String> iList =  ImmutableList.of("jack","tom","lily","bin")
}
/**
过滤器 Collections2.filter()
*/
@Test
public void testGuava2(){
    //guava提供的创建ArrayList
    List<String> list = Lists.newArrayList("java","php","jack","ham");
    //guava提供的过滤器
    Collections<Sting> result =  Collections2.filter(list,(e)->e.startsWith("j"));
    result.forEach(System.out::println);
}
/**
转换  Collections2.transform()
*/
@Test
public void testGuava3(){
    Set<Long> timeSet = Sets.newHashSet(20180201L,20190515L,20201221L);
    Collections<String> timeCollect = Collections2.transform(timeSet,(e)->new SimpleDateFormat("yyyy-MM-dd").format(e));
    timeCollect.forEach(System.out::println);
}
/**
组合式函数   Functions.compose()
*/
@Test
public void testGuava4(){
    List<String> list = Lists.newArrayList("java","php","jack","ham");
    
    Function<Stirng,String> f1 = new Function<String,String>(){
        public String apply(String t){
            return t.length()>4?t.substring(0,4):t;
        }
    };
    Function<Stirng,String> f2 = new Function<String,String>(){
        public String apply(String t){
            return t.toUpperCase();
        }
    };
    Function<String,String> f = Functions.compose(f1,f2);
    Collections<String> coll = Collections2.transfrom(list,f);
    coll.forEach(System.out::println);
}
/**
加入约束:非空、长度验证
*/
@Test
public void testGuava5(){
    Set<Long> timeSet = Sets.newHashSet();
    //14版本可用,14后用不了
    Constraint<String> constraint = new Constraint<>(){
        public String checkElement(String element){
            
        }
    }
    //这两个还可以用
    Preconditions.checkArgument(expression);
    Preconditions.checkNotNUll(reference);
}
/**
集合操作:交集、差集、并集
*/
@Test
public void testGuava6(){
    Set<Integer> set1 = Sets.newHashSet(1,2,3);
    Set<Integer> set2 = Sets.newHashSet(3,4,5);
​
    //交集
    SetView<Inter> v1 = Sets.intersection(set1,set2);
    v1.forEach(System.out::println);
    
    //差集,以set1为主,找差异
    SetView<Inter> v2 = Sets.difference(set1,set2);
    v2.forEach(System.out::println);
​
    //并集,去重
    SetView<Inter> v3 = Sets.union(set1,set2);
    v3.forEach(System.out::println);
}
/**
Multiset:无序可重复
*/
@Test
public void testGuava7(){
    String s = "good good study day day up";
    String[] s2 = s.split(" ");
    HashMultiset<String> set = HashMultiset.create();
    for(String str :s2){
        set.add(str);
    }
    Set<String> set2 = set.elementSet();
    for(String str :set2){
        System.out.println(str+":"+set.count(str));
    }
}
/** 
Multimap  key 可以重复
*/
@Test
public void testGuava8(){
    Map<String,String> map = new HashMap<>();
    map.put("西游记","吴承恩");
    map.put("红楼梦","曹雪芹");
    map.put("水浒传","施耐庵");
    map.put("xxx","施耐庵");
​
    Multimap<String,String> mmap = ArrayListMultimap.create();
    Iterator<Map.Entry<String,String> iter = map.entrySet().iterator();
    while(iter.hashNext()){
        Map.Entry<String,String> entry = iter.next();
        mmap.put(entry.getValue(),entry.getKey());
    }
    Set<String> keySet = mmap.keySet();
    for(String key : keySet){
        Collection<String> values = mmap.get(key);
        System.out.println(key+"->"+values);
    }
}
/** 
BiMap:双向Map(bidirectional Map)键与值不能重复
*/
@Test
public void testGuava9(){
    BigMap<String,String> map = HashBiMap.create();
    map.put("finally_test","15112315478");
    map.put("bin_test","15178784545");
    String name = map.inverse().get("15112315478");
    System.out.println(name);
}
/** 
双键的Map--->Table--->rowKey+columnkey + value
*/
@Test
public void testGuava10(){
    Table<String,String,Integer> table = HashBasedTable.create();
    table.put("jack","java",80);
    table.put("tom","php",84);
    table.put("bin","java",60);
    table.put("lily","c++",89);
    
    Set<Cell<String,String,Integer>> cells = table.cellSet();
    for(Cell c: cells){
        System.out.println(c.getRowKey()+"-"+c.getColumnKey()+"-"+c.getVatlue());
    }
​
}

 

参考资料:

JDK1.8帮助文档

 

■免责申明
⒈ 本站是纯粹个人学习网站,与朋友交流共赏,不存在任何商业目的。
⒉ 本站利用了部分网络资源,版权归原作者及网站所有,如果您对本站所载文章及作品版权的归属存有异议,请立即通知我们,我们将在第一时间予以删除,同时向你表示歉意!

 

posted @ 2022-09-28 17:17  逝去の年华  阅读(261)  评论(0编辑  收藏  举报