java基础-集合

以下内容为本人的学习笔记,如需要转载,请声明原文链接https://www.cnblogs.com/lyh1024/p/16738857.html

1.集合框架概述

1.1集合框架 的作用

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

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

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

集合中分为三大接口;

Collection、Map、Iterator

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

1.2 集合框架结构图:

java基础-集合

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

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

此类实现 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()方法的。

3.3 TreeSet(排序)

基于 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(顺序)

具有可预知迭代顺序的 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

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

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

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

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

实现 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()图解:

java基础-集合

stream.flatMap()图解:

java基础-集合

6.集合框架Map接口

6.1 Map接口(映射接口)

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

6.2 HashMap
/**
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();
}
​
}
​
额外补充:

<<左移,>>>右移

     数学意义:在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以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。

  计算 : 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

此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 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

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

基于红黑树(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等。

有待补充....

参考资料:

JDK1.8帮助文档

发表回复