集合
集合与数组的对比
1、集合、数组都是对多个数据进行存储操作的结构,简称Java 容器。
说明:此时的存储,主要是指内存层面,不涉及持久化的存储(.txt,.jpg等)
2.1、数组在存储多个数据方面的特点
>一旦初始化以后,其长度就确定了.
>数组一且定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。 比 如: String[] arr;int[] arr1;object[] arr2;
2.2、数组在存储多个数据方面的缺点:
>一旦初始化以后,其长度就不可修改
>数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
>获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
>数组存储数据的特点:有序、可重复。对于无序,不可重复的需求,不能满足。
集合框架
|-----Collection接口: 单列集合,用来存储一个一个的对象
|------List接口: 存储有序的、可重复的数据。有索引值|-----实现类ArrayList、 Linkedlist. Vector
|-------Set接口:存储无序的、不可重复的数据 没有索引值|----实现类HashSet、L inkedHashSet. TreeSet
|-----Map接口:双列集合,用来存储一对(key - value) 对的数据
|------实现类HashMap、LinkedHashMap、 TreeMap、 Hashtable、Properties|
Collection接口
Collection常用方法
1、add(Object obj):添加元素
2、addAll(Collection b):添加集合b中的所有元素
3、int size():获取集合中有效元素的个数
4、void clear():清空集合
5、boolean isEmpty():判断集合是否为空
下列6个方法调用了Object中的equals方法来比较数据,对于自定义数据,如果要比较数据值,要重写equals方法。
1、boolean contions(Object obj):查找当前集合中是否包含元素obj
2、boolean contionsAll(Collection b):查找当前集合中是否包含集合b的所有元素
3、boolean remove(Object obj):只移除找到的第一个匹配元素
4、boolean removeAll(Collection b):在当前集合中移除集合b的所有数据
5、retainsAll(Collection b):获取当前集合和集合b的交集,并返回给当前集合,不影响集合b
6、equals(Collection b):判断当前集合和集合b是否相同,注意每个数据的顺序也必须相同
equals(Object obj)
7、hashCode():返回当前对象的hash值
8、toArray():集合转换为数组
将数组转换为集合:调用Arrays的静态方法:List asList(Object[ ] obj)
9、iterator()方法:用于生成Iterator接口实例。
数组和集合的转换
toArray():集合转换为数组
数组转换为集合:Arrays的静态方法asList(Object[ ] obj)
注意:插入的是基本数据类型数组的话,List会将其当作一个元素。例如:
List list= Arrays.asList(new int[]{1, 2, 3}); System.out.println(list.size()); //1
正确做法:
将基本数据类型改为包装类 List list= Arrays.asList(new Integer[]{1, 2, 3}); 或者不用大括号框,直接放多个数据 List list= Arrays.asList(1, 2, 3);
Iterator接口
对象称为迭代器,主要用于遍历Collection集合中的元素,为容器而生
①、实例化:Iterative iterator=集合.iterator(); //没生成一个迭代器,迭代器指针默认的都在-1处
②、next()方法: iterator.next();指针跳转到下一个元素,先跳转再返回值,跳转后指针不可逆,如果需要再次遍历该集合,重新生成一个迭代器
③、hasNext()方法:判断是否有下一个元素,有的话返回true,搭配next使用防止异常
例:
List arr= Arrays.asList(123,456); System.out.println(arr.size());//2 Iterator iterator=arr.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); }
如果代码是这样
while(iterator.next()!=null){ System.out.println(iterator.next()); } 那就相当于跳转了两次才输出一个,并且当指针移动到没有元素的地方调用next()会出现异常
④、remove():通过迭代器方法移除集合中迭代器指针指向的位置,同一位置不可以重复移除,否则报IllegalStateException
jdk5.0新增foreach循环
用于遍历集合和数组,内部调用了迭代器
遍历集合的格式
public static void main(String[] args) {
List arr= Arrays.asList(123,456,"Sr");
//for(集合中元素类型 局部变量 :集合对象)
for(Object obj:arr){ //将arr[i]赋值给obj,对obj进行操作
System.out.println(obj);
}
}
/*
123
456
Sr
*/
遍历数组的格式
public static void main(String[] args) {
int[] arr= new int[]{1,23,4,55};
//for(数组中元素类型 局部变量 :数组对象)
for(int i:arr){ //这是将arr[i]的值赋值给i,在对i进行操作
System.out.println(i);
}
}
/*输出
1
23
4
55
*/
Collection子接口一:List
|------List接口: 存储有序的、可重复的数据。有索引值
|-----实现类ArrayList(jdk1.2):List接口的主要实现类。线程不安全,查询效率高, 底层使用Object[ ] elementDate存储
|-----LinkedList(jdk1.2):底层使用双向链表存储,对于频繁插入删除操作,使用该实现类效率比ArraysList高
|-----Vector(jdk1.0):List接口的古老实现类,线程安全,效率低,,底层使用Object[ ] elementDate存储
方法:
增:add(Object obj)
删:remove(Object obj)、remove(int index)
改:set(int index,Object obj)
查:get(int index)
插:add(int index,Object obj)
大小:size()
遍历:iterator、foreach、for
1、比较ArrayList、 LinkedList、 Vector三者的异同:
相同:三各类都是实现了List接口,存储数据的特点相同,存储有序的可重复的数据
不同:见上
2、remove(2)默认是移除索引为2的元素,如果想要移除数据为2的元素,需要使用list.remove(new Integer(2));
ArrayList 的源码分析:
1、jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[ ] elementData
list. add(123); //elementData[0] = new Integer(123);
list. add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器: ArrayList list = new ArrayList(int capacity)
2、jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData 初始化为{}.并没有创建长度是10的Object[]数组。
List. add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到eLementData[0]中
后续的添加和扩容操作与jdk 7无异。
3、小结: jdk7 中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8 中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省了资源
LinkedList的源码分析:
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list. add(123);//将123封装到Node中,创建了Node对象。
其中, Node定义为:体现了 LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, Eelement, Node<E> next) {
this. item = element;
this.next = next;
this.prev = prev;
}}
4、vector源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底部都创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍
Collection子接口二:Set
使用的都是Collection接口中的方法
|-----Set接口:存储无序的、不可重复的数据。没有索引值|------HashSet: 作为Set 接口的主要实现类;线程不安全的;可以存储null 值。底层链表和数组共存
|------LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历。优点:频繁遍历时效率会比HashSet高
|------TreeSet: 可以按照添加对象的指定属性,进行排序。
set没有索引,只能使用foreach、iterator遍历
1、无序性:不等于随机性。是存储时并非按照数组索引顺序添加,而是根据哈希值添加
2、不可重复性:保证添加的元素使用equals()判断时,不能返回true。
添加元素过程:以HashSet为例:
向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算a的哈希值,哈希值再通过某些算法计算出再HashSet底层数组的存放位置(索引值),判断该位置是否有元素:
如果该位置没有元素,则元素a添加成功,
如果该位置上有元素,判断哈希值是否相同
哈希值不同,添加成功
哈希值相同,调用元素a所在类的equals()方法判断元素是否相同
equals返回true,添加失败
equals返回false,添加成功,添加到该索引位置处,以链表方式存储
jdk7,元素a存入数组中,指向原来的元素,也就是插入链表首部
jdk8,元素a不存入数组,插入链表尾部
要求:添加元素所在类必须重写equals()和hashCode()
重写两个方法尽量保持一致性,相同一个对象哈希值必须相同,软件直接生成的满足此要求
Set中remove(Object obj),也是先计算哈希值,再根据算法算出索引,再去指定索引上查找值,所以如果添加的元素数据改变,根据改变后的值查找就不会再查找到它的位置,也就是无法移除了。
linkedHashSet
在HashSet基础上,每个数据维护了头尾指针,存储了每个数据的插入顺序
优点:频繁遍历操作时效率会更高。
TreeSet
按照添加对象的指定属性,进行排序。
1、添加的元素必须是同一类的对象,类中写排序方法
2、两种排序方式:自然排序(实现Comparable接口),定制排序(Comparator接口)
①自然排序中,判断两个对象相同的标准是compareTo()返回0,与HashSet不同;
②定制排序中,比较两个对象相同的标准是compare()返回0.
自然排序代码示例(加上泛型的代码)
public class ComparableTest {
public static void main(String[] args) {
Collection<Person> treeSet=new TreeSet<>();
Person p1= new Person("tom", 12);
Person p2 = new Person("anna", 15);
Person p3 = new Person("anby", 22);
Person p4 = new Person("som", 12);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
treeSet.add(p4);
Iterator<Person> iterator=treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/*输出
Person{name='anby', age=22}
Person{name='anna', age=15}
Person{name='tom', age=12}
Person{name='som', age=12}
*/
}
}
class Person implements Comparable<Person> {
String name;
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person o) {
if (Integer.compare(this.age, o.age) == 0) { //年龄从大到小排序,年龄相同名字从大到小排序
return -this.name.compareTo(o.name);
}
return -Integer.compare(this.age, o.age);
}
}
定制排序使用方法,在TreeSet构造器中传入Comparator实现类对象。(加上泛型的代码)
public class ComparatorTest {
public static void main(String[] args) {
TreeSet<Person> treeSet=new TreeSet<Person>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) { //实现年龄从达到小排,年龄相同按名字从大到小排
if(o1.age==o2.age)
return -o1.name.compareTo(o2.name);//名字从大到小排
return -Integer.compare(o1.age,o2.age); //年龄从达到小排
}
});
Person p1= new Person("tom", 12);
Person p2 = new Person("anna", 15);
Person p3 = new Person("anby", 22);
Person p4 = new Person("som", 12);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
treeSet.add(p4);
Iterator<Person> iterator=treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/*输出
Person{name='anby', age=22}
Person{name='anna', age=15}
Person{name='tom', age=12}
Person{name='som', age=12}
*/
}
}
class Person{
String name;
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Set底层实现调用的是Map的底层实现
Map接口
Map结构
|-----Map接口:双列集合,用来存储 key - value对的数据,Set接口数据只占用了key的 位置
|------HashMap实现类:主要实现类,线程不安全,效率高,可以存储null的key和value|------LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历原因:在原有的HashMap底层结构基础上,添加了一对指针指向前后元素
对于频繁的遍历操作,此类效率高于HashMap
|------TreeMap实现类:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序(Comparable)或定制排序(Comparator)
底层使用红黑树
|------Hashtable实现类:古老实现类,线程安全的,效率低,不可以存储null的key和value
|------Properties:常用来处理配置文件。key和value都是String类型
Map结构的理解 双列集合
1、Map中的key是无序的,不可重复的,使用Set存储 -->key所在的类要重写equals和hashCode方法(以HashSet为例)
2、Map中的value是无序的、可重复的,使用Collection存储-->value所在类要重写equals方法
3、一个键值对:key-value构成了一个Entry对象 -->无序的、不可重复的,使用Set存储
Map常用方法
增加、修改:Object put(Object key,Object value):将指定key-value添加到(伙子u该)当前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集合,集合中的都是entry
遍历
方式一、Set entrySet()返回的Set集合中都是entry类型,通过iterator迭代器访问Set中每个数据,强转为Map.Entry类型,然后调用Entry的getKey()和getValue()获取key值value值
方式二、通过Set keySet()方法和iterator迭代器获取每个key值,再根据Object get(Object key)获取对应key值的value值。
HashMap底层实现原理
HashMap底层:数组+链表(jdk7之前)
数组+链表+红黑树(jdk8)
jdk7
HashMap map=new HashMap();
实例化以后,底层创建了长度16的一维数组Entry[ ] table。
map.put(key1,value1);
...
首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后得到在Entry数组中的索引值,查找该数组中索引值位置是否为空:
如果此位置上数据为空,此时key1-value1添加成功。
如果此位置不为空(如果有多个数据,那这些数据以链表形式存在),比较key1和已经存储的数据的哈希值:
如果key1哈希值与已经存在的数据的哈希值都不同,那么key1-value1添加成功
如果key1哈希值与已经存在的数据的哈希值相同,继续比较:调用key1所在类的equals方法,判断两个key是否相同
如果equals返回为false,此时key1-value1添加成功
如果equals返回为true,使用value1替换value2(覆盖掉相同key值的原元素数据)
当达到临界值时开始扩容,默认扩容为原来容量的2倍,并将原数组中数据重新计算哈希值计算在新数组中的索引值然后存储到新数组中
jdk8相较于jdk7底层数据的不同
1、new HashMap():底层没有创建长度为16的数组
2、jdk8底层的数组是:Node[ ],而非Entry[ ]
3、首次调用put()方法时,底层创建长度为16的数组。
4、jdk7底层结构只有数组+链表,jdk8中底层结构:数组+链表+红黑树
当数组的某一索引位置上的元素以链表形式存在的数据个数>8且当前数组长度>64时,此时此索引位置上的所有数据改为使用红黑树存储
HashMap中的常量
DEFAULT_ INITIAL_ CAPACITY : HashMap默认容量“ 16
DEFAULT_ LOAD_ FACTOR: HashMap默认加载因子(负载因子): 0.75
threshold:扩容的临界值=容量*填充因子: 16*0.75 = 12,为了防止链表过长
TREEIFY_ THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_ TREEIFY_ CAPACITY: 桶中的Node被树化时最小的hash表容量:64
LinkedHashMap底层实现原理
HashMap中的Node[ ] 只有一个next对象,指向下一个数据,而LinkedHashMap继承了Node[ ],改名为Entry[ ],但是有两个对象 before和after,指向前一个数据和后一个数据
TreeMap
添加key-value值要求key必须是同一个类的对象,并且如果key所在类是自定义类必须重写排序(自然排序,定制排序)参考TreeSet处写的代码
Hashtable子类Properties类
用于处理配置文件,key和value都是String类型
//创建了一个jdbc.properties的文件,文件内写name="123";
public class PropertiesTest {
public static void main(String[] args) throws IOException {
//创建对象
Properties p=new Properties();
//读取jdbc.properties文件内容
FileInputStream fis=new FileInputStream("jdbc.properties");
p.load(fis); //加载流对应的文件
//获取对应属性的值,变量名要相同才能获取,空格也不能多
System.out.println(p.getProperty("name")); //tom
System.out.println(p.getProperty("password")); //123
}
}
Collections(操作Collection、Map)工具类
常用方法
reverse(List):反转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中
注意:dest的size必须比src的size大才可以接受复制,否则报异常:Sourde does not fit in dest
处理方式:dest生成时为他添加足够数量的null:List dest=Arrays.asList(new Object[src.size()]);
boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换List集合中的旧值
提供了很多synchronizedXxx()方法,解决线程安全问题
List list=Collections.synchronizedList(list); //list就是线程安全的List
Map map=Collections.synchronizedMap(map); //map就是线程安全的Map