集合实际上就是一个容器,可以用来容纳其他类型的数据,数组相当于一个最简单的集合
集合在java中本身就是一个容器,是一个对象,是一个引用数据类型
集合容器中存储的其实是每个对象的内存地址,每个内存地址指向各自的对象
集合里面可以套集合
java中每一个不同的集合,底层都会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构中,数据结构 就是数据存储的结构,不同的数据结构,数据存储方式不同
例如:数组,二叉树,链表,哈希表
所有的集合都是可迭代的,可遍历的
java中集合分为两大类:
一类是单个方式存储元素(对象)
以单个方式存储元素,这一类的集合中超级父接口:java.util.Collection
一类是以键值对的方式存储元素(对象)
以单个方式存储元素,这一类的集合中超级父接口:java.util.Map
在java中,数据结构已经写好了常用的工具类Collections
需要掌握怎么用,在合适的情况下选择合适的集合
new不同的对象,会创建一个底层数据结构不同的集合
所有的集合类和集合接口,都在java.util.*下边
Map下的集合不能使用Collections工具类
没有使用泛型之前,Collection中可以存储Object的所有子类型
使用泛型之后,Collection中只能存储某个具体的类型
boolean add(Object e) 向集合中添加元素
//创建一个集合对象
//Collection collection = new Collection(); 接口是抽象的,无法实例化
//多态
Collection c = new ArrayList();
c.add(1200);//自动装箱
c.add(3.14);//自动装箱
c.add(new Object());
c.add(new Student());
c.add(true);//自动装箱
int size() 获取集合中元素的个数
//获取集合中元素的个数
System.out.println("集合中元素个数为:"+c.size());
void clear() 清空集合
//清空集合
c.clear();
System.out.println("集合中元素个数为:"+c.size());//0
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
c.add("hello");
c.add("world");
//判断集合中是否包含hello
boolean b = c.contains("hello");
System.out.println(b);//true
void remove() 删除集合中某个元素
//删除集合中某个元素
c.remove("hello");
System.out.println(c.contains("hello"));//false
boolean isEmpty() 判断集合是否为空
//判断集合是否为空
System.out.println(c.isEmpty());//false
c.clear();
System.out.println(c.isEmpty());//true
Object[] toArray() 将集合转换为数组 [作为了解,使用不多]
//将集合转换为数组
Object[] objects = c.toArray();
System.out.println(Arrays.toString(objects));//[world]
boolean retainAll() 保留指定集合中的元素
Iterator迭代器,只能在所有的Collection及其子类中使用,Map集合下不能用,因为Collection继承了Iterable接口
public interface Collection<E> extends Iterable<E>
//如下的遍历方式/迭代方式,是所有Collection通用的一种方式
//创建集合对象
Collection c = new ArrayList();//
//添加元素
c.add("hello");
c.add("world");
c.add(123);
c.add(new Object());
//对集合Collection进行遍历/迭代
//第一步:获取集合对象的迭代器对象
Iterator it = c.iterator();
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合
/*
boolean hasNext()如果仍有元素可以迭代,则返回true
Object next() 返回迭代的下一个元素
*/
while (it.hasNext()){
//存进去什么类型,取出来就是什么类型
//此时并没有使用泛型,所以取出来就是Object类型
Object obj = it.next();
//输出的时候会换成字符串,因为println会调用toString
System.out.println(obj);
}
使用增强for循环,代码更加方便简洁,同样适合所有的Collection及其子类使用
for(Object o : c){
System.out.println(o);
}
contains方法是用来判断集合中是否包含某个元素的方法,调用了equals方法进行比较,比较内容不比较地址
Collection c = new ArrayList();
//像集合中存储元素
String s1 = new String("abc");
c.add(s1);
String s2 = new String("abc");
c.add(s2);
//源码中比较的不是地址,是指内容
System.out.println(c.contains(x));//true
List集合存储元素特点:有序可重复,存储的元素有下标
有序指的是存进去是什么顺序取出来还是什么顺序,不是指按照大小排序
常用类:
ArrayList(非线程安全):集合底层数据结构为数组
LinkedList:集合底层数据结构为双向链表
Vector(线程安全):集合底层数据结构为数组
其所有方法都有synchronized修饰,但是效率低下,现在保障线程安全有其他方法,Vector使用较少
1.默认初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素是,初始化容量是10 )
private static final int DEFAULT_CAPACITY = 10;
2.集合底层是一个Object[]数组
private static final Object[] EMPTY_ELEMENTDATA = {};
3.常用构造方法:
new ArrayList();//创建默认定长ArrayList
new ArrayList(20);//创建定长ArrayList
new ArrayList(set);//创建一个ArrayList,并将set集合中的元素放进去
//该方法可以用于Collection下的所有集合
4.ArrayList集合的扩容:
以下是底层扩容数组的代码,通过位运算使新数组的容量变为原容量的1.5倍。
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
注意:尽可能少的扩容。因为数组扩容效率比较低,建议在使ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
5.数组优点:
检索效率比较高。( 每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6.数组缺点:
随机增删元素效率比较低
7.向数组末尾添加元素,效率很高,不收影响
8.面试时如何介绍
ArrayList是最常用的集合,因为往数组末尾添加元素,效率不受影响。另外,我们检索/查找某个元素的操作比较多
1.链表的结构
对于链表的数据结构来说,基本的单元是Node
对于单项链表来说,任何一个结点Node中都有两个属性:
一个属性,是存储的数据,另一个属性是下一个结点的内存地址
2.链表的优点
由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。
3.链表的缺点
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。
所以LinkedList集合检索/查找的效率较低。
4.LinkedList和ArrayList 区别
ArrayList :把检索发挥到极致。( 末尾添加元素效率还是很高的)
LinkedList :把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
因为Vector使用较少便没有过多研究,不过中途发现了有趣的一点,ArrayList 中通过位运算扩容,Vector通过三目运算符判断扩容,ArrayList扩容后为1.5倍,Vector扩容后为两倍。
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
无序不可重复,存储的元素无下标,无序指的是存进去什么顺序取出来就不一定是这个顺序了
常用类:
HashSet:底层实际上new了一个HashMap集合,向HashSet中存储元素,实际上是存储到HashMap上了
HashSet是一个哈希表(散列表)数据结构
TreeSet:底层实际上new了一个TreeMap集合,间接实现了SortedSet接口,能够对集合中的对象进行排序。
TreeSet是一个红黑树数据结构
1.按照哈希算法来存取集合中的对象,存取速度比较快。
2.(详情原理看5.6.8和5.6.9)当向集合中加入一个对象时,HashSet会调用对象的hashCode()方法来获得哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。
3.如果要判断去重,必须重写hashCode()和equals()。JVM会先调用hashCode()比较哈希码,确定相同为true后再调用equals(),确定值相同则会认为两个对象是同一个
Set set = new HashSet();
Customer customer1 = new Customer("Tom", 15);
Customer customer2 = new Customer("Tom", 15);
set.add(customer1);
set.add(customer2);
System.out.println(set.size());//2
//因为customer中未重写hashCode()和equals()
4.底层实际上new了一个HashMap集合,该HashMap初始化容量是16,默认加载因子是0.75,意味着如果容量到达四分之三则会扩容,以下是HashSet源码节选
//HashMap中的key用来保存set的值 private transient HashMap<E,Object> map; //使用一个PRESENT作为Map集合的所有value。 private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } //HashMap初始化容量是16,默认加载因子是0.75 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
1.TreeSet集合储存元素特点:
1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合
2.无序指的是存进去的顺序和取出来的顺序不同,并且没有下标
2.TreeSet支持两种排序方式:自然排序和客户端排序。
自然排序:实现Comparable接口并重写compareTo方法
客户端排序:实现比较器Comparator接口并重写compare方法
返回值等于0,x=y,结果不变
返回值大于0,x>y,升序排列
返回值小于0,x Comparable和Comparator怎么选择呢? Comparator接口的设计符合0CP原则。 3.底层实际上new了一个TreeMap集合 HashMap:底层是哈希表数据结构,非线程安全,对放入其中的键用hash算法进行去重操作 HashTable:synchronized修饰,效率低,使用较少 子类:Properties:继承HashTable,储存也用键值对,只支持String类型 SortedMap:子类:TreeMap:底层数据结构是二叉树,对其中放入的数据除了去重之外,还需要对放入其中的键进行排序 1.Map和Collection没有继承关系。 2.Map集合以key和value的方式存储数据:键值对 key:非空且唯一 value:没有要求 键相同的情况下,会做值的覆盖 key和value都是引用数据类型。 key和value都是存储对象的内存地址。 key起到主导的地位, value是key的一个附属品。 V put(K key, V value) 向Map集合中添加键值对 V get(object key) 通过key获取value boolean isEmpty( ) 判断Map 集合中元素个数是否为e void clear() 清空Map 集合 int size() 获取Map 集合中键值对的个数。 boolean containsKey(Object key) 判断Map 中是否包含某个key boolean containsValue(object value) 判断Map 中是否包含某个value V remove(object key) 通过key 删除鍵值对 Map无法直接遍历,都需要通过方法间接转换成Set之后才能用Collection中的遍历方式进行遍历 Set keySet() 获取Map集合所有的key (所有的键是一个set集合) Set 如上,使用了keySet()或是entrySet()方法后,就可以像遍历Collection一样遍历Map,无论是用迭代器还是增强for循环都可以 HashMap集合key部分允许null,但是只能用一个null,面试可能会问,但是开发用不到 1.HashMap 集合底层是哈希表/散列表的数据结构。 2.哈希表是一个怎样的数据结构呢? 哈希表是一个数组和单向链表的结合体。 3.HashMap集合底层源代码: public class HashMap{ 哈希表/散列表:一维数组,这个数组中的每一个元素都是一个单项链表(数组和链表的结合体) 4.为什么哈希表的随机增删,以及查询效率都很高? 增删是在链表上完成。 5.HashMap 集合的key部分特点: 无序,不可重复。 6.哈希表HashMap 使用不当时无法发挥性能! 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了 7.HashMap初始化容量是16,默认加载因子是.75 8.向Map集合中存与取,都是先调用key的hashCode方法,然后再调用equals equals方法有可能调用,也有可能不调用 拿put(k,v)举例,什么时候equals不会调用? 拿get(k)举例,什么时候equals不会调用? 注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写,并且equals方法返回如果是true , hashCode()方法返回的值必须一样,equals方法返@true表示两个对象相同,在同一个单向链表上比较,那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的,所以hashCode()方法的返回值也应该相同。 9.范围问题 在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数童小于6时,会重新把红黑树变成单项链表数据结构,这种方式也是为了提高检索缩小扫描范围,提高效率 对于哈希表数据结构来说: 基本同TreeSet一样,排序实现接口也与之相同,详情见4.3内容 本文为我上课学习时的课堂笔记,是将当堂写的java文件中的注释整理后直接复制过来,并未做过多的修改,是上课跟随老师学习的第一观感,发出来既是作为自己的学习积累,也是作为一种纪念。 个人感觉学习Collection和Map时并无太大困难之处,能快速跟上老师速度并熟练敲出代码,但就是考试笔试时容易忘记个别方法名或者拼错,通过这次整理笔记也能对知识体系有更稳固的掌握,课余时间会闲下来多了解了解数据结构中的底层操作,算法这块也是我的薄弱,希望可以找个时间专攻,弥补一下。
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口。
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable{
//NavigableMap中的key用来保存set的值
private transient NavigableMap<E,Object> m;
//使用一个PRESENT作为Map集合的所有value。
private static final Object PRESENT = new Object();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
5 Map
5.1 Map集合存储元素特点及分类
5.2 Map的特性
5.3 Map接口中常用方法:
Map<Integer,String> map = new HashMap<>();
//健不允许重复
//健重复了,执行覆盖操作
map.put(1, "hello");
map.put(1, "world");
System.out.println(map);//{1=world}
String s = map.get(1);
System.out.println(s);//world
System.out.println(map.isEmpty());//false
map.clear();
System.out.println(map.isEmpty());//true
map.put(1, "hello");
map.put(2, "world");
System.out.println(map.size());//2
System.out.println(map.containsKey(1));//true
System.out.println(map.containsValue("world"));//true
map.remove(1);
System.out.println(map.containsKey(1));
5.4 关于Map遍历/迭代专题
Set mapKeys=map.keySet();
Iterator iter =mapKeys.iterator();
while(iter.hasNext()) {
Object key =iter.next();
System.out.println(key+":"map.get(key));
}
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for(Map.Entry<Integer, String> entry : entries){
System.out.println(entry.getKey()+":"+entry.getValue());
}
5.5 小知识
Map map = new HashMap();
map.put(null,null);
map.put(null,100);
System.out.println(map.size());//1
System.out.println(map.get(null));//100
5.6 HashMap
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
//HashMap底层实际上是一个数组(一维数组)
Node
//静态的内部类HashMap,Node
static class Node
final int hash;
final K key;
V value;
Node
}
}
查询也不需要都扫描,只需要部分扫描。
为什么统序?因为不- -定挂到哪个单向链表上。
不可重复是怎么保证的? equals 方法来保证HashMap集合的key不可重复。
如果key重复了, value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。.
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
纯单向链表。这种情况我们成为:散列分布不均匀。
什么是散列分布均勻?
假设有100个元素, 10个单向链表,那么每个单向链表上有10个节点,这是最好的,
是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为-维数组了,没有链表的概念了。
也是散列分布不均匀。
散列分布均匀需要你重导hashCode()方法时有一定的技巧。
k. hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
k. hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
如果o1和2的hash值相同,一定是放到同一个单向链表上。当然如果o1和2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。5.7 TreeMap
6 小结