JAVA集合类
java的集合类通常分为Set、List、Map和Queue四大体系(本次介绍前三种)
集合与数组的区别
集合和数组类似,都可以存放很多元素,但与数组不同的是,集合的长度是可以变化的,而数组的长度是固定不变的;数组是存放基本类型的数据,集合是用来存放对象的引用
Collection API
最基本的集合接口是Collection接口,根据组织的方式及实现功能不同,Collection的子接口分为两类:Set接口和List
各种类的关系
集合类型
从体系上讲集合的类型可归纳为三种:集(Set)、列表(List)、映射(Map)。
Set集合是无序的集合,Set集合中不区分元素的顺序,但不允许出现重复的元素。SortedSet继承Set接口,与Set集合相同也不允许出现重复的元素,单机和中的元素是有序排列的。
List集合是有序集,在List中元素的存储按照加入是的顺序排列,以索引的方式提供对元素的访问,且允许包含重复元素。
映射中保存成对的“”键-值”(Key-Value)信息,Key和Value均为对象,映射中不能包含重复的键,键的排列是无序的,每个键最多只能映射一个值。SortMap继承Map接口,但他的键是有顺序集合。
List接口及ArrayList、Vector类
List接口的实现类主要有ArrayList、Stack、Vector和LinkedList
1、ArrayList常见使用法
public class Arraylist {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
list.add("张三");//使用List的方法将张三添加进去
list.add("王五");
list.add("李四");
list.add("小红");
list.add("小白");
System.out.println(list);
//使用索引操作list
list.remove(1);//使用函数移除指定位置元素 从零开始
System.out.println(list);
list.add(1,"新王五");//使用add函数添加
System.out.println(list);
list.set(3, "取代小红");//替换指定位置元素
System.out.println(list);
System.out.println(list.get(3));//获取指定位置的元素
}
}
运行结果:
addAll函数
List<String> list = new ArrayList<>();
List<String> list4 = new ArrayList<>();
list4.add("数据库");
list4.add("操作系统");
list4.add("编译原理");
System.out.println(list.size());
list.add("java");
list.add("python");
list.add("c#");
list.add("script");
list.addAll(list4);
增强for循环输出与list的截取
for(String ipo:list){
System.out.println(ipo);
}
//list的截取
System.out.println("++++++++++++++++++++++");
list=list.subList(1,2);//结尾的不截取
for(String ipc:list){
System.out.println(ipc);
}
在这里需要注意的一项,在使用增强for循环的时候其本质就是使用java中迭代器,而是用迭代的时候是不允许对原来的内容进行任何操作。
插入数据底层的变化
可以看出修改的数据越靠后,使用的时间就越短。
原因分析
因为ArrayList结构是有序列表,在进行添加新数据的时候,按照索引查找到位置,将值赋值给索引出,然后将索引处后面的所有内容向后移动,而下标越小,需要向后移动的数据越多,因此需要的时间就越大。
2、Vector向量
Vector类也实现了LIst接口,也用于表述长度可变的对象数组列表。与ArrayList的差别是:Vector是同步(线程安全)的,运行效率较低,主要用在多线程环境中;而ArrayList不是同步的,适合在单线程的环境中使用。
3、Stack堆栈
堆栈是一种“先进后出”数据结构,可以形象比较成为弹夹,只能在一端进行输入输出,最后的进入的最先出来
Stack 使用实例
public class Test_Stack {
public static void main(String[] args) {
// TODO Auto-generated method stub
Stack<String> stack = new Stack<String>();
stack.push("张三");//将字符串压入栈中
stack.push("李四");
stack.push("王五");
stack.push("小红");
System.out.println(stack);
System.out.println("弹栈前:size="+stack.size());
System.out.println("弹出元素:"+stack.pop());
System.out.println("弹栈后栈中的元素:"+stack);
System.out.println("弹栈后:size="+stack.size());
}
}
运行结果
4、LinkedList
LinkedList实现的是一个双向链表。每个节点除含有元素外,还包含向前、向后的指针。
链表的查找规律:距离中间越远查找越快。
源码分析:
通过分析源码可以看出,每次查询的时候先判断索引与中间值进行比较,当大于中间值的时候就会从最后一个元素向前查找。
5、LinkedList与ArrayList使用上面的区别
由于二者的底层是不一样的因此使用的时候也有一些差异
- LinkedList底层是一个双向链表,因此在有关对元素进行操作的都选取LinkedList。
- ArrayList底层是顺序表,因此设计到查询的业务尽量选取ArrayList
Map接口及Hashmap接口
1、HashMap
使用方法
public class Test_Map {
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<String,String> map = new HashMap<String,String>();
//给map中添加元素
map.put("王五", "董事长");
map.put("张三", "经理");
System.out.println(map);
//根据指定的key获取对应的value
String wang = map.get("王五");
System.out.println(wang);
//根根据key删除元素 并且会返回对应的value的值
String value = map.remove("张三");
System.out.println(value);
System.out.println(map);
}
}
结果:
1.1HashMap的遍历*
public class Test_Map {
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<String,String> map = new HashMap<String,String>();
//给map中添加元素
map.put("王五", "董事长");
map.put("张三", "经理");
for(Map.Entry<String,String> entry: map.entrySet()) {
System.out.println("key = "+entry.getKey()+", value = "+entry.getValue());
}
}
}
结果:
Set<String> set = map.keySet();
Integer value = map.get("A");
for(String str : set){
Integer a = map.get(str);
System.out.println(a);
}
其实这里有四种遍历方法,这里只列出其中最常用的一种
一个好用的方法
System.out.println(map.getOrDefault(“f”,1000)); 在map中查找key为f查到的话返回查到的值,没有查到的话返回默认值1000
2、HashTable
HashMap是基于哈希表的Map接口的实现,此实现提供所有可选择的映射操作,并允许使用空(null)值和空(nulll)键。除了不同步和允许使用null之外,hashmap类与HashTable类大致相同。
3、HashTable和HashMap的区别
- HashMap没有锁 因此读写都不安全
- HashTable有锁机制,因此是线程安全的但是效率低,但是读写都是安全的。
- HashMap键允许为空,值也允许为空。
- HashTable键不允许为空,值也不允许为空。
Set接口和其实现类
Set接口中定义的常用方法和Conllection接口方法相同,另外Set对add()添加了限制,即不能添加相同的内容元素对象。主要接口的实现类有HashSet和TreeSet类
常见适用方法
住:没有get方法
public class Test_hashset {
public static void main(String[] args) {
// TODO Auto-generated method stub
Set<String> hs = new HashSet<String>();
hs.add("张三");
hs.add("李四");
System.out.println(hs);
hs.add("小王");
hs.add("小王");
System.out.println(hs);
}
}
结果:
可以看到 自动过滤掉重复的内容了
set集合高级用法
因为set不允许有重复的但是当使用下面的这种方式,他不能判断类是否完全相同
Set<String> set = new HashSet();
set.add("admin");
set.add("root");
set.add("test");
set.add("administor");
// for(String str : set){
// //set.remove("admin");
// //set.remove(str);
// //出现错误的原因是使用迭代器时候不允许操作原来的内容 包括增删改查
// System.out.println(str);
// }
Set<Student> set3 = new HashSet<>();
Student student = new Student("2018005845",22,"山西太原");
Student student1 = new Student("2018005845",22,"山西太原");
Student student2 = new Student("2018005845",22,"山西太原");
set3.add(student1);
set3.add(student);
set3.add(student2);
System.out.println(set3);
student类如下:
public class Student {
private String num;
private int age;
private String address;
public Student(String num, int age, String address) {
this.num = num;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"num='" + num + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
运行结果:
从运行结果当中可以看出,三个类都输出了
分析原因:
因为在使用HashSet的add其实使用的HashMap的put函数
HashMap put源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
分析源码可以得出结论:
hashSet使用的是hashMap的put方法,而hashMap的put方法,使用hashCode()用key作为参数计算出hash值,然后进行比较,如果相同,再通过equals()比较key值是否相同,如果相同,返回同一个对象。
我们在使用的时候对象的对象的HashCode值不一样,因此添加的时候就把三个对象当成不一样的对象,因此最后三个对象都添加成功
解决这种问题的方法:
重写类中hashCode和equals方法
在student类中写:
书写的方式有很多种 只要能满足最后的结果即可。