JAVA——集合
集合是Java中用来存放对象的容器,且只能存放对象。
相比较数组而言,数组只能存储基本类型,而且数组定义之后长度不可变。集合是一个可变的容器,但是只能用于存储引用类型的数据。集合类全部都支持泛型,是一种数据安全的用法。
Collection接口:
在此接口中定义了两个非常常用的子接口:List接口和Set接口。
List接口: 中的元素可重复存储,并且元素的存储是有序的。
Set接口: 中的元素不可重复,并且元素的存储是无序的。
Collection接口中提供的方法:
- int size(); 返回此collection中的元素数。
- boolean isEmpty(); 判断此collection中是否包含元素。
- boolean contains(Objectobj); 判断此collection是否包含指定的元素。
- boolean contains(Collectionc); 判断此collection是否包含指定collection中的所有元素。
- boolean add(Objectelement); 向此collection中添加元素。
- boolean addAll(Collectionc);将指定collection中的所有元素添加到此collection中
- boolean remove(Objectelement); 从此collection中移除指定的元素。
- boolean removeAll(Collectionc); 移除此collection中那些也包含在指定collection中的所有元素。
- void clear(); 移除些collection中所有的元素。
- boolean retainAll(Collectionc); 仅保留此collection中那些也包含在指定collection的元素。
- Iterator iterator(); 返回在此collection的元素上进行迭代的迭代器。
- Object[] toArray(); 把此collection转成数组。
List接口:
存储的元素可以重复,并且是有序的。
特有的方法:
- public Object get(int index) 根据下标,返回列表中的元素
- public Object add(int index, Object element); 在列表的指定位置插入指定元素.将当前处于该位置的元素(如果有的话)和所有后续元素向右移动
- public Object set(int index, Object element) ; 用指定元素替换列表中指定位置的元素
- public Object remove(int index) 移除列表中指定位置的元素
List实现类ArrayList:
向ArraysList中添加元素:
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("d");
list.add("c");
list.add("a");
根据下标获得指定元素:
String str = list.get(0);
用指定元素替换指定位置的元素
list.set(2, "a");
移除列表中的指定下标元素
list.remove(0);
这里我们可以思考一下,既然ArrayList存储的数据只能是引用类型,那为什么在我们日常的使用过程中以下代码不会报错呢。
list.add(10);//这里的ArrayList是存储Integer对象的
原因是,ArrayList只能存储引用类型是没有错的,是在使用add方法的时候,int类型的“10”被自动装箱成了Integer类型,所以“10”还是以对象的形式存储进了ArrayList中的。
注意: ArrayList是线程不安全的。
public class ArrayListTest {
/**
* 实现了list接口,存储的数据有序,可重复
* 优点:效率快
* 缺点:线程不安全,增加删除数据操作速度慢。
* 底层:使用的是Object[] elementData数组
*/
public static void main(String[] args) {
/**
* ArrayList是一个集合,实际上是一个存储对象的长度可变的数组
* ArrayList无参构造器和有参构造器是有区别的(创建动态数组初始长度时)
* JDK7中:
* 调用ArrayList的无参构造器,会直接默认创建一个长度为10的Object[]对象数组,类似于单例中的饿汉式
* JDK8中:
* 调用ArrayList的无参构造器,不会先初始化ArrayList的初始长度,当调用add方法的时候
* 才会创建初始化长度为10(创建一个长度为10的Object[]数组),类似于单例中的懒汉式
*
* ArrayList长度可变的底层实现过程:
* 当ArrayList进行对比后发现,需要添加的对象已经超过了自身容纳的长度时,会创建一个新的Object[]数组
* 新的数组容量扩充为原来的1.5倍,将旧数组中的数据复制到新数组中,然后将旧数组的引用指向新数组的地址。
* 如果扩容一次之后,发现扩容后的数组任然没办法满足要存储的数据,那么就将创建该数据长度的数组。
*/
ArrayList<String> list1 = new ArrayList<>();//调用无参构造器,集合长度默认为10
/**
* 调用有参构造器,直接会在底层向Object[] elementData数组指定初始长度
* 在实际开发过程中知道需要数组大小时,推荐使用此方法
*/
ArrayList<String> list2 = new ArrayList<>(20);//调用有参构造器,集合默认长度为指定长度
}
}
List的实现类LinkedList
LinkedList的使用方式和ArrayList是一致的,LinkedList是线程安全的。
public final class LinkedListTest {
/**
* LinkedList实现了list接口,存储的数据有序,可重复
* LinkedList使用在进行频繁增加删除的场景
* LinkedList底层实现是链表的形式,所以在进行增加删除等操作时不会像ArrayList一样要操作位置之后的每一个元素
* 链表只需要将前一个元素的尾部指向插入元素的首部,再将插入元素的尾部指向插入元素位置后一个元素的首部即可
*/
public static void main(String[] args) {
/**
* LinkedList内部声明了一个Node类,在该类中有2个属性一个是first,另外一个是last
* 当调用add方法的时候就会将添加的数据,存进Node中,创建Node对象
* Node对象中的first指向前一个元素,若没有前一个元素,则first为null;
* Node对象中的last指向后一个元素,若没有后一个元素,则last为null;
* 当first或last为null时,可以理解为Node为链表中的第一个元素或最后一个元素
*/
LinkedList<String> list = new LinkedList<>();
list.add("123");
}
}
Vector
远古级的类,在JDK1.0就提出了,现在基本不适用。
public class VectorTest {
/**
* Vector是JDK1.0中的类,远古级别,现在基本不会使用
* Vector是线程安全的,所以效率比较低
* 虽然Vector是线程安全的,但是在实际开发过程中任然很少用,我们可以使用Collections工具类中的方法实现list的线程安全
* Vector底层是Object[] elementData
*/
public static void main(String[] args) {
/**
* Vector在创建的时候初始长度为10,这与ArrayList一样
* 但是Vector与ArrayList不同的一点是,Vector在扩容的时候是直接扩充原来2倍的大小,这比起ArrayList要大
*/
Vector<String> v = new Vector<String>();
v.add("123");
}
}
迭代器
Iterator: 是集合的迭代器,可以使用迭代器去遍历一个集合。
常用的方法:
- hashNext(); 如果迭代器中还有元素,则返回true
- next(); 返回迭代器中的下一个元素
- remove(): 删除迭代器中新返回的元素
注意:
- Iterator只能够单向移动
- remove()是在迭代器进行迭代过程中修改集合的唯一一个安全的方法。
Iterator<数据类型> it = list.iterator();
while(it.hashNext){
it.next();
}
ListIterator: 继承于Iterator,只能让List类型的进行访问
特点:
- 可以向前或者向后双向遍历
- 产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引
- 可以使用set()方法替换它访问过的最后一个元素
- 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素
Set接口:
存储的元素无序,且不可重复。
注意: 遍历set只有两种方式,forearch和迭代器。
HashSet:
public class HashSetTest {
/**
* HashSet是接口Set的主要实现类
* HashSet存储的数据是无序且唯一的,线程不安全
* 这里的无序性不是指随机,在调用输出时顺序是固定的,但是存储的位置不是顺序的
*/
public static void main(String[] args) {
/**
* HashSet存储方式不再是单一的数组
* 在刚开始存储数据的时候,会去取数据的哈希值,然后通过算法获取存储数据在数组中的下标
* 然后将数据存储在指定位置。之后如果有其他数据通过算法算出的数组下标,在数组中已经存储
* 了元素,则调用equals方法比较两个元素大小
* 若返回值为true,则不插入元素
* 若返回值为false,在JDK7中,将要插入的元素放在数组之外
* 在JDK8中,将要插入的元素放进数组,原本在数组内的元素取出,存储在数组之外。
*/
HashSet<String> set = new HashSet<String>();
set.add("123");
}
}
使用方法与ArrayList类似,但是提供了一些Set集合特有的方法
HashSet<String> hs = new HashSet<String>();
hs.add("User1");
hs.add("User2");
hs.add("User3");
hs.add("User4");
hs.add("User5");
hs.remove("User5");
System.out.println("判断集合中有没有元素:" + hs.isEmpty());
System.out.println("判断集合中元素的个数:" + hs.size());
System.out.println("判断集合中是否有指定元素:" + hs.contains("User1"));
LinkedHashSet:
相比较HashSet,用法相同,但读取出的元素有序,
public class LinkedHashSetTest {
/**
* LinkedHashSet与HashSet基本一样
* 不同的是LinkedHashSet遍历出来的数据的顺序与存储数据的顺序是一样的
* LinkedHashSet存储结构上与HashSet类似,不同的是LinkHashSet在数组外的元素还会拥有2个属性
* 一个属性代表插入位置前面的元素,另一个位置代表插入位置之后的元素,
* 这就使得LinkedHashSet知道存储的顺序,遍历出来的数据就会是插入时的顺序
*/
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<String>();
set.add("123");
}
}
TreeSet:
能够按照某种规则对元素进行排序,且唯一。
可以使用自然排序(基本类型)或者比较器(引用类型 ,需要实现接口中的compare方法)实现排序方式,通过比较器实现需要创建一个匿名内部类去实现Comparator接口中的compare方法。(Comparator的优先级大于Comparable)
TreeSet<Student> set = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// TODO Auto-generated method stub
String stuName1 = o1.getName();
String stuName2 = o2.getName();
if(stuName1.equals(stuName2) && o1.getAge() == o2.getAge()) {
return 0;
}
if(stuName1.length() != stuName2.length()) {
return stuName1.length() - stuName2.length();
}else {
if(o1.getAge() != o2.getAge()) {
return o1.getAge() - o2.getAge();
}else {
return 1;
}
}
}
});
Map接口:
List存储的是单一对象,而map存储的是双对象,以键值对的形式表现。
KEY-------->VALUE
key与value之间存在着映射关系,一个key对应一个value,并且Key是唯一的。
在Map中,Key必须唯一,但是value可以重复。
HashMap:
可以存null键null值,但是线程不安全。
在Map接口中也提供了另外两个类,与HashMap用法一致,效果也一样。但是他们是线程安全的。
这两个分别是Hashtable和ConcurrentHashMap。
Hashtable:不可以存null键null值,线程安全,但是在实际开发当中并不推荐使用此方法。
ConcurrentHashMap:不可以存null键null值,线程安全,分段式加锁,效率更快,推荐使用。
使用put方法,向map中添加元素
HashMap<String, Integer> map = new HashMap<>();
map.put("小明", 32);
map.put("小黄", 22);
map.put("小绿", 25);
map.put("小红", 22);
map.put("小黑", 12);
并且可以通过key对value的值进行修改
map.put("小黑", 19);
但是在遍历Map的时候有一个问题,Map没有提供可以直接遍历的方法。所以我们只能通过Set来对Map中的元素进行遍历。
可以实现对Map遍历的方式有两种:
- 通过Set存储Key值,通过Key值取出value实现对于Map的遍历。
Set<String> setKey = map.keySet();//存储key值
for (String str : setKey) {
Integer age = map.get(str);
System.out.println("key:" + str + "--" + "value:" + age);
}
- 通过Set来存储Key与Value之间的映射关系,实现对于Map的遍历
Set<Entry<String, Integer>> entrySet = map.entrySet();//存储key与value之间的映射关系
for (Entry<String, Integer> entry : entrySet) {
String str = entry.getKey();
Integer age = entry.getValue();
System.out.println("key:" + str + "--" + "value:" + age);
}
LinkedHashMap:
用法与HashMap一致,不过LinkedHashMap存储的数据是有序的。(线程也是不安全的)
TreeMap:
使用方式和TreeSet类似,需要去实现接口中的compare方法。
可以事先根据比较器提供的方法对元素进行排序。
TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
String stuName1 = o1.getName();
String stuName2 = o2.getName();
//姓名和年龄相同认为是同一个学生
if(stuName1.equals(stuName2) && o1.getAge() == o2.getAge()){
return 0;
}
//姓名长度不相同
if(stuName1.length() != stuName2.length()){
return stuName1.length() - stuName2.length();
}else{//姓名长度相同
//年龄不相同
if(o1.getAge() != o2.getAge()){
return o1.getAge() - o2.getAge();
}else{//年龄相同
return 1;
}
}
}
});