一、集合和数组概念
在Java中,数组是存储某一类型的数据的容器,但是这个容器有局限性,因为定义后的数组长度不可变,超出长度后,再存放数据就会报错(例如:数组下标越界)。而且数组只能存放基本数据类型和对象
在开发过程中,大多时候数据长度是不确定的,这就需要有不定长的容器来存放数据,这就涉及到集合Collection,Java 中的集合Collection都采用了泛型实现,可以存入任何类型的对象数据,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。它以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,从而大大提高了软件的开发效率
二、集合分类
Java的集合类主要由两个接口派生而出: Collection和Map, Collection和Map是Java 集合框架的根接口,这两个接口又包含了一些子接口或实现类。
Java集合框架是一个用来代表和操纵集合的统一架构,包含如下内容:
接口:代表集合的抽象数据类型。例如: Collection、List、Set、Map 等。定义多个接口,便于以不同的方式操作集合对象
实现(类):接口的具体实现。例如:ArrayList、LinkedList、HashSet、HashMap。
算法:实现接口的对象里的方法中执行的计算,例如:Collections 工具类提供了对集合进行排序,遍历等多种算法实现。
Collection接口下有三个子接口,List,Queue,Set及最常用的三个类,ArrayList,LinkedList,HashSet。
Map接口下最常用的是HashMap。
1、Collection接口
Collection 是最基本的集合接口,是单列集合的顶层父类,一个独立元素的序列。一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。
1)List集合是一个有序的队列,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。
List的实现类有LinkedList、ArrayList、Vector、Stack。
2)Set集合是一个不允许有重复元素的集合。存储一组唯一,无序的对象。
Set的实现类有HashSet、TreeSet。
HashSet依赖于HashMap,他实际上是通过HashMap实现的;
TreeSet依赖 于TreeMap,他实际是通过TreeMap实现的。
2、Map接口
是双列集合的顶层父类接口,是一个映射接口,即key-Value键值对。允许使用键来查找对应的值,从某种意义上来说,他将数字和对象关联在一起。Map中的每一个元素包含"key"和"key对应的value"。
1)Map.Entry:描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。
Entry将键值对的对应关系封装成了对象。
即键值对对象,在遍历Map集合时,可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
相关方法
getKey()方法:获取Entry对象中的键
getValue()方法:获取Entry对象中的值
entrySet()方法:用于返回Map集合中所有的键值对(Entry)对象,以Set集合形式返回。
2)Map的实现类有:HashMap ,TreeMap。LinkedHashMap 继承自 HashMap 。
3、迭代器Iterator
1)Iterator通常被称为轻量级对象,创建它的代价很小,它是遍历集合的工具,通常通过Iterator迭代器来遍历集合。Collection依赖于Iterator,Collection的实现类都要实现iterator()函数,返回一个Iterator对象。
三、List接口
- ArrayList介绍
ArrayList是一个数组队列,也是一个动态数组,底层是Object数组,容量是自增长的,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。它继承与AbstractList。
ArrayList线程是非安全的,一般使用在单线程的情况下。要保证同步,可以使用:List list = Collections.synchronizedList(new ArrayList());
进行包装,默认容量为10.
ArrayList可以有null值
ArrayList集合特点:底层采用的是数组结构
ArrayList al=new ArrayList();//创建了一个长度为0的Object类型数组
al.add("abc");//底层会创建一个长度为10的Object数组 Object[] obj=new Object[10]
//obj[0]="abc"
//如果添加的元素的超过10个,底层会开辟一个1.5*10的长度的新数组
//把原数组中的元素拷贝到新数组,再把最后一个元素添加到新数组中
原数组:
a b c d e f g h k l
添加m:
a b c d e f g h k l m null null null null
常用方法:add
,addAll
,remove
,indexOf
,subList
,contains
,isEmpty
…
- LinkedList介绍
LinkedList 是一个继承于AbstractSequentialList的双向链表。实现了List、Deque、Cloneable、Java.io.serializable接口,它也可以被当作堆栈、队列或双端队列进行操作,。
LinkedList 是非线程安全的,集合中的元素允许为空,保存的元素为有序的,实现了List接口,则允许集合中的元素是可以重复的。经常用在增删操作较多而查询操作很少的情况下。
1)LinkedList中定义了两个属性
size:LinkedList对象中存储的元素个数。
header:LinkedList是基于链表实现的, header 就是链表的头结点 ,Entry是节点对应的对象。
Entry类中只定义了存储的元素、前一个元素、后一个元素,这就是双向链表的节点的定义,每个节点只知道自己的前一个节点和后一个节点。
2)LinkedList元素存储
LinkedList存储元素的方法有:add(E e)、addBefore(E e,Entry entry)、 add(int index,E e)、addFirst(E e)、 addLast(E e)等。
addBefore(E e,Entry entry)是私有方法,无法在外部程序中调用(可以通过反射调用)。
addBefore(E e,Entry entry)先通过Entry的构造方法创建e的节点newEntry(包含了将其下一个节点设置为entry,上一个节点设置为entry.previous的操作,相当于修改newEntry的“指针”),之后修改插入位置后newEntry的前一节点的next引用和后一节点的previous引用,使链表节点间的引用关系保持正确。之后修改和size大小和记录modCount,然后返回新插入的节点。
则有,addBefore(E e,Entry entry)实现在entry之前插入由e构造的新节点。
add(E element) 在header节点之前插入由element构造的新节点。
public void addFirst(E e) {
addBefore(e, header.next);
}
add(int index, E element) 调用了addBefore(E e,Entry entry)方法,只是entry节点由index的值决定
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
addFirst(E e) 实现在header元素的下一个元素之前插入。
addLast(E e) 实现在header节点前插入节点。
3)LinkedList元素读取
LinkedList元素读取的方法有:set(int index,E element) ,get(int Index) ,getFirst(),getLast()等。
set(int index,E element) 先获取指定索引的节点,之后保留原来的元素,然后用element进行替换,之后返回原来的元素
get(int Index) 用于获得指定索引位置的节点的元素。通过entry(int index)方法获取节点。遍历链表并获取节点,entry的方法是根据索引值,进行遍历。
getFirst() 返回链表的第一个节点的元素
getLast() 获取header节点的前一个节点的元素
三、Set接口
- Set接口的特点
Set集合不包含重复元素,无索引
Set集合取出元素的方式可以采用:迭代器、增强for。
Set集合有多个子类,常用的有HashSet、LinkedHashSet。
Set接口的实现类,HashSet (哈希表):无序集合,存储和取出的顺序不同,没有索引,不存储重复元素
- HashSet介绍
底层数据结构是哈希表(实际上是一个 HashMap 实例),允许使用 null 元素,唯一且无序,非线程安全.通过元素的hashcode和equals来保证元素的唯一性。如果元素的hashcode值相同,才会判断equals是否为true; 如果元素的hashcode的值不同,不会调用equals。
HashSet常用方法
public boolean contains(Object o) :如果set包含指定元素,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中所有元素的数组public Object[] toArray(Object[] a) :返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
public boolean add(Object o) :如果set中不存在指定元素,则向set加入
public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true
- LinkedHashSet介绍
LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高
- TreeSet介绍
TreeSet实现了SortedSet接口,顾名思义这是一种排序的Set集合,底层是用TreeMap实现的,本质上是一个红黑树原理。 TreeSet提供了一些额外的按排序位置访问元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
四、Map接口
- Map接口下的集合与Collection接口下的集合比较
Collection中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储。
Collection中的集合称为单列集合,Map中的集合称为双列集合。
Map中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找对所对应的值。
- Map常用方法
Object put(Object key,Object value):用来存放一个键-值对Map中
Object remove(Object key):根据key(键),移除键-值对,并将值返回
void putAll(Map mapping) :将另外一个Map中的元素存入当前的Map中
void clear() :清空当前Map中的元素
Object get(Object key) :根据key(键)取得对应的值
boolean containsKey(Object key) :判断Map中是否存在某键(key)
boolean containsValue(Object value):判断Map中是否存在某值(value)
public Set keySet() :返回所有的键(key),并使用Set容器存放
public Collection values() :返回所有的值(Value),并使用Collection存放
public Set entrySet() :返回一个实现 Map.Entry 接口的元素 Set
- HashMap和LinkedHashMap
HashMap 基于哈希表的 Map 接口的实现,存入顺序和输出顺序无关。存储键值对,键是唯一的,允许使用 null 值和 null 键,非同步(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同)。
interface Map{
interface Entry{//Entry是Map的一个内部接口
//由Map的子类的内部类实现
}
}
class HashMap{
static class Entry<K,V> implements Map.Entry<K,V> {//Entry对象指的就是该类的对象
final K key;
V value;
}
}
使用HashMap集合,存储自定义的对象
public class HashMapDemo {
public static void main(String[] args) {
function_1();
}
/*
* HashMap 存储自定义对象Person,作为键出现
* 键的对象,是Person类型,值是字符串
* 保证键的唯一性,存储到键的对象,重写hashCode equals
*/
public static void function_1(){
HashMap<Person, String> map = new HashMap<Person, String>();
map.put(new Person("a",20), "里约热内卢");
map.put(new Person("b",18), "索马里");
map.put(new Person("b",18), "索马里");
map.put(new Person("c",19), "百慕大");
for(Person key : map.keySet()){
String value = map.get(key);
System.out.println(key+"..."+value);
}
System.out.println("===================");
for(Map.Entry<Person, String> entry : map.entrySet()){
System.out.println(entry.getKey()+"..."+entry.getValue());
}
}
/*
* HashMap 存储自定义的对象Person,作为值出现
* 键的对象,是字符串,可以保证唯一性
*/
public static void function(){
HashMap<String, Person> map = new HashMap<String, Person>();
map.put("beijing", new Person("a",20));
map.put("tianjin", new Person("b",18));
map.put("shanghai", new Person("c",19));
for(String key : map.keySet()){
Person value = map.get(key);
System.out.println(key+"..."+value);
}
System.out.println("=================");
for(Map.Entry<String, Person> entry : map.entrySet()){
String key = entry.getKey();
Person value = entry.getValue();
System.out.println(key+"..."+value);
}
}
}
LinkedHashMap 继承自 HashMap 所以具有 HashMap 的所有特性。同时又实现了双向链表的特性,保留了元素插入顺序。
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String, String> link = new LinkedHashMap<String, String>();
link.put("1", "a");
link.put("13", "a");
link.put("15", "a");
link.put("17", "a");
System.out.println(link);
}
}
TreeMap则是对Map中的元素进行排序(根据键(Key)进行排序)。HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。所以在完成了所有的元素的存放后,再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。
- Map集合遍历方式
1、Map集合遍历方式keySet方法
1).获取Map集合中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键
2).遍历键的Set集合,得到每一个键
3).根据键利用get(key)去Map找所对应的值
public class MapDemo1 {
public static void main(String[] args) {
/*
* 1. 调用map集合的方法keySet,所有的键存储到Set集合中
* 2. 遍历Set集合,获取出Set集合中的所有元素 (Map中的键)
* 3. 调用map集合方法get,通过键获取到值
*/
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("a", 11);
map.put("b", 12);
map.put("c", 13);
map.put("d", 14);
//1. 调用map集合的方法keySet,所有的键存储到Set集合中
Set<String> set = map.keySet();
//2. 遍历Set集合,获取出Set集合中的所有元素 (Map中的键)
Iterator<String> it = set.iterator();
while(it.hasNext()){
//it.next返回是Set集合元素,也就是Map中的键
//3. 调用map集合方法get,通过键获取到值
String key = it.next();
Integer value = map.get(key);
System.out.println(key+"...."+value);
}
System.out.println("=======================");
for(String key : map.keySet()){
Integer value = map.get(key);
System.out.println(key+"...."+value);
}
}
}
2、Map集合遍历方式entrySet方法
entrySet方法,键值对映射关系获取
实现步骤:
1). 调用map集合方法entrySet()将集合中的映射关系对象,存储到Set集合Set<Entry <K,V> >
2). 迭代Set集合
3). 获取出的Set集合的元素,是映射关系对象
4). 通过映射关系对象方法 getKet, getValue获取键值对
public class MapDemo2 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer, String>();
map.put(1, "abc");
map.put(2, "bcd");
map.put(3, "cde");
//1. 调用map集合方法entrySet()将集合中的映射关系对象,存储到Set集合
Set<Map.Entry <Integer,String> > set = map.entrySet();
//2. 迭代Set集合
Iterator<Map.Entry <Integer,String> > it = set.iterator();
while(it.hasNext()){
// 3. 获取出的Set集合的元素,是映射关系对象
// it.next 获取的是什么对象,也是Map.Entry对象
Map.Entry<Integer, String> entry = it.next();
//4. 通过映射关系对象方法 getKet, getValue获取键值对
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"...."+value);
}
}
}
3、Map集合遍历方式增强for循环
实现步骤:
1. 调用map集合方法entrySet()将集合中的映射关系对象,存储到Set集合Set<Entry <K,V> >
2. 迭代Set集合
3. 获取出的Set集合的元素,是映射关系对象
4. 通过映射关系对象方法 getKet, getValue获取键值对
Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以了。
public class MapDemo2 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer, String>();
map.put(1, "abc");
map.put(2, "bcd");
map.put(3, "cde");
//1. 调用map集合方法entrySet()将集合中的映射关系对象,存储到Set集合
Set<Map.Entry <Integer,String> > set = map.entrySet();
//2. 迭代Set集合
Iterator<Map.Entry <Integer,String> > it = set.iterator();
while(it.hasNext()){
// 3. 获取出的Set集合的元素,是映射关系对象
// it.next 获取的是什么对象,也是Map.Entry对象
Map.Entry<Integer, String> entry = it.next();
//4. 通过映射关系对象方法 getKet, getValue获取键值对
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"...."+value);
}
System.out.println("=========================");
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println(entry.getKey()+"..."+entry.getValue());
}
}
}
五、Collections和Arrays工具类
- Collections
Collections提供对集合进行操作的一个包装器,实现对集合的查找、排序、替换、 线程安全化(将非同步的集合转换成同步的)等操作。
常用方法:
Collections.max(list);//返回list中字典顺序最大的元素。
int index = Collections.binarySearch(list,”zz”);//二分查找,返回角标,必须是有序的。
Collections.fill();//可以将list集合中的所有元素替换成指定元素。
Collections.repalceAll(list,”要被替换的”,”替换的值”);//可以将list集合中的指定元素替换成指定元素。
Collections.reverse(); 反转
Collections.reverseOrder(参数是比较器);//逆向反转排序。倒序。
Collections.shuffle(list);//随机对list中的元素进行位置的置换。
synchronizedXXX(XXX);非同步集合转成同步集合的方法。
- Arrays
Arrays用于操作数组对象的工具类,里面都是静态方法。
Arrays.asList:可以从 Array 转换成 List。可以作为其他集合类型构造器的参数。
Arrays.binarySearch:在一个已排序的或者其中一段中快速查找。
Arrays.copyOf:如果你想扩大数组容量又不想改变它的内容的时候可以使用这个方法。
Arrays.copyOfRange:可以复制整个数组或其中的一部分。
Arrays.deepEquals、Arrays.deepHashCode:Arrays.equals/hashCode的高级版本,支持子数组的操作。
Arrays.equals:如果你想要比较两个数组是否相等,应该调用这个方法而不是数组对象中的 equals方法(数组对象中没有重写equals()方法,所以这个方法之比较引用而不比较内容)。
Arrays.fill:用一个给定的值填充整个数组或其中的一部分。
Arrays.hashCode:用来根据数组的内容计算其哈希值(数组对象的hashCode()不可用)。
Arrays.sort:对整个数组或者数组的一部分进行排序。也可以使用此方法用给定的比较器对对象数组进行排序。
Arrays.toString:打印数组的内容。
六、面试常见问题总结
1、HashMap和Hashtable的区别?
相同点:
HashMap和Hashtable都是java的集合类,都可以用来存放java对象
不同点:
1)历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个现实。
2)同步性:Hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的,而HashMap则是异步的,因此HashMap中的对象并不是线程安全的,因为同步的要求会影响执行的效率,所以如果你不需要线程安全的结合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率,我们一般所编写的程序都是异步的,但如果是服务器端的代码除外。
3)值:HashMap可以让你将空值作为一个表的条目的key或value。Hashtable是不能放入空值(null)的。
2、ArrayList和Vector的区别?
相同点:ArrayList与Vector都是java的集合类,都是用来存放java对象 。
不同点:
1)同步性:Vector是同步的,这个类的一些方法保证了Vector中的对象的线程安全的,而ArrayList则是异步的,因此ArrayList中的对象并不 是线程安全的,因为同步要求会影响执行的效率,所以你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必 要的性能开销。
2)数据增长:从内部实现的机制来讲,ArrayList和Vector都是使用数组(Array)来控制集合中的对象,当你向两种类型中增加元素的时候,如果元素的数目超过了内部数组目前的长度他们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大,所以如果你要在集合中保存大量的数据,那么使用Vector有一些优势,因为你可以通过设置集合的初始大小来避免不必要的资源开销。
3)总结:
1)如果要求线程安全,使用Vector,Hashtable
2)如果不要求线程安全,使用ArrayList,LinkedList,HashMap
3)如果要求键值对,则使用HashMap,Hashtable
4)如果数据量很大,又要求线程安全考虑Vector
3、Arraylist 与 Linkedlist联系与区别?
1)、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2)、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3)、对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
4、HashMap与TreeMap联系与区别?
1)、 HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
2)、在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
在HashMap中,同样的值的map,顺序不同,equals时,false;
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。
七、小结
ArrayList: 元素单个,效率高,多用于查询
Vector: 元素单个,线程安全,多用于查询
LinkedList:元素单个,多用于插入和删除
HashMap: 元素成对,元素可为空
HashTable: 元素成对,线程安全,元素不可为空
本文部分截取以下文章:
https://www.cnblogs.com/admol/p/5127823.html
https://www.cnblogs.com/chenglc/p/8073049.html