集合与数组
- 数组
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须是同一类型的元素
- 数组的增加/删除元素操作比较麻烦
- 集合
- 可以动态保存任意多个对象
- 提供了一系列方便操作对象的方法,使用集合添加/删除元素更加简洁明了
- Collection接口有两个重要的子接口(List、Set),它们的实现子类都是单列集合
- Map接口的实现子类都是双列集合 K-V
Collection接口
Collection接口实现类的特点
- Collection实现子类可以存放多个元素,每个元素可以是Objcet
- 有些Collection的实现类可以存放重复的元素,有些则不可以
- 有些Collection的实现类(List)是有序(存放和取出顺序一致)的,有些(Set)则不是
- Collection接口没有直接的实现子类,是通过它的子接口List和Set来实现的
-
常用方法
public static void main(String[] args) { List list = new ArrayList(); //add 添加单个元素 list.add("jack"); list.add(10);//list.add(new Integer(10)) list.add(true); System.out.println("list="+list); //remove 删除指定元素 list.remove(0); list.remove(true); System.out.println("list="+list); //contains 判断元素是否存在 System.out.println(list.contains(10)); //size 获取元素个数 System.out.println(list.size()); //isEmpty 判断是否为空 System.out.println(list.isEmpty()); //clear 清空 list.clear(); System.out.println("list="+list); //addAll 添加多个元素 ArrayList arrayList = new ArrayList(); arrayList.add("歪比歪比"); arrayList.add("歪比巴卜"); list.addAll(arrayList); System.out.println("list="+list); //containsAll 判断多个元素是否存在 System.out.println(list.containsAll(arrayList)); //removeAll 删除多个元素 list.add("玛卡巴卡"); list.removeAll(arrayList); System.out.println("list="+list); }
Collection接口遍历元素方式1 Iterator(迭代器)
- Iterator对象成为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator仅用于遍历集合,Iterator本身并不存放对象
- next()作用:1.下移 2.将下移后集合位置上的元素返回
public class CollectionIterator {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.5));
col.add(new Book("小李飞刀","古龙",5.1));
col.add(new Book("红楼梦","曹雪芹",30.2));
//System.out.println("col=" + col);
//希望能够遍历 col集合
//1.先得到 col 对应的迭代器
Iterator iterator = col.iterator();
//2.使用while循环遍历
//ctrl + j 显示所有快捷键
//idea 使用输入itit可快捷生成该循环
while (iterator.hasNext()){//判断后面是否还有数据
//返回下一个元素,类型是Object
Object next = iterator.next();
System.out.println("next=" + next);
}
//3.当退出while循环后,iterator迭代器指向最后一个元素
//4.如果希望再次遍历,需要重置迭代器
iterator = col.iterator();
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
Collection接口遍历元素方式2 增强for循环
- 增强for循环可以替代iterator迭代器;增强for循环就是简化版iterator,本质一样,只能用于遍历集合或数组
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.5));
col.add(new Book("小李飞刀","古龙",5.1));
col.add(new Book("红楼梦","曹雪芹",30.2));
//增强for底层仍是迭代器
//idea快捷键 I
for (Object book : col){//不加泛型默认Object
System.out.println("book=" + col);
}
}
List接口
-
List接口基本介绍
-
List接口是Collection接口的子接口
-
List集合类中元素是有序的(即添加顺序和取出顺序一致)、且可重复
-
List集合中的每个元素都有其对应的顺序索引
public static void main(String[] args) { //1.List集合类中元素是有序的(即添加顺序和取出顺序一致)、且可重复 List list = new ArrayList(); list.add("jack"); list.add("tom"); list.add("mary"); list.add("hsp"); list.add("tom"); System.out.println("list="+list); //List集合中的每个元素都有其对应的顺序索引 System.out.println(list.get(3)); }
-
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
-
List常用接口:ArrayList、LinkedList、Vector
-
-
List常用方法
public static void main(String[] args) { List list = new ArrayList(); list.add("歪比歪比"); list.add("歪比巴卜"); //插入单个元素 list.add(1,"DCBC"); System.out.println("list="+list); //插入多个元素 List list2 = new ArrayList(); list2.add("jack"); list2.add("tom"); list.addAll(1,list2); System.out.println("list="+list); //返回某个元素在集合中首次出现的位置 System.out.println(list.indexOf("tom")); //返回某个元素在集合中最后出现的位置 list.add("tom"); System.out.println(list.lastIndexOf("tom")); //删除指定位置的元素并返回此元素 System.out.println(list.remove(0)); //设置指定位置的元素 list.set(1,"mary"); System.out.println("list="+list); //返回从起始位置到末尾位置的子集合 前闭后开 List list3 = list.subList(0,3); System.out.println("list="+list); System.out.println("list3="+list3); }
List接口实现子类
ArrayList
-
ArrayList注意事项
- ArrayList可以加入一个或多个null
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,但ArrayList线程不安全(执行效率高),在多线程中不建议使用ArrayList
-
ArrayList扩容机制
-
ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;//被transient 修饰的属性不会被序列化
-
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加时扩容elementData为10;如果需要再次扩容,则扩容elementData为原来的1.5倍(新容量等于旧容量加上旧容量的一半)
-
如果使用的是指定大小的构造器,则初始elementData容量为指定大小;如果需要扩容,则扩容elementData为原来的1.5倍
-
Vector
-
Vector注意事项
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized。在开发中,需要线程同步安全时,考虑使用Vector
-
Vector扩容机制
底层结构 线程安全(同步)效率 扩容倍数 ArrayList 可变数组 不安全,效率高 无参:第一次10;从第二次开始按1.5倍扩容
有参:1.5倍扩容Vector 可变数组 安全,效率不高 无参:第一次10;从第二次开始按2倍扩容
有参:2倍扩容
LinkedList
- LinkedList注意事项
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
- LinkedList底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象)里面又维护了pre、next、item三个属性,其中通过pre指向前一个节点,通过next指向后一个节点,最终实现双向链表
- LinkedList的元素的添加和删除不是通过数组完成的,相对来说效率较高
ArrayList和LinkedList比较
底层结构 | 增删效率 | 查改的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低(数组扩容) | 较高 |
LinkedList | 双向链表 | 较高(链表追加) | 较低 |
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下选择ArrayList
Set接口
- Set接口基本介绍
- 添加和取出顺序不一致,没有索引
- 不允许重复元素,所以最多有一个null
Set接口实现子类
HashSet
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,每当数组元素达到临界值就进行扩容(数组大小乘以2),临界值是数组当前大小*加载因子(默认为0.75)
- 在java8中,如果一条链表元素到达TREEIFY_THRESHOLD(默认为8)并且table的大小>=MIN_TREEIFY_CAPACITY(默认为64)就进行树化(红黑树),否则扩容
- 不能有重复元素/对象,可以存放null
LinkedHashSet
- LinkedHashSet继承了HashSet,是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组table+双向链表
- LinkedHashSet根据元素的hashCode值决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
- 不允许添加重复元素
TreeSet
- 使用无参构造器时取出无序
- 可以使用有参构造器传入一个比较器来自定义排序规则,该比较器在进行添加操作时被调用
- 比较器返回结果为0时不进行添加操作
public static void main(String[] args) {
//当使用无参时,取出无序
//TreeSet treeSet = new TreeSet();
//使用有参时可以传入一个比较器(匿名内部类)并自定义排序规则
TreeSet treeSet = new TreeSet(new Comparator() {//在进行添加操作时被调用
@Override
public int compare(Object o1, Object o2) {//定义比较规则
//return ((String) o1).compareTo((String) o2);//按字符串从小到大排序
return ((String) o1).length() - ((String)o2).length();//按字符串长度排序
}
});
//注意:compare返回结果为0时则不进行添加操作
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("a");
treeSet.add("2");
System.out.println("treeSet = " + treeSet);
}
Map接口
jdk1.8
-
特点
- Map用于保存具有映射关系的数据:key-value
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,若出现重复则会替换之前的值;Map中的value可以重复
- Map中的key和value可以为null,为null的key只能有一个
- 常用String类作为Map的key
- 一对k-v是放在一个HashMap$Node中的,Node实现了Entry接口
-
常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键取值
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清除键值对
- containsKey:查找键是否存在
- keySet:获取所有键
- entrySet:获取所有关系k-v
- values:获取所有值
public static void main(String[] args) { Map map = new HashMap(); map.put("n1","jack"); map.put("n2","jack"); map.put("n3","tom"); map.put(null,"lucy"); map.put("n4","joe"); System.out.println("map=" + map); map.remove("n2"); System.out.println("map=" + map); Object val = map.get("n1"); System.out.println("val=" + val); }
-
遍历方法
public static void main(String[] args) { Map map = new HashMap(); map.put("n1","jack"); map.put("n2","jack"); map.put("n3","tom"); map.put(null,"lucy"); map.put("n4","joe"); //第一组:先取出所有key,再根据key取出对应的value Set keySet = map.keySet(); //1.增强for System.out.println("-----1-----"); for (Object key :keySet) { System.out.println(key + "-" + map.get(key)); } //2.迭代器 System.out.println("-----2-----"); Iterator iterator = keySet.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key + "-" + map.get(key)); } //第二组:把所有value取出 Collection values = map.values(); //可用collection使用的遍历方法 //第三组:通过EntrySet获取k-v Set entrySet = map.entrySet();//EntrySet<Entry<K,V>> //1.增强for System.out.println("-----3-----"); for (Object entry :entrySet) { //将entry转成Map.Entry Map.Entry m= (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); } //2.迭代器 System.out.println("-----4-----"); Iterator iterator1 = entrySet.iterator(); while (iterator1.hasNext()) { Object next = iterator1.next();//next实现类型为HashMap$Node //向下转型 Map.Entry m = (Map.Entry) next; System.out.println(m.getKey() + "-" + m.getValue()); }
Map接口实现子类
Map接口常用实现类:HashMap、Hashtable和properties
HashMap
-
特点
- HashMap以 key-value 对的方式来存储数据(HashMap$Node类型)
- key不能重复,值可以重复,key和value均可使用null
- 如果添加相同的key则会覆盖原来的键值对
- 不保证映射顺序,因为底层是以hash表的方式来存储的
- HashMap没有实现同步,因此线程不安全
- table数组中的节点HashMap N o d e 实 现 了 M a p Node实现了Map Node实现了MapEntry<K,V>
-
扩容机制
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加k-v时,通过key的哈希值得到再table的索引,然后判断该索引处是否有元素,如果没有直接添加。若该索引处有元素,继续判断该元素的key是否和准备加入的元素的key相等,如果相等则直接替换val。如果不相等则判断是树结构还是链表结构再进行添加;如果添加时发现容量不够则需要扩容
- 第1次添加时扩容table容量为16,临界值(threshold)为12,计算方法为当前容量乘以加载因子;以后再扩容时需要扩容table容量为原来的2倍
- Java8中,如果一条链表元素超过TREEIFY_THRESHOLD(默认为8)并且table的大小>=MIN_TREEIFY_CAPACITY(默认为64)就进行树化(红黑树),否则扩容
Hashtable
- 特点
- 存放的元素是键值对
- hashtable的键和值都不能为null
- 线程安全
- 数组初始化大小为11
properties
-
特点
- 继承Hashtable并实现了Map接口
-
常用方法
public static void main(String[] args) { Properties properties = new Properties(); properties.put("jack",10); properties.put("tom",11); properties.put("lucy",12); properties.put("jerry",13); System.out.println("properties = " + properties); //通过k获取值 System.out.println(properties.get("jack")); System.out.println(properties.getProperty("tom")); //删除 properties.remove("jack"); System.out.println("properties = " + properties); //修改 properties.put("tom",50); System.out.println("properties = " + properties); }
TreeMap
与TreeSet类似
- 使用无参构造器时取出无序
- 可以使用有参构造器传入一个比较器来自定义比较规则(或排序规则),该比较器在进行添加操作时被调用
- 比较器返回结果为0时不进行添加操作而不是替换
public static void main(String[] args) {
//使用无参构造器创建TreeMap是无序的(也没有排序)
//TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).compareTo((String) o2);//按k(String)大小排序
}
});
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("lucy","露西");
treeMap.put("smith","斯密斯");
System.out.println("treemap=" + treeMap);
}
集合选型规则
- 先判断存储的类型
- 若是一组对象(单列):Collection接口
- 允许重复:List
- 增删多:LinkedList(底层维护了一个双向链表)
- 改查多:ArrayList(底层维护了Object类型的可变数组)
- 不允许重复:Set
- 无序:HashSet(底层是HashMap,维护了一个哈希表)
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet(维护数组+双向链表)
- 允许重复:List
- 若是一组键值对:Map
- 键无序:HashMap(底层是哈希表)
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件:Properties
Collections工具类
-
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集合中两处元素进行交换
-
查找、替换操作
- max(Collection):根据元素的自然顺序返回集合中的最大元素
- max(Collection,Comparator):根据比较器指定的顺序返回集合中最大的元素
- min(Collection):根据元素的自然顺序返回集合中的最小元素
- min(Collection,Comparator):根据比较器指定的顺序返回集合中最小的元素
- frequency(Collection,Object):返回集合中指定元素的出现次数
- copy(List dest,List src):将src中的内容复制到dest中,两个数组大小需一致
- replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("lucy");
list.add("smith");
Collections.reverse(list);
System.out.println("反转");
System.out.println("list = " + list);
Collections.shuffle(list);
System.out.println("随机排序");
System.out.println("list = " + list);
Collections.sort(list);
System.out.println("自然排序后");
System.out.println("list = " + list);
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("按字符串长度大小排序");
System.out.println("list = " + list);
Collections.swap(list,0,1);
System.out.println("交换元素位置");
System.out.println("list = " + list);
System.out.println("最大元素:" + Collections.max(list));
ArrayList dest = new ArrayList();
for (int i =0; i < list.size(); i++){
dest.add("");
}//拷贝前需先将目的数组大小与源数组大小一致,否则会抛出异常
Collections.copy(dest,list);
System.out.println("dest = " + dest);
}