根着尚硅谷老师java视频做的笔记
集合
Java集合框架概述
-
集合、数组都是对多个数据进行存储操作的结构,简称Java容器.
说明:此时的存储,主要是指的是内存层面的存储,不涉及持久化存储(.txt,.jpg,.avi, 数据库中)
-
数组存储多个数据方面的特点
- 2.1特点
一旦初始化以后,长度就确定了
数组一旦定义好,其元素的类型也确定了.我们也就只能操作指定类型的数据了,比如:String[] arr; int[] arr1; Object[] arr2;(Object可以装他的子类)
- 2.2缺点
一旦初始化,长度就不能修改了
数组中提供的方法有限,对于添加、删除、插入数据等操作,非常不便,效率不高
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、重复.对于无序、不可重复的需求,不能满足;
- Collection接口继承树(虚线代表实现 实线代表继承关系)
- Map接口实现树
|----Collection接口:单例集合,用来存储一个一个对象
|----List接口:存储有序的、可重复的数据 -->"动态"数组
|----ArrayList、LinkList、Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的"集合"
|----HashSet、LinkedHashSet、TreeSet
|----Map接口:双列集合,用来存储一对(key-value)一对的数据 —>高中函数:y=f(x)
|----HashMap、LinkedHashMap、TreeMap、HashTable、Properties
Collection接口方法的使用
boolean | add(E e) 确保此集合包含指定的元素(可选操作)。 |
---|---|
boolean | addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(可选操作)。 |
void | clear() 从此集合中删除所有元素(可选操作)。 |
boolean | contains(Object o) 如果此集合包含指定的元素,则返回 true 。 |
boolean | containsAll(Collection<?> c) 如果此集合包含指定 集合 中的所有元素,则返回true。 |
boolean | equals(Object o) 将指定的对象与此集合进行比较以获得相等性。 |
int | hashCode() 返回此集合的哈希码值。 |
boolean | isEmpty() 如果此集合不包含元素,则返回 true 。 |
Iterator<E> | iterator() 返回此集合中的元素的迭代器。 |
default Stream<E> | parallelStream() 返回可能并行的 Stream 与此集合作为其来源。 |
boolean | remove(Object o) 从该集合中删除指定元素的单个实例(如果存在)(可选操作)。 |
boolean | removeAll(Collection<?> c) 删除指定集合中包含的所有此集合的元素(可选操作)。 |
default boolean | removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。 |
boolean | retainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素(可选操作)。 |
int | size() 返回此集合中的元素数。 |
default Spliterator<E> | spliterator() 创建一个Spliterator 在这个集合中的元素。 |
default Stream<E> | stream() 返回以此集合作为源的顺序 Stream 。 |
Object[] | toArray() 返回一个包含此集合中所有元素的数组。 |
<T> T[] | toArray(T[] a) 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 |
- 代码测试
package SetBao;
import org.junit.Test;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
public class CollectionTest {
@Test
public void test1(){
//Collection是接口,所有用ArrayList去测试他接口中定义的抽象方法的具体功能
Collection coll = new ArrayList();
//add(Object e) :将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123); //自动装箱
coll.add(new Date());
//size():获取添加的元素的个数
System.out.println(coll.size()); //4
//addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
coll.addAll(coll1);
System.out.println(coll.size()); //6
System.out.println(coll.toString()); //调ArrayList集合中重写的toString
//clear():清空集合元素
coll.clear();
//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());
}
}
结果:
4
8
[AA, BB, 123, Mon May 10 15:06:59 CST 2021, 456, CC, 456, CC]
true
- Person类
package SetBao;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 方法测试1
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
// Person p = new Person("Jerry", 20);
coll.add(new Person("Jerry", 20));
//1.contains(Object obj):判断当前集合是否包含obj
//我们在判断时会调用集合中对象所在类的equals()
boolean contains=coll.contains(123);
System.out.println(contains); //true
System.out.println(coll.contains(new String("Tom"))); //String重写了equals
System.out.println(coll.contains(new Person("Jerry", 20)));//Person没有重写equals,走的是Object的方法
//2.containAll(Collection coll1):判断形参coll1中的所有元素是否都在当前集合中
Collection coll1 = Arrays.asList(123,456,new String("Tom"));
boolean b = coll.containsAll(coll1);
System.out.println(b);//true
Collection coll2 = new ArrayList();
coll2.add(123); //自动装箱变成对象
coll2.add(456); //自动装箱变成对象
coll2.add(new String("Tom"));
boolean b1 = coll.containsAll(coll2);
System.out.println(b1);//true
}
结果:
true
true
false
true
true
- 测试方法2
@Test
public void test2(){
//3.remove(Object obj):从当前集合中删除obj元素
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
boolean c = coll.remove(123);//也会调用equals去找看有没有
System.out.println(c);//true 移除成功
System.out.println(coll);//[456, Tom, false, Person{name='Jerry', age=20}]
coll.remove(new String("Tom"));
System.out.println(coll);//[456, false, Person{name='Jerry', age=20}]
boolean b2 = coll.remove(new Person("Jerry", 20));//Person重写equals()后才会移除成功
System.out.println(coll);//[456, false, Person{name='Jerry', age=20}]
System.out.println(b2);//false;
//4.removeAll(Collection coll):差集:从当前集合中移除coll1中所有元素(即移除交集部分)
Collection coll1=Arrays.asList(456,789);
coll.removeAll(coll1);
System.out.println(coll);//[false, Person{name='Jerry', age=20}]
}
结果:
true
[456, Tom, false, Person{name='Jerry', age=20}]
[456, false, Person{name='Jerry', age=20}]
[456, false, Person{name='Jerry', age=20}]
false
[false, Person{name='Jerry', age=20}]
Process finished with exit code 0
- 方法测试3
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
// Collection coll1=Arrays.asList(456,789);
//5. retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合(应该说修改coll集合)
// boolean b = coll.retainAll(coll1);
// System.out.println(b); //true 返回的都是true,只要能求交集,不管有没有元素,返回都是true
// System.out.println(coll); //[456]
//6.equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同
Collection coll1 = new ArrayList();
coll1.add(456); //自动装箱变成对象
coll1.add(123); //自动装箱变成对象
coll1.add(new String("Tom"));
coll1.add(false);
System.out.println(coll.equals(coll1)); //位置必须也一样,是有序的,因为右边是ArrayList
}
结果:
false
- 测试4
@Test
public void test4(){
Collection coll1 = new ArrayList();
coll1.add(456); //自动装箱变成对象
coll1.add(123); //自动装箱变成对象
coll1.add(new String("Tom"));
coll1.add(false);
//7.hasCode():返回当前对象的哈希值
System.out.println(coll1.hashCode());
//8.集合转换为--->数组:toArray()
coll1.toArray();
Object[] arr=coll1.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("++++++++++++++++++");
//拓展:数组--->集合:调用Arrays类的静态方法asList()
List<Object> objects = Arrays.asList(new Object[]{"AA", "BB", "CC"});
List<String> s = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(objects);//[AA, BB, CC]
System.out.println(s);//[AA, BB, CC]
List arr1= Arrays.asList(new int[]{123,456});
System.out.println(arr1.size()); //1 他认为上面的是一个元素
List arr2= Arrays.asList(new Integer[]{123,456});
System.out.println(arr2.size()); //2 包装类的时候就可以被认为是两个元素
}
结果:
17240151
456
123
Tom
false
++++++++++++++++++
[AA, BB, CC]
[AA, BB, CC]
1
2
- 方法测试iterator():返回Iterator接口的实例,用于遍历集合元素,放在IteratorTest.Java中
Iterator迭代器接口
-
概述:
Iterator对象称为迭代器(设计模式的一种)主要用于遍历Collection集合中的元素.
GOF给迭代器模式的定义为:提供一个方法访问一个容器(contaireer)对象中各个元素,二又不需暴露该对象的内部细节,迭代器模式,就是为容器而生,类似于"公交车上的售票员",“火车上的乘务员”,“空姐”.
Iterator 仅用于遍历集合,Iterator本身并不提供封装对象的能力,如果需要创建Iterator对象,则必须有一个被迭代的集合
集合对象每次调用iterator()方法都会得到一个全新的迭代器对象,默认游标都在集合的第一元素之前
-
内部方法 :hasNext()和next()
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
Iterator iterator = coll.iterator();
//方式一:
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
//报异常,NoSuchElementException
// System.out.println(iterator.next());
//方式二:不推荐
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }
//方式三:推荐
while ((iterator.hasNext())){
System.out.println(iterator.next());
}
}
结果:
123
456
Tom
false
Person{name='Jerry', age=20}
- 错误用法
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
//错误方式一:NoSuchElementException
Iterator iterator = coll.iterator();
// while (iterator.next()!=null){
// System.out.println(iterator.next());
// }
// //错误 不断出现123
// while (coll.iterator().hasNext()){
// System.out.println(coll.iterator().next());
// }
}
- 测试Iterator的remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用remove() ,但两者都是真正把元素在coll中移除了.
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
//删除集合中"Tom"
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object object = iterator.next();
//删除集合中
if("Tom".equals(object))
iterator.remove();
}
System.out.println("=========重新遍历=======");
Iterator iterator1 = coll.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
System.out.println(coll);
}
-
注意一些细节:
如果还未调用next()或在上一次调用next()方法之后已经调用了remove方法,再调用
remove都会报IllegaLStateException.
foreach
-
jdk5.0新增了foreach循环,用于遍历集合、数组
-
遍历集合
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123); //自动装箱变成对象
coll.add(456); //自动装箱变成对象
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
//for(集合中元素类型 局部变量: 集合对象
//内部仍然调用了迭代器
for (Object obj: coll ) {
System.out.println(obj);
}
}
结果:
123
456
Tom
false
Person{name='Jerry', age=20}
- 遍历数组
@Test
public void test2(){
int[] arr=new int[]{1,2,3,4,5};
//for(数组元素的类型 局部变量:数组对象)
for (int i:arr) {
System.out.println(i);
}
}
结果:
1
2
3
4
5
- 练习题
@Test
public void test3(){
String [] arr=new String[]{"MM","MM","MM"};
//方式一:普通for循环
for (int i = 0; i < arr.length; i++) {
arr[i]="GG";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]); //GG
}
//方式二:增强for循环
for (String s:arr
) {
s="MM"; //改的是s,没有该arr里面的,重新赋s的值
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
结果:
GG
GG
GG
GG
GG
GG
Collection子接口一:List
概述
List接口的三个实现类
ArrayList LinkedList Vector
面试题:Arrayay、LinkedList、Vector三者的异同
List(1.2出现)的实现类 | 相同点 |
---|---|
ArrayList | 实现List接口、存储有序的、可重复的数据 |
LinkedList | 实现List接口、存储有序的、可重复的数据 |
Vector | 实现List接口、存储有序的、可重复的数据 |
|----Collection接口:单例集合,用来存储一个一个对象
|----List(1.2才有)接口:存储有序的、可重复的数据 -->"动态"数组
|----ArrayLis:作为list接口的主要实现类:线程不安全的、效率高;底层使用Object[]存储 ,源码:transient Object[] elementData; // non-private to simplify nested class access
|----LinkList:对于频繁的插入、删除操作、使用此类效率比ArrayList高;底层使用双向链表存储.
|----Vector:作为List接口的古老实现类(1.0就有);线程安全,效率低;底层使用Object[]存储 源码:protected Object[] elementData;
- LinkList底层源码(用双向链表存储数据)
/**
* Pointer to first node. //指向第一个结点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node. //指向最后个节点
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
ArrayList源码分析
- JDK7的ArrayList源码
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)
- JDK8中ArrayList的变化:
ArrayList list=new ArrayList(); //底层Object[]elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData[0]
…
后续的添加和扩容操作与JDK7无异
- 调用空参构造器的时候并没有把数组实例化,而JDK7调用空参构造器的时候直接实例化了,构建数组的数组长度是10,一定程度上浪费了内存
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- 调用add方法的时候 ,会先去看数组长度够不够
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) { //第一次add时候 minCapacity=1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
- 如果elementData=={},那就返回DEFAULT_CAPACITY, minCapacity最大值,就是10,不空就返回minCapacity,然后返回的值作为 ensureExplicitCapacity()括号里面的参数
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //DEFAULTCAPACITY_EMPTY_ELEMENTDATA)={}
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
- 然后 ensureExplicitCapacity()判断当前值是不是大于当前数组大小,大于就进行grow扩容,不大于什么也不做.
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 扩容大小和jdk7一样也是原来的1.5倍,grow(int minCapacity)
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*增加这容量确保他可以容纳至少这期望的最小容纳数量
* @param minCapacity the desired minimum capacity
* 参数 :minCapacity 这期望最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容1.5倍
if (newCapacity - minCapacity < 0) //如果新的容量小于期望的容量 那么直接newCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //扩容后的值大于这个MAX_ARRAY_SIZE,这个MAX_ARRAY_SIZE大小为Integer.MAX_VALUE - 8时候走下面语句
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- hugeCapacity(int minCapacity)方法,期望的值是小于0直接抛出超出内存溢出异常,否则返回三元表达式对应的结果值.
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- 小结:jdk7中的arrayList的对象创建类似于单例的饿汉式,而jdk8中的arrayList的对象创建类似于单例的懒汉式,延迟了数组的创建,节省内存.
LinkedList源码
LinkedList linkedList= 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, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
- 调用add方法的时候,会调用linkLast方法
public boolean add(E e) {
linkLast(e);
return true;
}
- linkLast方法(代码其实非常好理解,学好数据结构看源码就会轻松好多)
transient Node<E> first; //指向第一个结点
transient Node<E> last; //指向最后一个节点
void linkLast(E e) {
final Node<E> l = last; //先用个新结点保存最后的结点
final Node<E> newNode = new Node<>(l, e, null); //然后建立个节点,让他的前一个节点为l(新创建的prev指向前面的元素,该元素可能为空),值为e,后一个节点为null
last = newNode;
if (l == null) //原链表当前没有节点的时候
first = newNode; //那么新创建的结点就是第一个节点
else //原链表有结点
l.next = newNode; //那么让原链表和新创的节点连接起来,让l的next指向新链表,新链表的prev已经在创建的时候指过了
size++;
modCount++;
}
Vector源码分析
Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认扩容为原来的数组长度的2倍
- 构造器
public Vector() {
this(10); //初始数组长度为10
}
- 调用add操作
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 扩容操作 (扩容为原来的二倍)
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity); //capacityIncrement空参构造时候为0
//capacityIncrement有参构造指定的时候会按照这个值扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
List接口常用方法
Modifier and Type | Method and Description |
---|---|
void | add(int index, E element) 将指定的元素插入此列表中的指定位置(可选操作)。 |
boolean | addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。 |
E | get(int index) 返回此列表中指定位置的元素。 |
int | indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 |
int | lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
E | remove(int index) 删除该列表中指定位置的元素(可选操作)。 |
E | set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素。 |
List<E> | subList(int fromIndex, int toIndex) 返回此列表中指定的 fromIndex (含)和 toIndex 之间的视图。 |
- 方法测试1
@Test
public void test(){
ArrayList<Object> list = new ArrayList<>();
list.add(123);
list.add("AA");
list.add(new Person("Tom",18));
System.out.println(list);
//add(int index, E element)`将指定的元素插入此列表中的指定位置(可选操作)。
list.add(1,"BB");
System.out.println(list);
List<Integer> list1 = Arrays.asList(1, 2, 3);
//addAll(int index, Collection<? extends E> c)将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。
list.addAll(1,list1); //没索引时候默认为末尾添加
System.out.println(list);
//get(int index)返回此列表中指定位置的元素。
int o = (int) list.get(1);
System.out.println(o);
}
结果:
[123, AA, Person{name='Tom', age=18}]
[123, BB, AA, Person{name='Tom', age=18}]
[123, 1, 2, 3, BB, AA, Person{name='Tom', age=18}]
1
- 测试2
@Test
public void test2(){
ArrayList<Object> list = new ArrayList<>();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",18));
int i = list.indexOf(456);
//int indexOf(Object obj):返回obj在集合中首次出现的位置,不存在返回-1
int j = list.indexOf(3456); //找不到返回-1
System.out.println("i = "+i+" j ="+j);
//lastIndexOf(Object o)返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
//remove(int index)删除该列表中指定位置的元素(可选操作),并返会删除的元素
//没找到肯定是越界异常,因为指定的index没有
Object obj = list.remove(0);
System.out.println(obj);
System.out.println(list);
//set(int index, E element)用指定的元素(可选操作)替换此列表中指定位置的元素。
list.set(1,"CC");
System.out.println(list);
//subList(int fromIndex, int toIndex)返回此列表中指定的fromIndex(含)和toIndex`之间的元素。
List subList = list.subList(1, 3);
System.out.println(subList);
System.out.println(list);
}
结果:
i = 1 j =-1
123
[456, AA, Person{name='Tom', age=18}]
[456, CC, Person{name='Tom', age=18}]
[CC, Person{name='Tom', age=18}]
[456, CC, Person{name='Tom', age=18}]
- 遍历List
@Test
public void test3(){
ArrayList<Object> list = new ArrayList<>();
list.add(123);
list.add(456);
list.add("AA");
//方式一:Iterator
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
//方式二:foreach循环
System.out.println("++++++++二++++++++");
for (Object o:
list) {
System.out.println(o);
}
//方式三:普通的for循环
System.out.println("++++++++三++++++++");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
结果:
123
456
AA
++++++++二++++++++
123
456
AA
++++++++三++++++++
123
456
AA
- 总结:常用方法
- 增:add(Object obj)
- 删:remove(int index)
- 改:set(int index, E element)
- 查:get(int index)
- 插:add(int index,Object ele)
- 长度:size()
- 遍历:①Iterator迭代器的方式②增强for循环③普通的循环
面试小题
- 结果是什么?
@Test
public void test(){
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
private static void updateList(List list){
Integer a=2;
list.remove(a); //也可以直接写成list.remove(new Integer(2))
}
答案:
[1,3]
解释:a是个对象,通过remove移除的是对象为a的元素,所以是[1,3]
- 改成这样结果又会是怎样?
@Test
public void test(){
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
private static void updateList(List list){
list.remove(2);
}
结果:
[1,2]
解释:移除的是下标为2位置的元素,因为他可以不装箱成类就找到,所以就不会去装箱,会把当做索引
- 那么这个结果你应该也知道了吧?
@Test
public void test(){
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(8);
updateList(list);
System.out.println(list);
}
private static void updateList(List list){
list.remove(8);
}
结果:IndexOutOfBoundsException
当然出错了,他找的是8下标,而不是对象,当数字能不自动装箱他肯定不自动装箱.
- 这个结果你清楚么?
@Test
public void test(){
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(8);
updateList(list);
System.out.println(list);
}
private static void updateList(List list){
boolean remove = list.remove("8"); //没有字符串8
System.out.println(remove); //false
}
结果:
false
[1, 2, 8]
- 所以我们要正确区分list中的remove(int index)和remove(Object obj)
Collection子接口二:Set
|----Collection接口:单例集合,用来存储一个一个对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的"集合"
|----HashSet:作为Set接口的主要实现类:线程不安全的,可以存储null值.
|----LinkedHashSet:作为hashSet的子类,遍历其内部数据时,可以按照添加的顺序去遍历.对于频繁的插入和遍历操作,LinkedHashSet效率高于HashSet
|----TreeSet:可以按照添加对象指定属性,进行排序.
- new对象时候,底层实现还是对应的Map
HashSet
-
Set接口没有额外定义新的方法,使用的都是Collection中 声明过的方法.
. -
Set存储无序性、不可重复的数据
一以HashSet为列说明
- 无序性:不等于随机性.存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的.
- 不可重复性:保证添加的元素按照equals判断时候不能反回true,即相同的元素只能添加一个
-
添加元素的过程:以HashSet为例:
-
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:岁哦因为值),判断此位置上是否已经有元素,
- 如果此位置上没有元素,则元素a添加成功. —>情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
- 如果hash值不相同,则元素a添加成功, —>情况2
- 如果hash值相同,进而需要调用元素a所在类的equals()方法,equals返回true,元素a添加失败,返回false,则元素a添加成功 —>情况3
-
对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上的数据以链表形式存储.
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:7上8下
-
HashSet:底层就是数组+链表的结构
- 要求:向HashSet中添加的数据,其所在的类一定要重写hashCode()和equals()方法
- 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具备相等的散列码.
- 重写的两个小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode()值. 尽量用idea自动生成的一般就够用了
LinkedHashSet
//LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
//优点:对于频繁的插入和遍历操作,LinkedHashSet效率高于HashSet
@Test
public void test(){
Set set = new LinkedHashSet();
set.add(456);
set.add("AA");
set.add("CC");
set.add("oo");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
- 结果是和添加顺序一样的,但这不叫做有序,只是LInkedHashSet里面有引用存上一个和下一个.
456
AA
CC
oo
TreetSet(采用红黑树存储结构)
- 向TreetSet中添加的数据,要求是相同类的对象
- 两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0、不再是equals方法了
- 定制排序中,比较两个对象是否相同的标准为:comparte()返回0,不再是equals方法了
- 测试 User类
public class User implements Comparable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从小到大排序,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user=(User)o;
//return this.name.compareTo(user.name);
//可以进行二级排序
int compare=this.name.compareTo(user.name);
if(compare!=0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
- 测试代码
@Test
public void test(){
Set set = new TreeSet();
//失败:不能添加不同类的对象
// set.add(456);
// set.add("AA");
//举例一:
// set.add(123);
// set.add(456);
// set.add(43);
// set.add(11);
// set.add(8);
// System.out.println(set); //[8, 11, 43, 123, 456]
//举例二:
// set.add("b");
// set.add("A");
// set.add("B");
// set.add("aD");
// set.add("aC"); //按照字典序进行拍的,先比较的是第一个字母
// System.out.println(set); //[A, B, aC, aDbb, b]
//举例三 User不去实现Comparable时候会报错,
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Jack",33));
set.add(new User("Jack",56)); //如果没有自定义年龄排序,那么这条数据将会失去
System.out.println(set); //[User{name='Jack', age=33}, User{name='Jack', age=56}, User{name='Jerry', age=32}, User{name='Jim', age=2}, User{name='Tom', age=12}]
}
- 定制排序
@Test
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User&&o2 instanceof User){
User u1=(User)o1;
User u2=(User)o2;
return Integer.compare(u1.getAge(),u2.getAge());//user方法去增加下get set方法
}else
throw new RuntimeException("输入的数据类型不匹配");
}
};
TreeSet set= new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Jack",33));
set.add(new User("adc",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
结果:
User{name='Jim', age=2}
User{name='Tom', age=12}
User{name='Jerry', age=32}
User{name='Jack', age=33}
User{name='Jack', age=56}
面试题
- Person类重写有hashCode和equals方法
public class Person {
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
- Test
public class Test {
public static void main(String[] args) {
HashSet<Object> set = new HashSet<>();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.setName("CC");
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);
}
}
结果可能会有一点出人意料,但是也在情理之中
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'},Person{id=1001, name='CC'}, Person{id=1001,name='AA'}]
- 解释
- set添加元素的时候按照的是hashCode和equals方法添加的,前两个元素添加好后,把p1 name改了,在调用移除元素的方法的时候,底层是根据改后的name和原来的id计算hashCode值去找对应位置是否有元素,没有就不删除,有元素那就看equals方法调用是否相同,相同了删除,不相同了不删除.
- 那么第二个输出语句就不难理解了,两者的hashCode值不一样,都会存在set集合中
- 第三个,根据添加的元素算出来的hashCode值的时候对应位置有元素,然后通过equals方法判断元素是否相同,相同了就不需要再重复加了,不同那么就会加进去,显然第三个和改过name后的p1是不相同的,所以照样会添加成功.
Map接口
一:Map的实现类的结构情况
|----Map:双列数据 存储key-value对的数据 ----类似于高中的函数:y=f(x)
|----HashMap:作为Map的主要实现类:线程不安全的,效率高;可以存储null的key和value
|----LinkedHashMap:保证在遍历map元素的时候,可以按照添加顺序实现遍历.
原因:在原有的HashMap底层的基础上,添加了一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于HashMap
|----TreeMap:可以按照添加的key-value对进行排序,实现排序遍历.此时考虑key的自然排序和定制排序
底层使用红黑树
|----Hashtable:作为古老的实现类:线程安全的,效率低,不能存储null的key和value
|----Properties:常用类处理配置文件key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树(jdk8)
- 测试key value是否可以存null
@Test
public void test(){
HashMap<Object, Object> map = new HashMap<>();
Hashtable<Object, Object> hashtable = new Hashtable<>();
map.put(null,null);
map.put(null,1);
// hashtable.put(null,1);//报异常
// hashtable.put(1,null);//报异常
}
Map结构的理解
Map中的key:无序的、 不可重复的,使用set存储所有的key —>key所在的类要重写equals()方法和hashCode()方法 (以hashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value–>value所在的类要重写equals()根据规定一般也要写hashCode
一个键值对:key-value构成了一个Entry对象
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
HashMap的底层实现原理?
-
Jdk7为例说明:
* HashMap map=new HashMap(); 在实例化以后,底层创建了长度是16的一维数组Entry[] table. ......可能已经执行多次put... map.put(key1,value1) 首先,调用key1所在类的hashCode()计算key1哈希值,次哈希值经过某种算法计算以后,得到在Entry数组中的存放位置. 如果此位置上数据为空,此时key1-value1添加成功, --->情况1 如果此位置上数据不为空,(意味着此位置上存在一个或多个数据(以链表的形式存在)),比较当前的key1和已经存在的一个或多个数据的哈希值,: 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功 --->情况2 如果key1的哈希值和某个已经存在的数据(key2-value2)哈希值相同了,调用key1所在类的equals()方法,比较: 如果equals()返回false: 此时key1-value1添加成功 --->情况3 如果equals()返回true:使用value1替换相同key的value值 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储 在不断的添加过程中:会涉及扩容问题,当超出临界值(且要存放的位置非空时),扩容,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
jdk8 相较于jdk7在底层实现方面的不同
1. new hashMap():在底层没有创建一个长度为16的Entry数组
2. jdk 8底层的数组是: Node[],而非Entry[]
3. 首次调用put方法的时候,底层创建长度为16的数组
4.jdk7底层结构只有:数组+链表.jdk8中底层结构:数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8,且当前数组的长度>64时,
此时此索引位置上的所有数据改为使用红黑树存储.
- 重要常量:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 HashMap的默认容量16
static final int MAXIMUM_CAPACITY = 1 << 30;//HashMap最大支持容量 2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;//HashMap的默认加载因子
int threshold; // 扩容的临界值,=容量*填充因子,首次扩容时候是0.75*16=12
static final int TREEIFY_THRESHOLD = 8;//Bucket中链表长度大于该默认值,转为红黑树
static final int MIN_TREEIFY_CAPACITY = 64; //桶中的Node被树化时最小的hash表容量,当桶中Node的数量达到需要变成红黑树的时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应该执行resize扩容操作 这个MIN_TREEIFY_CAPACITY值至少是TREEIFY_THRESHOLD的4倍
jdk7的hashMap
jdk8的hashMap
- 构造器
public HashMap() {
//加载因子赋值为0.75
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
- put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
- putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//声明tab为Node类型
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//如果首次调用,那么||前面就是true,就会进入下面语句进行扩容
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;
}
- resize()方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; 第一次时时null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap=0
int oldThr = threshold; //oldThr=0
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; //把默认16赋值给newCap
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //16*0.75=12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; //12
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //造数组
table = newTab; //造好的数组给table
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
- treeifyBin()
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
LInkedHashMap底层实现原理(了解)
public class LinkedHashMap<K,V>
extends HashMap<K,V>
调用put方法时候用的还是hashMap的,然后putValue也是hashMap中的,但是他把putVal中用到的newNode(参数…)这个方法重写了
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
hashMap中的内部类: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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
LinkedHashMap中的内部类:Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; //能够记录添加元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
Map中定义的方法
Modifier and Type | Method and Description |
---|---|
void | clear() 从该地图中删除所有的映射(可选操作)。 |
default V | compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 尝试计算指定键的映射及其当前映射的值(如果没有当前映射, null )。 |
default V | computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null 。 |
default V | computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射。 |
boolean | containsKey(Object key) 如果此映射包含指定键的映射,则返回 true 。 |
boolean | containsValue(Object value) 如果此地图将一个或多个键映射到指定的值,则返回 true 。 |
Set<Map.Entry<K,V>> | entrySet() 返回此地图中包含的映射的Set 视图。 |
boolean | equals(Object o) 将指定的对象与此映射进行比较以获得相等性。 |
default void | forEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。 |
V | get(Object key) 返回到指定键所映射的值,或 null 如果此映射包含该键的映射。 |
default V | getOrDefault(Object key, V defaultValue) 返回到指定键所映射的值,或 defaultValue 如果此映射包含该键的映射。 |
int | hashCode() 返回此地图的哈希码值。 |
boolean | isEmpty() 如果此地图不包含键值映射,则返回 true 。 |
Set<K> | keySet() 返回此地图中包含的键的Set 视图。 |
default V | merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。 |
V | put(K key, V value) 将指定的值与该映射中的指定键相关联(可选操作)。 |
void | putAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此映射(可选操作)。 |
default V | putIfAbsent(K key, V value) 如果指定的键尚未与某个值相关联(或映射到 null )将其与给定值相关联并返回 null ,否则返回当前值。 |
V | remove(Object key) 如果存在(从可选的操作),从该地图中删除一个键的映射。 |
default boolean | remove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目。 |
default V | replace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。 |
default boolean | replace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目。 |
default void | replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。 |
int | size() 返回此地图中键值映射的数量。 |
Collection<V> | values() 返回此地图中包含的值的Collection 视图。 |
- 添加删除修改操作
@Test
public void test(){
HashMap<Object, Object> map = new HashMap<>();
//添加
map.put("AA",123);
map.put(45,123);
//修改
map.put("AA",321);
System.out.println(map);
HashMap<Object, Object> map1 = new HashMap<>();
map1.put("CC",123);
map1.put("DD",123);
map.putAll(map1);
System.out.println(map);
//remove(Object key)
Object value = map.remove("CC"); //没有返回就是null
System.out.println(value); //CC对应的value
System.out.println(map);
//clear()
map.clear(); //map=null操作不同
System.out.println(map.size());
System.out.println(map);
}
结果:
{AA=321, 45=123}
{AA=321, CC=123, DD=123, 45=123}
123
{AA=321, DD=123, 45=123}
0
{}
- 判空 查key value是否存在
@Test
public void test2(){
HashMap<Object, Object> map = new HashMap<>();
map.put("AA",123);
map.put(45,123);
map.put("AA",321);
//Object get(Object key)
System.out.println(map.get(45)); //123
//`boolean containsKey(Object key)`
boolean isExist = map.containsKey("88");
System.out.println(isExist); //false
// containsValue(Object value)
System.out.println(map.containsValue(123)); //true
map.clear();
System.out.println(map.isEmpty()); //true
}
- 遍历
@Test
public void test3(){
HashMap map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("AA",321);
System.out.println(map);
System.out.println("====");
//遍历所有的key集:keySet
Set set=map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("====");
//遍历所有的value
Collection values = map.values();
for (Object obj : values) {
System.out.println(obj);
}
System.out.println("====");
//遍历所有的key-value
//方式一:entrySet
Set entrySet = map.entrySet();
System.out.println(entrySet);
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
//entrySet集合中的元素都是entry
Map.Entry entry= (Map.Entry) iterator1.next();
System.out.println(entry.getKey()+"--->"+entry.getValue());
// System.out.println(iterator1.next());
}
System.out.println("===方式二===");
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while (iterator2.hasNext()) {
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key+"--->"+value);
}
}
TreeMap
向TreeMap中添加key-value,要求key必须是同一个类创建的对象
因为要按照key进行排序:自然排序、定制排序
- User类
package SetBao.ti;
public class User implements Comparable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从小到大排序,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user=(User)o;
//return this.name.compareTo(user.name);
//可以进行二级排序
int compare=this.name.compareTo(user.name);
if(compare!=0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
- 自然排序
//自然排序
@Test
public void test4(){
TreeMap<Object, Object> map = new TreeMap<>();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
//entrySet集合中的元素都是entry
Map.Entry entry= (Map.Entry) iterator1.next();
System.out.println(entry.getKey()+"--->"+entry.getValue());
// System.out.println(iterator1.next());
}
}
结果:
User{name='Jack', age=20}--->76
User{name='Jerry', age=32}--->89
User{name='Rose', age=18}--->100
User{name='Tom', age=23}--->98
- 定制排序
//定制排序
@Test
public void test5(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User&& o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("输入的类型不匹配");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
//entrySet集合中的元素都是entry
Map.Entry entry= (Map.Entry) iterator1.next();
System.out.println(entry.getKey()+"--->"+entry.getValue());
// System.out.println(iterator1.next());
}
}
结果:
User{name='Rose', age=18}--->100
User{name='Jack', age=20}--->76
User{name='Tom', age=23}--->98
User{name='Jerry', age=32}--->89
Hashtable子类:Properties
public class PropertiesTest {
public static void main(String[] args) throws IOException {
Properties pros= new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis); //加载流对应的文件
String name = pros.getProperty("name");
System.out.println(name);
String password1 = pros.getProperty("password");
System.out.println(password1);
Object password = pros.get("password");
System.out.println(password);
}
}
结果:
Tom
123456
123456
面试题
- HashMap的底层实现原理?
- HashMap 和Hashtable的异同?
- CurrentHashMap(有分段锁的技术)与HashTable的异同?(暂时不讲)
- 你对put/get方法的认识?扩容机制,负载因子,吞吐临界值?
Collections工具类
-
Collections:
操作Collection、Map的工具类
-
面试题:Collection 和Collections的区别?
一个是接口,一个是类,Collections是操作Collection的工具类
- reverse(List):反转List中元素的顺序
- shuffle(Life):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List集合元素按升序排列
- sort(List,Comparator):根据指定的Compartor产生的顺序对List集合进行排序
- swarp(List,int ,int):将指定List集合中的i处元素和j处元素进行交换
@Test
public void test(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(76);
list.add(76);
list.add(-92);
System.out.println(list);
//
//随机排序
// Collections.shuffle(list);
// System.out.println(list);
Collections.swap(list,1,2);
System.out.println(list);
int frequency = Collections.frequency(list, 76);
System.out.println(frequency); //2 某个数出现的频率
}
- 复制操作
@Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
//错误写法,报异常了:IndexOutOfBoundsException: Source does not fit in dest
// List dest=new ArrayList();
// Collections.copy(dest,list);
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());
Collections.copy(dest,list);
System.out.println(dest);
}
- Collections 类汇总提供了多个 synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时线程安全问题,也可以操作Map
@Test
public void test3(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
}