java基础-集合
以下为本人的学习笔记
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<E> extends Iterator
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
类还为在列表的开头及结尾 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));
}
}
3集合框架Set接口
3.1 set接口
public interface Set<E> extends Collection<E>
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 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 集合输出
前面我们已经学习了集合的基本操作,很多情况下,我们需要把集合的内容进行输出,也就是遍历集合
遍历集合的方式有一下几种:
-
Iterator
-
ListIterator(一般用得很少)
-
Enumeration(枚举迭代接口)
-
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)用指定元素替换 next或 previous` 返回的最后一个元素(可选操作)。 |
4.4 Enumeration
-
public interface **Enumeration<E>**
实现 Enumeration 接口的对象,它生成一系列元素,一次生成一个。连续调用 nextElement
方法将返回一系列的连续元素。
注:此接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。
类型 | 说明 |
---|---|
boolean |
hasMoreElements() 测试此枚举是否包含更多的元素。 |
E |
/**
集合输出,迭代
*/
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接口的实现可以直接拥有这些在默认值方法中定义的默认行为,而不需要新增一行代码
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接口相关)
说明 | |
---|---|
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) |
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接口相关)
说明 | |
---|---|
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 其他
说明 | |
---|---|
disjoint(Collecition<?> c1,Collection<?> c2) | 如果两个指定collection中没有相同的元素,则返回true。 |
addAll(Collection<? super T> c,T...a) | 一种方便的方式,将所有指定元素添加到指定collection中。 |
Comparator<T> reverseOrder(Comparator<T>cmp) |
/反转顺序
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等。
版本下载: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")
}
/**
过滤器 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帮助文档
■免责申明
⒈ 本站是纯粹个人学习网站,与朋友交流共赏,不存在任何商业目的。
⒉ 本站利用了部分网络资源,版权归原作者及网站所有,如果您对本站所载文章及作品版权的归属存有异议,请立即通知我们,我们将在第一时间予以删除,同时向你表示歉意!