一、集合与数组
1、 集合与数组存储数据概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)。
2、数组存储的特点
一旦初始化以后,其长度就确定了。 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
比如:
String[] arr0; int[] arr1; Object[] arr2;
3、数组存储的弊端
- 一旦初始化以后,其长度就不可修改。
- 数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
4、集合存储的优点
解决数组存储数据方面的弊端。
5、集合的分类
Java集合可分为Collection和Map两种体系
- Collection接口:单列数据,定义了存取一组对象的方法的集合。
- Map接口:双列数据,保存具有映射关系“key-value对”的集合。
二、Collection接口
1、简介
- JDK不提供Collection接口的任何直接实现,而是提供更具体的子接口实现类(如:Set和List)。
- Collection接口是List、Set、Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List集合和 Queue集合。
2、Collection接口常用方法
01、add(Object obj); --添加一个元素
02、addAll(Collection coll); --添加一个集合
03、int size(); --获取有效元素的个数
04、void clear(); --清空集合
05、boolean isEmpty(); --判断是否为空,集合是否包含某个元素
06、boolean contains(Object obj); --判断是否包含某一个元素(调用元素的equals方法来比较的)
07、boolean containsAll(Collection c); --判断是否包含某一个集合元素(也是调用元素的equals方法来比较的。 )
08、boolean remove(Object obj); --删除一个元素;通过元素的equals方法判断是否是要删除的元素。只会删除找到的第一个元素。
09、boolean removeAll(Collection coll); --删除该集合中与coll中相同得值,
10、boolean retainAll(Collection c); --把当前集合中的元素和c集合中相同的元素获取出来重新赋值给该集合,不影响c集合。
11、boolean equals(Object obj); --判断集合是否相等。
12、Object [] toArray(); --转换成对象数组。
13、int hashCode(); --获取集合对象的哈希值。
14、Iterator<E> iterator(); --返回迭代器对象,用于集合遍历。
3、Collection集合与数组间的转换
- 集合转数组
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.toArray();
}
- 数组转集合
public static void main(String[] args) {
String[] s = {"AA", "BB", "CC"};
List<String> list = Arrays.asList(s);
}
4、遍历集合
4.1、遍历Collection的3种方式
- 迭代器Iterator
- forEach循环(或增强for循环)
- Stream流
4.2、案例
- 迭代器Iterator :主要用于遍历 Collection 集合中的元素。 GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
public static void main(String[] args) {
Collection<String> list = Arrays.asList();
Iterator<String> iterator = list.iterator();
//hasNext():判断是否还下一个元素
while (iterator.hasNext()) {
//next():①指针下移 ②下移以后,将集合位置上的元素返回
System.out.println(iterator.next());
}
}
- forEach循环(或增强for循环)
public static void main(String[] args) {
//1.forEach
Collection<String> list = Arrays.asList();
list.forEach(s-> System.out.println());
//2.for
for (String s : list) {
System.out.println(s);
}
}
- Stream流
public static void main(String[] args) {
Collection<String> list = Arrays.asList();
list.stream().forEach(l -> System.out.println(l));
}
4.3、iterator中remove()方法的使用
- 内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove();
- 在未调用next()或者在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
示例
public static void main(String[] args) {
Collection<String> list = Arrays.asList("1", "2", "3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
// iterator.remove();//1、在未调用next()前调用会报错
Object obj = iterator.next();
if ("Tom".equals(obj)) {
iterator.remove();
// iterator.remove();//2、连续调用两次也会报错。
}
}
}
5、Collection子接口:List接口
5.1、存储的数据特点
- 存储有序的、可重复的数据。
- 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组。
- List容器中的元素都对应一个整数型的序号索引,记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK AP中List接口的实现类常用的有:ArrayList、LinkedList和 Vector。
5.2、常用实现类
5.2.1、ArrayList
作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储,默认情况下,扩容为原来的1.5倍,同时将原有的数组数据复制到新的数组中。ArrayList是List接口的典型实现类、主要实现类,用的也是最多的。可以存储null值。
问:Array Listi的JDK 1.8之前与之后的实现区别?
JDK 1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组。
JDK 1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组。
5.2.2、LinkedList
对于频繁的插入、删除操作,使用此类效率比ArrayList高;因为底层使用双向链表存储。双向链表,内部没有声明数组,而是定义了Node类型的 frst 和 last,用于记录首末元素。同时,定义内部类Node,作为 Linkedlist中保存数据的基本结构。Node除了保存数据,还定义了两个变量 :prev 变量记录前一个元素的位置, next 变量记录下一个元素的位置。
5.2.2、Vector
• Vector是一个古老的集合,JDK 1.0就有了。大多数操作与ArrayList相同,区别在于Vector是线程安全的。
• 在各种List中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免选择使用。
• JDK 7.0和JDK 8.0中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
5.3、面试题
• ArrayList和 Linkedlist的区别
二者都线程不安全,相比线程安全的 Vector,ArrayList执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,Linkedlist基于链表的数据结构。对于随机访问get和set,ArrayList绝对优于Linkedlist,因为Linkedlist要移动指针。对于新增和删除操作add(特指插入)和 remove,Linkedlist比较占优势,因为 ArrayList要移动数据。
• ArrayList和 Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比 ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack.
6、Collection子接口:Set接口
- Set接口是Collection的子接口,set接口没有提供额外的方法;
- Set集合不允许存储相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。(多用于过滤操作,去掉重复数据)
- Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法。
- 用于存放无序的、不可重复的元素;无序性不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的;不可重复性:保证添加的元素照equals()判断时,不能返回true。即:相同的元素只能添加一个。
- 向Set中添加的数据,其所在的类一定要重写hashCode()和equals();主要指:HashSet、LinkedHashSet。
6.1、添加元素过程
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
• 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
• 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
• 如果hash值不相同,则元素a添加成功。—>情况2
• 如果hash值相同,进而需要调用元素a所在类的equals()方法:
• equals()返回true,元素a添加失败
• equals()返回false,则元素a添加成功。—>情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
6.2、常用实现类
6.2.1、HashSet
• Hashset是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。
• HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
• HashSet不能保证元素的排列顺序,线程是不安全的,集合元素可以是nul
• HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
6.2.2、LinkedHashSet
• LinkedhashSet是HashSet的子类
• LinkedhashSet根据元素的hashCode值来决定元素的存储位置但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
• LinkedhashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
• LinkedhashSet不允许集合元素重复。
6.2.3、TreeSet
• Treeset是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
• TreeSet底层使用红黑树结构存储数据
• 向TreeSet中添加的数据,必须是相同类型的对象,不能即是int又是String,添加对象也是一样。
• 不能存储null值。
6.2.3.1、TreeSet排序方式:
• TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
• 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回0.不再是equals().
• 定制排序中,比较两个对象是否相同的标准为:compare() 返回0.不再是equals().
方式一:自然排序
自然排序 TreeSet会调用集合元素的compareTo(object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口;实现Comparable接口的类必须实现compareTo(Object obj)方法,对象是通过该方法的返回值来比较大小;向TreeSet中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
方式二:定制排序
通过 实现Comparator接口并且需要重写 compare(T o1,T o2)方法。 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
public static void main(String[] args) {
Comparator comparator= 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;
return Integer.compare(u1.getAge(), u2.getAge());
} else {
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
//comparator实例作为形参传递给TreeSet的构造器
TreeSet set = new TreeSet(comparator);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 2));
set.add(new User("Mike", 65));
set.add(new User("Mary", 33));
set.add(new User("Jack", 33));
set.add(new User("Jack", 56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
三、Map接口
• 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接口使用频率最高的实现类
1、HashMap
• HashMap是Map接口使用频率最高的实现类。
• 允许使用null键和null值。
• HashMap判断两个key相等的标准是:两个key通过equals()方法判断,如果返回true,则hashCode值也相等。
• 线程不安全的,效率高;
• 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
• 一个key-value构成一个entry,所有的entry构成的集合是Set:无序的、不可重复的。所以遍历HashMap需要先map.entrySet()才能遍历。
1.1、HashMap实现原理
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
1.2、HashMap数据结构
a、JDK 7.0及以前的版本:HashMap是数组+链表结构(地址链表法)
b、在JDK 8.0中,存储结构=数组+链表+红黑树的组合;
1.3、jdk1.7 及以前
a、添加元素的过程
HashMap map = new HashMap()
//在实例化以后,底层创建了长度是16的一维数组Entry[] table。
map.put(key1,value1)
- 首先,调用key1所在类的hashCode()计算key1存储位置哈希值,此哈希值经过某种算法计算以后,得到在Entry[]数组中的存放位置。
- 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
- 如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表形式存在);那么把key1的值和已经存在的一个或多个数据的值用equals()方法进行比较。
- 如果key1的值与已经存在的数据的值都不相同,此时key1-value1以链表形式添加成功。----情况2
- 如果key1的值和已经存在的某一个数据(key2-value2)的值相同,那么用key1需要新添加的值替换原来key2的值。 ----情况3
b、HashMap的扩容
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对 HashMap的数组进行扩容,而在HashMap数组扩容之后,原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是 resize。
c、HashMap扩容机制
当HashMap中的元素个数超过阈值(threshold) = 负载因子(loadFactor) x 数组容量(capacity),就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_ FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组容量(DEFAULT INITIAL CAPACITY)为16,那么当 HashMap中元素个数超过16 x 0.75=12(这个值就是代码中的 threshold值,也叫做阈值)的时候,就把数组的大小扩展为2 * 16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
1.4、HashMap在JDK 8.0及以后
a、 HashMap在JDK 8.0中,存储结构=数组+链表+红黑树的组合;
JDK 8.0底层的数组是:Node[];而非Entry[]
b、添加元素的过程
当实例化一个HashMap时,会初始化 initialCapacity(初始容量16)和loadFactor(加载因子),在put第一对映射关系时,系统会创建一个长度为 initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”( bucket),每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。每个 桶中存储一个元素,即一个Node对象,但每一个Noe对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链表。也可能是一个一个 TreeNode对象, 当Node链表的对象个数如果达到了8个,并且桶的长度大于64,那么这个链表会变成红黑树,当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
2、LinkedHashMap
• LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap
• 在遍历map元素时,可以照添加的顺序实现遍历。原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
• 对于频繁的遍历操作,此类执行效率低于HashMap。
3、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.
• TreeMap的key不能存null值。
4、Hashtable
• Hashtable是个古老的Map实现类,JDK1.0就提供了。不同于 HashMap,Hashtable是线程安全的.
• Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用
• 与HashMap.不同,HashTable不允许使用null作为key和value.
5、Properties
• Properties类是Hashtable的子类,该对象用于处理属性文件
• 由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型
• 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("E://jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6、 常用方法
添加、删除、修改操作
• 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 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集合
四、Collections工具类的使用
1、作用
Collections是一个操作Set、Lit和Map等集合的工具类;Collections中提供了一系列静态的方法对集合元素进行排序、査询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
2.常用方法
排序操作
• reverse(List); //反转 List 中元素的顺序
• shuffle(List); //对 List 集合元素进行随机排序
• sort(List); //根据元素的自然顺序对指定 List 集合元素升序排序
• sort(List,Comparator); //根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
• swap(List,int i, int j); //将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换
• Object max(Collection);//根据元素的自然顺序,返回给定集合中的最大元素
• Object max(Collection,Comparator);//根据 Comparator 指定的顺序,返回给定集合中的最大元素
• Object min(Collection);//根据元素的自然顺序,返回给定集合中的最小元素
• Object min(Collection,Comparator);//根据 Comparator 指定的顺序,返回给定集合中的最小元素
• int frequency(Collection,Object);//返回指定集合中指定元素的出现次数
• void copy(List dest,List src);//将src中的内容复制到dest中
• boolean replaceAll(List list,Object oldVal,Object newVal);//使用新值替换
3、同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
示例
@Test
public void test3() {
List list = new ArrayList();
list.add(123);
list.add(123);
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
System.out.println(list);//[123, 43, 765, -97, 0]
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
System.out.println(list1);//[123, 123, 123, 43, 765, -97, 0]
}
面试题:
所以集合都能存储null值吗?
1、Vector、Arraylist、LinkedList可以存储多个null.
2、HashSet、Linkedset可以存储一个null. TreeSet不能存储null.
3、HashMap 、LinkedHashMap key与value均可以为null. treemap key不可以为null,value可以为null.
4、HashTable、ConcurrentHashMap key与value均不能为null.