Map接口
Map接口概述
y=f(x)
(x1,y1) (x2,y2),…
-
Map与Collection并列存在。用于保存具有映射关系的数据:key-value
-
Map 中的key 和value 都可以是任何引用类型的数据
-
Map 中的key 用Set来存放,不允许重复,即同一个Map 对象所对应的类,须重写hashCode()和equals()方法
-
常用String类作为Map的“键”
-
key 和value 之间存在单向一对一关系,即通过指定的key 总能找到唯一的、确定的value
-
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map 接口使用频率最高的实现类
Map接口:常用方法
Map map = new HashMap();
//Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
map.put("AA",11);//添加操作
map.put("BB",33);
map.put("CC",22);
map.put("AA",55);//修改操作
System.out.println(map);//{AA=55, BB=33, CC=22}
//void putAll(Map m):将m中的所有key-value对存放到当前map中
Map m1 = new HashMap();
m1.put("DD",44);
m1.put("EE",8);
map.putAll(m1);
System.out.println(map);//{AA=55, BB=33, CC=22, DD=44, EE=8}
//Object remove(Object key):移除指定key的key-value对,并返回value
Object cc = map.remove("CC");
Object zz = map.remove("ZZ");
System.out.println(cc);//22
System.out.println(zz);//null
System.out.println(map);//{AA=55, BB=33, DD=44, EE=8}
//void clear():清空当前map中的所有数据
m1.clear();//与 m1 = null; 不同
System.out.println(m1);//{}
System.out.println(m1.size());//0
//Object get(Object key):获取指定key对应的value
Object aa = map.get("AA");
System.out.println(aa);//55
//boolean containsKey(Object key):是否包含指定的key
boolean bbIsExist = map.containsKey("BB");
System.out.println(bbIsExist);//true
//boolean containsValue(Object value):是否包含指定的value
boolean isExist55 = map.containsValue(55);
System.out.println(isExist55);//true
//int size():返回map中key-value对的个数
int mapSize = map.size();
System.out.println(mapSize);//4
//boolean isEmpty():判断当前map是否为空
boolean mapIsempty = map.isEmpty();
boolean m1Isempty = m1.isEmpty();
System.out.println(mapIsempty);//false
System.out.println(m1Isempty);//ture
//m1 = null;
//System.out.println(m1.isEmpty());NullPointerException
//boolean equals(Object obj):判断当前map和参数对象obj是否相等
System.out.println(map.equals(m1));//false
//Set keySet():返回所有key构成的Set集合
Set keyset = map.keySet();
//遍历方式一:
for(Object o : keyset){
System.out.println(o);
}
//遍历方式二:
keyset.forEach(System.out::println);
Iterator iterator = keyset.iterator();
//遍历方式三:
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//Collection values():返回所有value构成的Collection集合
Collection values = map.values();
for (Object obj : values){
System.out.println(obj);
}
//Set entrySet():返回所有key-value对构成的Set集合
Set entrySet = map.entrySet();
//遍历所有的key——value
//方式一:entrySet()
Iterator ite1 = entrySet.iterator();
while(ite1.hasNext()){
Object o = ite1.next();
//entrySet()集合中的元素都是entry
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey()+"----->"+entry.getValue());
}
//方式二:
Set keyset1 = map.keySet();
Iterator itera = keyset1.iterator();
while(itera.hasNext()){
Object key = itera.next();
System.out.println(key + "--->" + map.get(key));
}
添加、删除、修改操作:
- Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
- void putAll(Map m):将m中的所有key-value对存放到当前map中
- Object remove(Object key):移除指定key的key-value对,并返回value
- void clear():清空当前map中的所有数据
元素查询的操作:
-
Object get(Object key):获取指定key对应的value
-
boolean containsKey(Object key):是否包含指定的key
-
boolean containsValue(Object value):是否包含指定的value
-
int size():返回map中key-value对的个数
-
boolean isEmpty():判断当前map是否为空
-
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
-
Set keySet():返回所有key构成的Set集合
-
Collection values():返回所有value构成的Collection集合
-
Set entrySet():返回所有key-value对构成的Set集合
Map map = new HashMap();
//map.put(..,..)省略
System.out.println("map的所有key:");
Set keys = map.keySet();// HashSet
for(Object key: keys) {
System.out.println(key + "->" + map.get(key));
}
System.out.println("map的所有的value:");
Collection values= map.values();
Iterator iter= values.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
System.out.println("map所有的映射关系:");
// 映射关系的类型是Map.Entry类型,它是Map接口的内部接口
Set mappings = map.entrySet();
for(Object mapping: mappings) {
Map.Entry entry= (Map.Entry) mapping;
System.out.println("key是:"+ entry.getKey() + ",value是:"+ entry.getValue());
}
Map实现类之一:HashMap
-
HashMap是Map 接口使用频率最高的实现类。
-
允许使用null键和null值,与HashSet一样,不保证映射的顺序。
-
所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
-
所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
-
一个key-value构成一个entry
-
所有的entry构成的集合是Set:无序的、不可重复的
-
HashMap 判断两个key 相等的标准是:两个key 通过equals() 方法返回true,hashCode值也相等。
-
HashMap判断两个value相等的标准是:两个value 通过equals() 方法返回true。
HashMap的存储结构
JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)
JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。
HashMap源码中的重要常量
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
MAXIMUM_CAPACITY :HashMap的最大支持容量,2^30
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树
UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size:HashMap中存储的键值对的数量
modCount:HashMap扩容和结构改变的次数。
threshold:扩容的临界值,=容量*填充因子
loadFactor:填充因子
HashMap的存储结构:JDK8之前
-
HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
-
每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。而且新添加的元素作为链表的head。
-
添加元素的过程:
向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果hash值不同,继续比较二者是否equals。如果返回值为true,则使用entry1的value去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。
- HashMap的扩容
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
- 那么HashMap什么时候进行扩容呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
HashMap的存储结构:JDK8
-
HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
-
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
-
那么HashMap什么时候进行扩容和树形化呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
- 关于映射关系的key是否可以修改?answer:不要修改
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
总结:JDK1.8相较于之前的变化:
1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
2.当首次调用map.put()时,再创建长度为16的数组
3.数组为Node类型,在jdk7中称为Entry类型
4.形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
5.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。
Map实现类之二:LinkedHashMap
-
LinkedHashMap是HashMap的子类
-
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
-
与LinkedHashSet类似,LinkedHashMap可以维护Map 的迭代顺序:迭代顺序与Key-Value 对的插入顺序一致
public void test2(){
Map map = new HashMap();
map.put(123,"AA");
map.put(456,"BB");
map.put(12,"CC");
System.out.println(map);//{456=BB, 123=AA, 12=CC}
map = new LinkedHashMap();
map.put(123,"AA");
map.put(456,"BB");
map.put(12,"CC");
System.out.println(map);//{123=AA, 456=BB, 12=CC}
}
HashMap中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
LinkedHashMap中的内部类:Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
Map实现类之三:TreeMap
-
TreeMap存储Key-Value 对时,需要根据key-value 对进行排序。TreeMap可以保证所有的Key-Value 对处于有序状态。
-
TreeSet底层使用红黑树结构存储数据
-
TreeMap的Key 的排序:
-
自然排序:TreeMap的所有的Key 必须实现Comparable 接口,而且所有的Key 应该是同一个类的对象,否则将会抛出ClasssCastException
-
定制排序:创建TreeMap时,传入一个Comparator 对象,该对象负责对TreeMap中的所有key 进行排序。此时不需要Map 的Key 实现Comparable 接口
-
-
TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
/*
5. TreeMap的使用
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要照key进行排序:自然排序 、定制排序
*/
@Test
public void test1(){
//自然排序 -------按照名字排序
TreeMap map = new TreeMap();
User u1 = new User("Tom",20);
User u2 = new User("Jerry",25);
User u3 = new User("Tom",18);
User u4 = new User("Jerry",19);
map.put(u1,98);
map.put(u2,97);
map.put(u3,95);
map.put(u4,98);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey() +" 得分:"+entry.getValue());
}
// User{name='Jerry', age=19} 得分:98
// User{name='Jerry', age=25} 得分:97
// User{name='Tom', age=18} 得分:95
// User{name='Tom', age=20} 得分:98
}
@Test
public void test2() {
//定制排序 -------按照年龄排序
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
if (u1.getAge() == u2.getAge()){
return u1.getName().compareTo(u2.getName());
}else{
return Integer.compare(u1.getAge(),u2.getAge());
}
}else{
throw new RuntimeException("参数有误");
}
}
});
User u1 = new User("Tom", 20);
User u2 = new User("Jerry", 25);
User u3 = new User("Tom", 18);
User u4 = new User("Jerry", 19);
map.put(u1, 98);
map.put(u2, 97);
map.put(u3, 95);
map.put(u4, 98);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey() + " 得分:" + entry.getValue());
}
// User{name='Tom', age=18} 得分:95
// User{name='Jerry', age=19} 得分:98
// User{name='Tom', age=20} 得分:98
// User{name='Jerry', age=25} 得分:97
}
}
Map实现类之四:Hashtable
-
Hashtable是个古老的Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
-
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
-
与HashMap不同,Hashtable不允许使用null 作为key 和value
-
与HashMap一样,Hashtable也不能保证其中Key-Value 对的顺序
-
Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
public void test1(){
Map map1 = new HashMap();
map1.put(null,01);//可以正常添加
System.out.println("-------------");
Map map2 = new Hashtable();
map2.put(null,01);//NullPointerException
}
Map实现类之五:Properties
-
Properties 类是Hashtable的子类,该对象用于处理属性文件
-
由于属性文件里的key、value 都是字符串类型,所以Properties 里的key 和value 都是字符串类型
-
存取数据时,建议使用setProperty(String key,Stringvalue)方法和getProperty(String key)方法
Properties pros= new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user= pros.getProperty("user");
System.out.println(user);
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesTest {
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties properties = new Properties();
fis = new FileInputStream("jdbc.properties");
properties.load(fis);//加载流对应的文件
String name = properties.getProperty("name");
String password = properties.getProperty("password");
System.out.println("名字:"+name+"\t密码:"+password);//名字:Tom 密码:123
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Collections工具类
-
Collections 是一个操作Set、List和Map 等集合的工具类
-
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
-
排序操作:(均为static方法)
- reverse(List):反转List 中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
- sort(List,Comparator):根据指定的Comparator 产生的顺序对List 集合元素进行排序
- swap(List,int,int):将指定list 集合中的i处元素和j 处元素进行交换
查找、替换
Collections常用方法
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection,Comparator):根据Comparator 指定的顺序,返回给定集合中的最大元素
-
Object min(Collection)
-
Object min(Collection,Comparator)
-
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
-
void copy(List dest,List src):将src中的内容复制到dest中
-
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
public void test(){
List list = new ArrayList();
list.add(11);
list.add(66);
list.add(22);
list.add(77);
list.add(55);
System.out.println(list);//[11, 66, 22, 77, 55]
//reverse(List):反转 List 中元素的顺序
List list1 = new ArrayList(list);
Collections.reverse(list1);
System.out.println(list1);//[55, 77, 22, 66, 11]
//shuffle(List):对 List 集合元素进行随机排序
List list2 = new ArrayList(list);
Collections.shuffle(list2);
System.out.println(list2);//[55, 66, 11, 77, 22]
//sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
List list3 = new ArrayList(list);
Collections.sort(list3);
System.out.println(list3);//[11, 22, 55, 66, 77]
//sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
List list4 = new ArrayList(list);
Collections.sort(list4, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int i1 = (int)o1;
int i2 = (int)o2;
return -Integer.compare(i1,i2);
}
});
System.out.println(list4);//[77, 66, 55, 22, 11]
//swap(List, int i, int j):将指定 list 集合中的 i 处元素和 j 处元素进行交换
List list5 = new ArrayList(list);
Collections.swap(list5,2,3);
System.out.println(list);//[11, 66, 22, 77, 55]
System.out.println(list5);//[11, 66, 77, 22, 55]
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
//Object min(Collection)
Object max = Collections.max(list);
Object min = Collections.min(list);
System.out.println(max);//77
System.out.println(min);//11
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//Object min(Collection,Comparator)
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 11);
System.out.println(frequency);//1
//void copy(List dest,List src):将src中的内容复制到dest中
System.out.println(list);//[11, 66, 22, 77, 55]
List list6 = Arrays.asList(new Object[list.size()]);
System.out.println(list6);//[null, null, null, null, null]
Collections.copy(list6,list);
System.out.println(list6);//[11, 66, 22, 77, 55]
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值
System.out.println(list6);
boolean b = Collections.replaceAll(list6, 11, 99);
System.out.println(b);//true
System.out.println(list6);//[99, 66, 22, 77, 55]
Collections常用方法:同步控制
Collections 类中提供了多个synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
/*
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,
我们可以将ArrayList、HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)
*/
List sycList = Collections.synchronizedList(list);
//sycList是线程安全的
补充:Enumeration
- Enumeration 接口是Iterator迭代器的“古老版本”
Enumeration stringEnum = new StringTokenizer("a-b*c-d-e-g", "-");
while(stringEnum.hasMoreElements()){
Object obj= stringEnum.nextElement();
System.out.println(obj);
}
数据结构
1.数据结构概述
数据结构(Data Structure是一门和计算机硬件与软件都密切相关的学科,它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用,涉及的内容包含:数据的逻辑关系、数据的存储结构、排序算法(Algorithm)、查找(或搜索)等。
2.数据结构与算法的理解
程序能否快速而高效地完成预定的任务,取决于是否选对了数据结构,而程序是否能清楚而正确地把问题解决,则取决于算法。
所以大家认为:“Algorithms + Data Structures = Programs”(出自:Pascal之父Nicklaus Wirth)
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。
3.数据结构的研究对象
3.1 数据间的逻辑结构
3.2 数据的存储结构:
线性表(顺序表、链表、栈、队列)
树
图
说明:习惯上把顺序表和链表看做基本数据结构(或真实数据结构)
习惯上把栈、队列、树、图看做抽象数据类型,简称ADT