集合框架
就是一个内存中的”容器”,用来存储数据.实际开发中用来替代数组的使用
不同的集合框架底层使用到的数据结构不一样.
api包:java.util
Collection[I]
List[I] - 有序,可重复
ArrayList[C] - “底层就是动态数组 - 内存中是连续的,有序的 - 便于使用下标来访问元素.但是增删效率略低”
LinkedList[C] - “底层是一个双向链表,访问比ArrayList慢,但是增删效率高”
适合解决栈列和队列的业务 - 贪吃蛇
Vector[C] - 线程安全的,使用方式和ArrayList一致.
Set[I] - 无序,不可重复
HashSet[C] - “底层使用的哈希算法,底层就是HashMap”
SortedSet[I]
TreeSet[C] - “可以使用可比较接口或者比较器接口来实现排序的功能,但是仍然是不可重复的”
“底层是TreeMap”
Map[I] - 采用key-value键值对形式来存储数据
- HashMap[C] - 针对key无序不可重复
- Hashtable[C]
- Properties[C] - .properties属性文件在内存中映射的那个对象
迭代器Iterator
作用 - 为了访问不同数据结构的集合,提供了一种统一的方式.
ArrayList
1 | public Iterator<E> iterator() { |
LinkedList
1 | private class Itr implements Iterator<E> { |
ArrayList练习
集合中添加N个图书对象,将图书名包含java的图书全部删除!
ArrayList扩容机制
- 第一次调用add方法的时候,才会初始化数组elementData
- 默认的数组的长度是10个
- 扩容的倍数是1.5倍
ArrayList和Vector区别
要想回答这个问题,可以先把各种都讲特性,然后再从底层存储结构,线程安全,默认大小,扩容机制,迭代器,增删改查效率这几个方向入手。
底层存储数据结构 - 本质上都是数组,Vector是使用队列[先进先出]来存储数据,但是本质仍然是数组.
线程安全性 - 前者是线程不安全,后者是线程安全
默认大小 - 俩者都是长度为10
扩容机制 - 前者是1.5倍,后者默认是扩容2倍,可以扩容系数可以设置的.
ArrayList和Vector检索元素,由于是数组,时间复杂度是O(1),在集合的尾部插入或者删除是O(1),但是其他的地方增加,删除,都是O(n),因为涉及到了数组元素的移动
ArrayList的删除和插入的效率一定会比LinkedList低吗?- 不一定,看是否操作的是集合的尾部
LinkedList
jdk8.0用到的是双向链表结构
链表肯定会比数组占用更多的内存
阔以模拟栈列和队列的业务
单向链表结构 - 必须从头节点开始
element - 真实的数据
next - 下一个节点的地址
单向循环结构
![]()
get(int index)底层
1
2
3
4 public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
内部类
1
2
3
4
5
6
7
8
9
10
11
12 private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
核心方法node(index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 Node<E> node(int index) {
// index=2
// 1 2 3 4 5 6 = size = 6
if (index < (size >> 1)) { // 如果在中间位置的左边
Node<E> x = first; // 头节点开始遍历
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 从尾部
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
remove(int index)底层
1 | public E remove(int index) { |
unlink(node(index));
1 | E unlink(Node<E> x) { // x-即将删除的节点 |
作业
括号匹配
1
2
3
4
5
6
7
8
9
10
11 ()
[]
{}
()[]{}
([{}])
[()]{}
(]
([)]{}
~~~javaarr = ( [ { } ] )
永远将第一个元素压入栈顶 push
从arr的第2个位置开始遍历
i下标对应的元素,和栈顶元素进行比较getFast();
不匹配 - 继续压入栈顶
匹配 - 弹出栈顶
集合是否为空 isEmpty();( [ ) ] { }
]
)
[
(
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 public class Purcase{ //购买类
private String brand; //品牌
private String name; //产品名
private double cost; // 费用
//构造,getter/setter,toString...
}
List<Purcase> list = new ArrayList<>();
Purcase p1 = new Purcase("宝洁","洗手粉",18.5);
Purcase p2 = new Purcase("联合利华","肥皂",4.5);
Purcase p3 = new Purcase("宝洁","牙膏",32.5);
Purcase p4 = new Purcase("宝洁","毛巾",14.5);
Purcase p5 = new Purcase("洁利","洗面奶",26.0);
Purcase p6 = new Purcase("好迪","洗发水",27.5);
Purcase p7 = new Purcase("多芬","沐浴露",38.5);
Purcase p8 = new Purcase("宝洁","洗洁精",3.4);
list.add(p1);
list.add(p2);
....
要求:写一个程序,打印出各品牌所花费的总费用①.
[可选,排好序后再打印输出,按花费总费用的降序排序②]
map => 统计每个随机数出现的次数. 15个随机数 1~5
“python java 123 15901121 dfdfd fdfd”
贪吃蛇的实现步骤
输出一个主界面
蛇身(LinkedList->存储了N个Node) - 长度,蛇头,方向
随机生成n个食物 - 使用HashSet来进行存储
1
2
3
4
5
6 * * * * * * * * * * *
* 0
* # # #
* 0 0
*
*
创建一个对象Node(int x,int y);
创建一个类SnakeGame
3-1. 创建一个内部类Snake - 维护一个LinkedList
3-2. Set
foods = new HashSet<>();//存放食物
走一步算法
4-1. 没有吃到食物
1
2
3
4 1. 确定新的方向 - w a s d
2. 新的坐标Node(i,j)
3. addFirst - 将新的坐标放入到蛇头
4. 判断新的坐标是否属于foods,如果不属于removeLast();//删除最后尾节点4-2. 吃到食物
HashSet
HashSet和HashMap => 存放数据的性能 - HashMap更高 - HashSet可能是计算整个对象的hashCode方法
1 | Set<Book> set = new HashSet<>(); |
HashMap
线程不安全的.
用到的数据结构
jdk8.0-开始桶数组 + 单向链表 + 红黑树
桶 - 哈希桶
put底层源码剖析
1 | HashMap<Integer,String> map = new HashMap<>(); |
1 | transient Node<K,V>[] table; |
扩容resize()
1 | //static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 |
put过程
排序
- 通过比较器接口java.util.Comparator
- 通过java.lang.Comparable[I]
面试题
List和Set区别
1
2
3
4
5 1、List,Set都是继承自Collection接口
2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
3.Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。ArrayList和LinkedList区别
1
2
3
4
5 ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。
对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
LinkedList集合不支持 高效的随机随机访问(RandomAccess)
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
- ArrayList和Vector区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 (1)同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它
的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程
安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编
写线程安全的代码。
(2)数据增长:
ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需
要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存
储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原
来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。
ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提
供设置增长空间的方法。
- HashSet和HashMap区别
1
2
3
4 (1)HashSet实现了Set接口, 仅存储对象; HashMap实现了 Map接口, 存储的是键值对.
(2)HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true.
(3)当使用无参构造创建 HashSet对象时, 其实调用了 HashMap的无参构造创建了一个 HashMap对象, 所以 HashSet 的初始化容量也为16, 负载因子也为 0.75.
- TreeSet和TreeMap区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 TreeMap 和 TreeSet 是 Java Collection Framework 的两个重要成员,其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。虽然 TreeMap 和TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法
TreeSet和TreeMap的关系
与HashSet完全类似,TreeSet里面绝大部分方法都市直接调用TreeMap方法来实现的。
相同点:
TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步
运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。
不同点:
最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口
TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
TreeSet中不能有重复对象,而TreeMap中可以存在
TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。
- HashMap和Hashtable[线程安全]区别
1
2
3
4
5
6
7 线程安全性不同。HashMap线程不安全;Hashtable 中的方法是Synchronize的。
key、value是否允许null。HashMap的key和value都是可以是null,key只允许一个null;Hashtable的key和value都不可为null。
迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable还使用了enumerator迭代器。
hash的计算方式不同。HashMap计算了hash值;Hashtable使用了key的hashCode方法。
默认初始大小和扩容方式不同。HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍;Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1。
是否有contains方法。HashMap没有contains方法;Hashtable包含contains方法,类似于containsValue。
父类不同。HashMap继承自AbstractMap;Hashtable继承自Dictionary。
- Collection和Collections[集合工具类]区别
1
2
3
4
5
6 1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。
2、java.util.Collections 是一个包装类。 它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
TreeSet
利用通过java.lang.Comparable[I]
对象去实现这个接口,并且重写comparaTo方法
利用构造器中可以传入一个比较器接口 => 更加灵活一点
1
2
3 public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
泛型
泛型符号
?
1
2 List<?> list = new ArrayList<>();
list.add(null);//只能添加nullK,V - 键,值
T - 类型
E - 元素
用法
泛型类
泛型方法
1
2
3
4
5
6
7
8
9
10
11
12 public <T> void find(T t){
System.out.println("find:"+t);
}
public <T> T get(T t){
System.out.println("find:"+t);
return t;
}
public static <T> void find2(T t){
System.out.println("find:"+t);
}
泛型好处
1、类型安全
泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。没有泛型,这些假设就只存在于系统开发人员的头脑中。
通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了,而不是在运行时当作ClassCastException展示出来。将类型检查从运行时挪到编译时有助于Java开发人员更早、更容易地找到错误,并可提高程序的可靠性。
2、消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。尽管减少强制类型转换可以提高使用泛型类的代码的累赞程度,但是声明泛型变量时却会带来相应的累赞程度。在简单的程序中使用一次泛型变量不会降低代码累赞程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低累赞程度。所以泛型消除了强制类型转换之后,会使得代码加清晰和筒洁。
3、更高的运行效率
在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
4、潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。
应用
1 |
|
1 | //业务接口 |
指定上限和下限
指定下限
泛型参数可以是E,也可以是E的父类
1 ? super E指定上限
泛型参数可以是E,也可以是E的子类
1 ? extends E
泛型是没有多态的.