集合
集合中类的关系图
数组与集合
1、数组与集合数据存储简述
集合、数组都是对多个数据进行存储操作的结构,简称java容器
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt .jpg等)
2、数组存储的特点
- 初始化以后,其长度就确定了
- 数组一旦定义好,其元素的类型就确定了,就只能操作指定类型的数据
3、数组存储数据的劣势
- 初始化以后,其长度不可改变
- 数组中提供的方法有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对无序、不可重复的需求不能满足
注:集合的数据存储可以很好解决数组存储方面的劣势
4、集合框架主要的类结构
Collection接口
Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 Collection 接口存储一组不唯一,无序的对象。
实体类 - Person.java
click me
class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (!Objects.equals(name, person.name)) return false;
return Objects.equals(age, person.age);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (age != null ? age.hashCode() : 0);
return result;
}
}
常用方法1
示例一
click me
@Test
public void test1(){
Collection coll = new ArrayList();
//1、add(object e):将元素添加到集合中
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new Date());
//2、size():获取集合的元素的个数
System.out.println(coll.size());//4
//3、addAll(Collection coll):将集合coll中的元素添加到coll2中
Collection coll2 = new ArrayList();
coll2.add(234);
coll2.addAll(coll);
System.out.println(coll2);//[234, AA, BB, 123, Tue Aug 30 11:38:26 CST 2022]
//System.out.println(coll);
//4、clear():清空集合
coll2.clear();
//5、isEmpty():判断当前集合是否为空
System.out.println(coll2.isEmpty());
}
常用方法2
示例二
click me
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
//6、contains(object obj):判断当前集合是否包含obj对象
boolean b1 = coll.contains(123);
System.out.println(b1);//true
System.out.println(coll.contains(new String("Mary")));//true
//没有重写Person类对象obj的equals():判断对象的地址是否相同
//System.out.println(coll.contains(new Person("Tom", 18)));//false
//重写Person类对象obj的equals():判断内容是否相同
System.out.println(coll.contains(new Person("Tom", 18)));//true
//7、containsAll(Collection coll):判断形参coll中的所有元素是否都存在于当前集合中
System.out.println(coll.containsAll(Arrays.asList(123,456)));//true
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
//8、remove(Object obj):移除当前集合中的obj元素,返回boolean
coll.remove(123);
System.out.println(coll);//[456, false, Mary, Person{name='Tom', age=18}]
//9、removeAll(Collection coll):从当前集合中移除集合coll的元素,返回boolean
Collection coll2 = Arrays.asList(456, false);
coll.removeAll(coll2);//相当于求coll与coll2的差集
System.out.println(coll);//[Mary, Person{name='Tom', age=18}]
}
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
//10、equals(Object obj):比较两个对象元素是否相同
Collection coll3 = new ArrayList();
coll3.add(123);
coll3.add(456);
coll3.add(false);
coll3.add(new String("Mary"));
coll3.add(new Person("Tom", 18));
//返回boolean值
System.out.println(coll.equals(coll3));//true
//11、retainAll(Collection coll2):获取当前集合和coll1集合的交集
Collection coll2 = Arrays.asList(123,456,"aaa");
coll.retainAll(coll2);//交集
System.out.println(coll);//[123, 456]
}
@Test
public void test5(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
//12、hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());//2024439392
//13、toArray():集合 --> 数组
Object[] objects = coll.toArray();
System.out.println(Arrays.toString(objects));//[123, 456, false, Mary, Person{name='Tom', age=18}]
//Arrays.asList():将数组 --> 集合
List list = Arrays.asList("aa0", 123, 343, "bb");
System.out.println(list);
//注意:
List<int[]> arr1 = Arrays.asList(new int[]{123, 1234});
System.out.println(arr1.size());//1 --> 此处数组是以一个对象添加到List集合中
System.out.println(arr1);//[[I@1a93a7ca]
List<Integer> arr2 = Arrays.asList(new Integer[]{124, 7852});
System.out.println(arr2.size());//2 --> 此处Integer类的每个整数自动装箱成为两个整型对象添加到集合中
System.out.println(arr2);//[124, 7852]
//14、iterator():返回Iterator接口的实例,用于遍历集合元素
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
注意:向Collection接口的实现类对象中添加数据obj时,要求obj所在的类要重写equals()
Collection集合与数组间的转换
//xxx.toArray(): 集合 --> 数组
Object[] objects = coll.toArray();
System.out.println(Arrays.toString(objects));//[123, 456, false, Mary, Person{name='Tom', age=18}]
//Arrays.asList(): 数组 --> 集合
List list = Arrays.asList("aa0", 123, 343, "bb");
System.out.println(list);
Iterator接口
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素
1、Collection集合的遍历方式
- 使用Iterator迭代器
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
- foreach循环(增强for循环,内部本质上调用迭代器)
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
for (Object obj : coll) {
System.out.println(obj);
}
coll.forEach(System.out::println);
}
2、常用方法
hasNext() 和 next()
remove():移除集合中的元素
示例
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Mary"));
coll.add(new Person("Tom", 18));
Iterator iter = coll.iterator();
while(iter.hasNext()){
Object next = iter.next();
if("Mary".equals(next)){
//remove():移除集合中的元素
iter.remove();
}
}
//再一次使用迭代器,需要新的迭代器
Iterator iter2 = coll.iterator();
while(iter2.hasNext()){
System.out.println(iter2.next());
}
}
迭代器的执行原理
注意
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,在调用remove都会报错IllegalStateException
拓展 fori 与 foreach 的使用注意
@Test
public void test4(){
String[] arr = new String[]{"aa","aa"};
for (int i = 0; i < arr.length; i++) {
arr[i] = "bb";//将字符串"bb"覆盖原来元素
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);//bb bb
}
String[] arr2 = new String[]{"aa","aa"};
for (String obj : arr2) {//将数组中的元素赋值给obj,不改变影响原来的数组arr2
obj = "bb";
}
for (int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//aa aa
}
}
List接口
1、List特点
List接口(jdk1.2):存储有序的、可重复的数据。相当于“动态”数组
注:添加的对象所在的类要重写equals()方法
2、常用实现类
ArrayList(jdk1.2): 作为List的主要实现类,其线程是不安全的,查询效率高;底层使用Object[] elementData数组
LinkedList(jdk1.2):对于频繁插入、删除操作,比ArrayList效率高,线程不安全。底层使用双向链表结构
Vector(jdk1.0):作为List的老的接口,线程安全的,效率低;底层使用Object[] elementData
3、List的常用方法
void add(int index,Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将集合eles中的所有元素添加进来
Object get(int index):获取指定index位置上的元素
int indexOf(Object obj):返回obj在集合中的首次出现的索引位置
int lastIndexOf(Object obj):返回obj在当前集合中末尾首次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素尾ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
示例
click me
@Test
public void test1(){
ArrayList list = new ArrayList();
//1、void add()
list.add(123);
list.add(456);
list.add("BB");
list.add(new Person("Mary", 20));
list.add(99);
System.out.println(list);//[123, 456, BB, Person{name='Mary', age=20}, 99]
//2、boolean addAll()
List list2 = Arrays.asList(12, "GG", "Li");
list.addAll(1, list2);
System.out.println(list);//[123, 12, GG, Li, 456, BB, Person{name='Mary', age=20}, 99]
}
@Test
public void test2(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("BB");
list.add(new Person("Mary", 20));
list.add(99);
//3、Object get()
Object obj = list.get(2);
System.out.println(obj);//BB
//4、int indexOf()
int index = list.indexOf(99);
System.out.println(index);//4
//5、int lastIndexOf()
int index2 = list.lastIndexOf("BB");
System.out.println(index2);//2
//6、Object remove()
Object removeObj = list.remove(list.size()-1);
System.out.println(removeObj);//99
System.out.println(list);//[123, 456, BB, Person{name='Mary', age=20}]
//7、Object set()
Object obj2 = list.set(0, "zhang");
System.out.println(obj2);//123, 注:返回的是当前位置上的元素
System.out.println(list);//[zhang, 456, BB, Person{name='Mary', age=20}]
//8、List subList()
List subList = list.subList(1, 3);
System.out.println(subList);//[456, BB]
System.out.println(list);//[zhang, 456, BB, Person{name='Mary', age=20}]
}
Set接口
1、Set特点
Set接口:存储无序的、不可重复的数据
存储无序的:不等于随机性.存储数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值所对应的位置添加
不可重复的:保证添加的元素按照equals()判断时,不能返回true,即:相同的元素只能添加一个
2、常用实现类
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet。(LinkedHashSet在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据)
TreeSet:可以按照添加对象的指定属性,进行排序
3、常用方法
Set接口中没有额外定义新的方法,使用的都是Collection中声明的方法
示例
click me
//HashSet的使用特点
@Test
public void test1(){
HashSet set = new HashSet();
set.add(234);
set.add(111);
set.add("NN");
set.add("MM");
set.add(new User("Mary", 10));
set.add(new User("Mary", 10));
set.add(555);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//LinkedHashSet的使用特点
@Test
public void test2(){
HashSet set = new LinkedHashSet();
set.add(345);
set.add(345);
set.add("NN");
set.add("MM");
set.add(new User("Mary", 10));
set.add(new User("Mary", 10));
set.add(555);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
/**
* 1、向TreeSet中添加的数据,要求是相同类的对象
* 2、两种排序的方式:自然排序(实现comparable接口) 和 定制排序(实现comparator接口)
* 3、自然排序中,比较两个对象是否相同标准为:compareTo()返回0,不再是equals()
*/
@Test
public void test3(){
TreeSet set = new TreeSet();
//错误:TreeSet添加的数据所在类的对象必须同一个对象
//set.add(12);
//set.add("AA");
//set.add(34);
//示例一:同一个整型对象
//set.add(12);
//set.add(2);
//set.add(-12);
//set.add(52);
//set.add(1);
//示例二:同一个User对象,实现comparable接口
set.add(new User("jack", 62));
set.add(new User("jim", 11));
set.add(new User("kim", 67));
set.add(new User("Mary", 52));
set.add(new User("Mike", 42));
set.add(new User("Mary", 31));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
HashSet添加元素的过程
注意
向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的技巧:对象中作用equals()方法比较的Field,都应该用来计算hashCode的值
HashSet内存解析示意图
Map接口
1、Map特点
Map:双列数据,存储key-value对的数据
Map数据结构的特点
Map中的key:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode()
Map中的value:无序的、可重复的,使用Collection存储所有的value --> value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象
Map中的entry:无序的、不可重复的,使用Set存储所有的Entry
2、常用实现类
HashMap的底层实现:数组 + 链表(jdk7及之前) / 数组 + 链表 + 红黑树 (jdk8)
3、常用方法
示例
click me
@Test
public void test3(){
HashMap map = new HashMap();
map.put("AA", 123);
map.put(23, 12);
map.put("CC", 98);
//get()
System.out.println(map.get(11));//null
//containsKey()
System.out.println(map.containsKey("ll"));//false
//containsValue()
System.out.println(map.containsValue(77));//false
map.clear();
//isEmpty()
System.out.println(map.isEmpty());//true
}
@Test
public void test2(){
HashMap map = new HashMap();
//Object put(Object key, Object value)
map.put("AA", 123);
map.put(23, 12);
//put()修改
map.put("AA", 77);
map.put("CC", 98);
System.out.println(map);//{AA=77, CC=98, 23=12}
HashMap map2 = new HashMap();
map2.put("DD", "jack");
map2.put("FF", 99);
//void putAll(Map m)
map.putAll(map2);
System.out.println(map);//{AA=77, CC=98, DD=jack, FF=99, 23=12}
//Object remove(Object key)
Object val = map.remove("LL");
System.out.println(val);//null
System.out.println(map);
//clear()
map.clear();//不等同于map=null;
System.out.println(map.size());//0
System.out.println(map);//{}
}
4、Map集合的遍历方式
@Test
public void test4(){
HashMap map = new HashMap();
map.put("AA", 123);
map.put(23, 12);
map.put("CC", 98);
//keySet():遍历所有的key集
for (Object key : map.keySet()) {
System.out.println(key);
}
//values():遍历所有的value集
for (Object value : map.values()) {
System.out.println(value);
}
//entrySet():遍历所有的key-value构成Set集合
//方式一:
Set set = map.entrySet();//entrySet集合中的元素都是entry
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry) iterator.next();
System.out.println(entry.getKey() + "===" + entry.getValue());
}
//方式二:for (Map.Entry<Integer, String> entry : map.entrySet()){}
for (Object obj: map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
//方式三:
Set set1 = map.keySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()){
Object key = iterator1.next();
Object value = map.get(key);
System.out.println(key + "+++" + value);
}
}
5、LinkeedHashMap的使用
继承了HashMap类,使用HashMap的put()方法,并重写了newNode(),在newNode()中重新new了一个Entry()方法,该方法又继承HashMap.Node<K,V>
内部源码区别
示例
@Test
public void test1(){
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(456,"aa");
map.put(123,"mm");
map.put(789,"nn");
for(Map.Entry<Integer,String> entry: map.entrySet()){
System.out.println("k=" + entry.getKey() + ", v=" + entry.getValue());
}
}
//结果
k=456, v=aa
k=123, v=mm
k=789, v=nn
6、TreeMap排序
按照key的值,进行自然排序、定制排序
7、Properties的使用
常用来处理配置文件,key-value都是String类型
public class PropertiesTest {
public static void main(String[] args) {
FileInputStream file = null;
try {
Properties pros = new Properties();
file = new FileInputStream("jdbc.properties");
pros.load(file);
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name=" + name + " ,password=" + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Collections工具类
1、特点
可以操作List、Set、Map集合的工具类
2、常用方法
示例
/**
* Collections类中提供了多个 synchronizedXxx()方法,
* 该方法可将指定集合包装成线程同步的集合,从而可以解决多线程
* 并发访问集合时的线程安全问题
*/
@Test
public void test2(){
ArrayList list = new ArrayList();
list.add(123);
list.add(99);
//返回list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
HashMap map = new HashMap();
map.put(1, "AA");
map.put(2, "BB");
//返回map1即为线程安全的Map
Map map1 = Collections.synchronizedMap(map);
}
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(123);
list.add(99);
list.add(3);
list.add(36);
list.add(-3);
list.add(0);
//反转
Collections.reverse(list);
//随机排序
Collections.shuffle(list);
//自然排序
Collections.sort(list);
//定制排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return -Integer.compare((int)o1, (int)o2);
}
});
//交换
Collections.swap(list, 1,2);
System.out.println(list);
//获取最大值、最小
System.out.println(Collections.max(list));//123
System.out.println(Collections.min(list));//-3
//元素出现的次数
System.out.println(Collections.frequency(list, 3));//1
//从src集合复制到dest
List list1 = Arrays.asList(new Object[list.size()]);
System.out.println(list1);//[null, null, null, null, null, null]
Collections.copy(list1, list);
System.out.println(list1);//[123, 3, 99, 36, -3, 0]
}
ArrayList和HashMap都是线程不安全的,若要求线程安全操作,使用Collections.synchronizedXxx(Xxx)进行转换
其它拓展
1、ArrayList的底层实现原理解析
2、LinkedList的源码分析
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(333);//将数据333封装到Node中,创建Node对象
//其中Node定义为:Node的特点体现了双向链表
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
3、Vector的底层实现分析
通过Vector()构造器创建对象,底层创建了长度为10的数组elementData;在扩容方面,默认扩容为原来数组长度的2倍
4、HashMap的底层实现原理解析
HashMap数据结构示意图
HashMap数据添加过程
说明:
关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储
在不断的添加过程中,涉及到扩容问题,当超出临界值(且要存放的位置为非null),扩容:默认的扩容方式:扩容为原来的2倍,并将原有的数据复制过来
jdk8与jdk7在底层实现方面的不同:
1、new HashMap():底层没有创建一个长度为16的数组
2、jdk8底层的数组是:Node[],而非(jdk7)Entry[]
3、首次调用put()方法,底层创建长度为16的数组
4、jdk7底层结构为: 数组+链表。jdk8中底层结构:数组+链表+红黑树
在形成链表结构时,七上八下(在jdk7中链表上新的元素指向旧的元素,在jdk8中旧的元素指向新的元素)
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前组的长度 > 64时;此时索引位置上的所有数据改为使用红黑树存储
源码常量词:
DEFAULT_INITIAL_CAPACITY: HashMap的默认容量:16
MAXIMUM_CAPACITY: HashMap的最大支持容量:2^30
DEFAULT_LOAD_FACTOR: HashMap的默认加载因子:0.75
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:填充因子
5、HashMap 与 Hashtable的异同
HashMap是Map接口的主要实现类,其线程不安全的,检索效率高,可以存储null的key和value
Hashtable是Map接口旧的实现类,其线程安全,效率低,不可以存储null的key和value
6、加载因子(LoadFactor)大小对HashMap的影响
标签:基础,add,System,println,coll,集合,new,javaSE,out From: https://www.cnblogs.com/bione/p/16811416.html