以下为本人的学习笔记
1.集合框架概述
1.1集合框架 的作用
在实际开发中,我们经常会对一组相同类型的数据进行统一管理操作。到目前为止,我们可以使用数组结构,链表结构,二叉树来实现。
数组的最大问题在于数组中的元素个数是固定的,要实现动态数组,还是比较麻烦,
在JDK1.2版本后,java完整提供了类集合的概念,封装了一组强大的,非常方便的集合框架API,让我们在开发中大大的提高了效率。
集合中分为三大接口;
Collection、Map、Iterator
集合框架的接口和类在java.util包中
1.2 集合框架结构图:
注:虚线表示接口,实现表示实现类。
1.3 Collection接口
Collection 层次结构_中的根接口。Collection 表示一组对象,这些对象也称为 collection 的 _元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何 _直接_实现:它提供更具体的子接口(如 Set
和 List
)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
接口的定义:
public interface Collection extends Iterator
2集合框架List接口
2.1 List接口
public interface List extends Collection
有序的 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(); }< code></list.size()></string>
在实际开发中,我们如何选择list的具体实现?
1.安全性问题
2.是否频繁插入,删除操作(LinkedList)
3.是否是存储后遍历
面试题:怎么实现ArrayList,即ArrayList的原理?
2.2ArrayList
public class ArrayList extends AbstractList implements List ,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(); }< code></v.size();i++){></string>
面试题:Vector与ArrayLIst的区别?
2.4 LinkedList
public class LinkedList extends AbstractSequentialList implements List ,Deque ,Cloneable,Serializable
List
接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null
)。除了实现 List
接口外, LinkedList
类还为在列表的开头及结尾 get
、 remove
和 insert
元素提供了统一的命名方法。
/**
 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)); } }< code></size></string>
3集合框架Set接口
3.1 set接口
public interface Set extends Collection
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 _set_抽象。
3.2HashSet
public class **HashSet<e>**extends AbstractSet<e>implements Set<e>, Cloneable, Serializable</e></e></e>
此类实现 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);
          }
​
​
}
​
}
​
}
​
​</string>
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</e></e></e>
基于 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()//根据年龄来比较,相同年龄会被判定为同一对象,存不进去
       
       
  }
}</cat></string>
3.4LinkedHashSet(顺序)
public class **LinkedHashSet<e>**extends HashSet<e>implements Set<e>, Cloneable, Serializable</e></e></e>
具有可预知迭代顺序的 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);
  }
​
}</string>
4.集合框架Iterator接口
4.1 集合输出
前面我们已经学习了集合的基本操作,很多情况下,我们需要把集合的内容进行输出,也就是遍历集合
遍历集合的方式有一下几种:
- Iterator
- ListIterator(一般用得很少)
- Enumeration(枚举迭代接口)
- foreach(最方便,用得也多)
其中Iterator的使用率最高,在JDK1.5后新增了foreach,也被大量使用。有了Iterator迭代器,不同的集合也可以用相同的方式来迭代,而内部隐藏了不同的具体实现
4.2 Iterator
public interface **Iterator<e>**</e>
对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。
类型说明 boolean
[hasNext() 如果仍有元素可以迭代,则返回
true。 E
next() 返回迭代的下一个元素。 void
remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
4.3 ListIterator
public interface **ListIterator<e>**extends Iterator<e></e></e>
系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。
类型说明 void
[add(E e) 将指定的元素插入列表(可选操作)。 boolean
hasPrevious() 如果以逆向遍历列表,列表迭代器有多个元素,则返回
true。 int
nextIndex() 返回对
next 的后续调用所返回元素的索引。 E
previous() 返回列表中的前一个元素。 int
previousIndex() 返回对
previous的后续调用所返回元素的索引。
voidset(E e)
用指定元素替换next
或previous
返回的最后一个元素(可选操作)。
4.4 Enumeration
public interface **Enumeration<e>**</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();
}
}</cat></string></stirng></cat></cat></cat></cat>
4.5 foreach
在前面中,我们使用foreach来输出数组的内容,那么也可以输出集合中的内容。在使用foreach输出时候要注意:创建集合时要指定操作泛型的类型。
List 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的子类 </string>
5.JDK1.8新特性
Comsumer 接口 消费者接口, foreach()有用到
Function 表示接收一个参数并产生结果的函数,T代表类型,R代表返回值
Supplier 接口 代表结果供应商,返回一个结果
Predicate 接口 断言接口,用于测试
//表示接收一个参数并返回结果的函数
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);
      }
  }
   
}
​</string></string></string></sring></sting></num;i++){></integer></integer></integer></integer></string,string></string,string>
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 默认构造方法
  }
}</string></stream></list<integer></string>
stream.map()图解:
stream.flatMap()图解:
6.集合框架Map接口
6.1 Map接口(映射接口)
*
public interface Map<k,v></k,v>
映射键到值的对象。一张Map不能包含重复的键,每个键可以映射到至多一个值。(一对一关系)
Map接口很常用,尤其在复杂数据存储上优势明显
6.2 HashMap
*
public class HashMap<k,v>
extends AbstractMap<k,v>
implements Map<k,v>, Cloneable, Serializable</k,v></k,v></k,v>
基于哈希表的 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();
}
​
}
​</string></integer></entry<integer,string></1(相当于是乘2),扩大1倍,扩充次数过多,会影响性能,每次扩充表示哈希表重新散列(重新计算每个对象的存储位置),我们在开发中,尽量要减少扩充次数带来的性能问题。>
额外补充:
<
- <
数学意义:在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以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</k,v></k,v></k,v>
此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 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);
}
​
​</1(即*2)+1>
面试题:HashtMap与Hashtable的区别?
6.4LinkedHashMap
public class **LinkedHashMap<k,v>**extends HashMap<k,v>implements Map<k,v></k,v></k,v></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");
}</string,string>
6.5TreeMap
public class **TreeMap<k,v>**extends AbstractMap<k,v>implements NavigableMap<k,v>, Cloneable, Serializable</k,v></k,v></k,v>
基于红黑树(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);
​
​
}</dog></dog,string></string,string>
未来想用红黑树来存储,单个对象就用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,BiFunctionremappingFunction)computeIfPresent(k key,BiFunctionremappingFunction)merge(k key,v value,BiFunction 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));
}
​
}</integer,string>
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);
​
}
​
}</string>
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>());
   
  }
}    </string></string>
7.4 设置不可变
Collections有三类方法可返回一个不可变集合:
emptyXxx():返回一个空的不可变的集合对象
singletonXxx():返回一个只包含指定对象的,不可变的集合对象
unmodifiableXxx():返回指定集合对象的不可变范围
List<string>  sList =  Collections.emptyList();
​
sList.add("bin");//会报错,不能添加,因为它是一个空的list</string>
7.5 其他
Method 说明disjoint(Collecition c1,Collection c2)如果两个指定collection中没有相同的元素,则返回true。addAll(Collection c,T…a)一种方便的方式,将所有指定元素添加到指定collection中。Comparator reverseOrder(Comparator 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"));
  </string></string></string></string></string></string>
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());
​
​
}</string>
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());
​
}</string></string>
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);
}
}
}</student>
多对多关系一般拆成两个一对多关系,找个中间件做两个一对多关系中的多
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最成熟和为人所知的部分。
- 不可变集合:用不变的集合进行防御性编程和性能提升。
- 新集合类型:multisets,multimaps,tables等
- 强大的集合工具类:提供java.util.Collections中没有的集合工具
-
扩展工具类:让实现和扩展集合类变得跟容易,比如创建Collection的装饰器,或实现迭代器
-
只读设置
- 函数式编程:过滤器
- 函数式编程:转换
- 组合式函数编程
- 加入约束:非空、长度验证
- 集合操作:交集、差集、并集
- Multiset:无序可重复
- Multimap key 可以重复
- BiMap:双向Map(bidirectional Map)键与值不能重复
- 双键的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")
}</string></string></string></string>
/**
过滤器 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);
}</sting></string>
/**
转换 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);
}</string></long>
/**
组合式函数 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);
}</string></string,string></string,string></stirng,string></string,string></stirng,string></string>
/**
加入约束:非空、长度验证
*/
@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);
}</string></long>
/**
集合操作:交集、差集、并集
*/
@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);
}</inter></inter></inter></integer></integer>
/**
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));
}
}</string></string>
/**
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);
  }
}</string></string></string,string></map.entry<string,string></string,string></string,string>
/**
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);
}</string,string>
/**
双键的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());
}
​
}</cell<string,string,integer></string,string,integer>
参考资料:
JDK1.8帮助文档
■免责申明
⒈ 本站是纯粹个人学习网站,与朋友交流共赏,不存在任何商业目的。
⒉ 本站利用了部分网络资源,版权归原作者及网站所有,如果您对本站所载文章及作品版权的归属存有异议,请立即通知我们,我们将在第一时间予以删除,同时向你表示歉意!
Original: https://www.cnblogs.com/lyh1024/p/16738857.html
Author: 逝去の年华
Title: java基础-集合
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/615907/
转载文章受原作者版权保护。转载请注明原作者出处!