国内做网站用的程序,建设通是个什么网站,营销推广的目标,短期设计培训班Java集合类
集合类
集合类其实就是为了更好地组织、管理和操作我们的数据而存在的#xff0c;包括列表、集合、队列、映射等数据结构。
集合根接口
Java中已经帮我们将常用的集合类型都实现好了#xff0c;我们只需要直接拿来用就行了
所有的集合类最终都是实现自集合根…Java集合类
集合类
集合类其实就是为了更好地组织、管理和操作我们的数据而存在的包括列表、集合、队列、映射等数据结构。
集合根接口
Java中已经帮我们将常用的集合类型都实现好了我们只需要直接拿来用就行了
所有的集合类最终都是实现自集合根接口的比如我们下面就会讲到的ArrayList类它的祖先就是Collection接口 这个接口定义了集合类的一些基本操作
public interface CollectionE extends IterableE {//-------这些是查询相关的操作----------//获取当前集合中的元素数量int size();//查看当前集合是否为空boolean isEmpty();//查询当前集合中是否包含某个元素boolean contains(Object o);//返回当前集合的迭代器我们会在后面介绍IteratorE iterator();//将集合转换为数组的形式Object[] toArray();//支持泛型的数组转换同上T T[] toArray(T[] a);//-------这些是修改相关的操作----------//向集合中添加元素不同的集合类具体实现可能会对插入的元素有要求//这个操作并不是一定会添加成功所以添加成功返回true否则返回falseboolean add(E e);//从集合中移除某个元素同样的移除成功返回true否则falseboolean remove(Object o);//-------这些是批量执行的操作----------//查询当前集合是否包含给定集合中所有的元素//从数学角度来说就是看给定集合是不是当前集合的子集boolean containsAll(Collection? c);//添加给定集合中所有的元素//从数学角度来说就是将当前集合变成当前集合与给定集合的并集//添加成功返回true否则返回falseboolean addAll(Collection? extends E c);//移除给定集合中出现的所有元素如果某个元素在当前集合中不存在那么忽略这个元素//从数学角度来说就是求当前集合与给定集合的差集//移除成功返回true否则falseboolean removeAll(Collection? c);//Java8新增方法根据给定的Predicate条件进行元素移除操作default boolean removeIf(Predicate? super E filter) {Objects.requireNonNull(filter);boolean removed false;final IteratorE each iterator(); //这里用到了迭代器我们会在后面进行介绍while (each.hasNext()) {if (filter.test(each.next())) {each.remove();removed true;}}return removed;}//只保留当前集合中在给定集合中出现的元素其他元素一律移除//从数学角度来说就是求当前集合与给定集合的交集//移除成功返回true否则falseboolean retainAll(Collection? c);//清空整个集合删除所有元素void clear();//-------这些是比较以及哈希计算相关的操作----------//判断两个集合是否相等boolean equals(Object o);//计算当前整个集合对象的哈希值int hashCode();//与迭代器作用相同但是是并行执行的我们会在下一章多线程部分中进行介绍Overridedefault SpliteratorE spliterator() {return Spliterators.spliterator(this, 0);}//生成当前集合的流我们会在后面进行讲解default StreamE stream() {return StreamSupport.stream(spliterator(), false);}//生成当前集合的并行流我们会在下一章多线程部分中进行介绍default StreamE parallelStream() {return StreamSupport.stream(spliterator(), true);}
}List列表
List列表线性表线性表支持随机访问相比之前的Collection接口定义功能还会更多一些。
ArrayList的底层是用数组实现的内部维护的是一个可动态进行扩容的数组也就是顺序表跟我们之前自己写的ArrayList相比它更加的规范并且功能更加强大同时实现自List接口。 List是集合类型的一个分支它的主要特性有
是一个有序的集合插入元素默认是插入到尾部按顺序从前往后存放每个元素都有一个自己的下标位置列表中允许存在重复元素
List接口中定义了列表类型需要支持的全部操作List直接继承自前面介绍的Collection接口其中很多地方重新定义了一次Collection接口中定义的方法这样做是为了更加明确方法的具体功能
//List是一个有序的集合类每个元素都有一个自己的下标位置
//List中可插入重复元素
//针对于这些特性扩展了Collection接口中一些额外的操作
public interface ListE extends CollectionE {...//将给定集合中所有元素插入到当前结合的给定位置上后面的元素就被挤到后面去了跟我们之前顺序表的插入是一样的boolean addAll(int index, Collection? extends E c);...//Java 8新增方法可以对列表中每个元素都进行处理并将元素替换为处理之后的结果default void replaceAll(UnaryOperatorE operator) {Objects.requireNonNull(operator);final ListIteratorE li this.listIterator(); //这里同样用到了迭代器while (li.hasNext()) {li.set(operator.apply(li.next()));}}//对当前集合按照给定的规则进行排序操作这里同样只需要一个Comparator就行了SuppressWarnings({unchecked, rawtypes})default void sort(Comparator? super E c) {Object[] a this.toArray();Arrays.sort(a, (Comparator) c);ListIteratorE i this.listIterator();for (Object e : a) {i.next();i.set((E) e);}}...//-------- 这些是List中独特的位置直接访问操作 --------//获取对应下标位置上的元素E get(int index);//直接将对应位置上的元素替换为给定元素E set(int index, E element);//在指定位置上插入元素就跟我们之前的顺序表插入是一样的void add(int index, E element);//移除指定位置上的元素E remove(int index);//------- 这些是List中独特的搜索操作 -------//查询某个元素在当前列表中的第一次出现的下标位置int indexOf(Object o);//查询某个元素在当前列表中的最后一次出现的下标位置int lastIndexOf(Object o);//------- 这些是List的专用迭代器 -------//迭代器我们会在下一个部分讲解ListIteratorE listIterator();//迭代器我们会在下一个部分讲解ListIteratorE listIterator(int index);//------- 这些是List的特殊转换 -------//返回当前集合在指定范围内的子集ListE subList(int fromIndex, int toIndex);...
}ArrayList基本实现
public class ArrayListE extends AbstractListEimplements ListE, RandomAccess, Cloneable, java.io.Serializable
{//默认的数组容量private static final int DEFAULT_CAPACITY 10;...//存放数据的底层数组这里的transient关键字我们会在后面I/O中介绍用途transient Object[] elementData;//记录当前数组元素数的private int size;//这是ArrayList的其中一个构造方法public ArrayList(int initialCapacity) {if (initialCapacity 0) {this.elementData new Object[initialCapacity]; //根据初始化大小创建当前列表} else if (initialCapacity 0) {this.elementData EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException(Illegal Capacity: initialCapacity);}}...public boolean add(E e) {ensureCapacityInternal(size 1); // 这里会判断容量是否充足不充足需要扩容elementData[size] e;return true;}...//默认的列表最大长度为Integer.MAX_VALUE - 8//JVM都C实现中在数组的对象头中有一个_length字段用于记录数组的长//度所以这个8就是存了数组_length字段这个只做了解就行private static final int MAX_ARRAY_SIZE Integer.MAX_VALUE - 8;private void grow(int minCapacity) {int oldCapacity elementData.length;int newCapacity oldCapacity (oldCapacity 1); //扩容规则跟我们之前的是一样的也是1.5倍if (newCapacity - minCapacity 0) //要是扩容之后的大小还没最小的大小大那么直接扩容到最小的大小newCapacity minCapacity;if (newCapacity - MAX_ARRAY_SIZE 0) //要是扩容之后比最大的大小还大需要进行大小限制newCapacity hugeCapacity(minCapacity); //调整为限制的大小elementData Arrays.copyOf(elementData, newCapacity); //使用copyOf快速将内容拷贝到扩容后的新数组中并设定为新的elementData底层数组}
}一般的如果我们要使用一个集合类我们会使用接口的引用
public static void main(String[] args) {ListString list new ArrayList(); //使用接口的引用来操作具体的集合类实现是为了方便日后如果我们想要更换不同的集合类实现而且接口中本身就已经定义了主要的方法所以说没必要直接用实现类list.add(科技与狠活); //使用add添加元素list.add(上头啊);System.out.println(list); //打印集合类可以得到一个非常规范的结果
}在使用Integer时要注意传参问题
public static void main(String[] args) {ListInteger list new ArrayList();list.add(10); //添加Integer的值10list.remove((Integer) 10); //注意不能直接用10默认情况下会认为传入的是int类型值删除的是下标为10的元素我们这里要删除的是刚刚传入的值为10的Integer对象System.out.println(list); //可以看到此时元素成功被移除
}快速生成一个只读的List
public static void main(String[] args) {ListString list Arrays.asList(A, B, C); //非常方便System.out.println(list);
}List是只读的不能进行修改操作只能使用获取内容相关的方法否则抛出 UnsupportedOperationException 异常。要生成正常使用的我们可以将这个只读的列表作为参数传入
public static void main(String[] args) {ListString list new ArrayList(Arrays.asList(A, B, C));System.out.println(list);
}LinkedList同样是List的实现类只不过它是采用的链式实现也就是我们之前讲解的链表只不过它是一个双向链表也就是同时保存两个方向
public class LinkedListEextends AbstractSequentialListEimplements ListE, DequeE, Cloneable, java.io.Serializable
{transient int size 0;//引用首结点transient NodeE first;//引用尾结点transient NodeE last;//构造方法很简单直接创建就行了public LinkedList() {}...private static class NodeE { //内部使用的结点类E item;NodeE next; //不仅保存指向下一个结点的引用还保存指向上一个结点的引用NodeE prev;Node(NodeE prev, E element, NodeE next) {this.item element;this.next next;this.prev prev;}}...
}LinkedList的使用和ArrayList的使用几乎相同各项操作的结果也是一样的在什么使用使用ArrayList和LinkedList我们需要结合具体的场景来决定尽可能的扬长避短。
只不过LinkedList不仅可以当做List来使用也可以当做双端队列使用。
迭代器
集合类都是支持使用foreach语法的
public static void main(String[] args) {ListString list Arrays.asList(A, B, C);for (String s : list) { //集合类同样支持这种语法System.out.println(s);}
}编译之后
public static void main(String[] args) {ListString list Arrays.asList(A, B, C);Iterator var2 list.iterator(); //这里使用的是List的迭代器在进行遍历操作while(var2.hasNext()) {String s (String)var2.next();System.out.println(s);}}迭代器默认有一个指向集合中第一个元素的指针。
每一次next操作都会将指针后移一位直到完成每一个元素的遍历此时再调用next将不能再得到下一个元素。
集合类的实现方案有很多可能是链式存储也有可能是数组存储不同的实现有着不同的遍历方式而迭代器则可以将多种多样不同的集合类遍历方式进行统一只需要各个集合类根据自己的情况进行对应实现就行了。
迭代器操作
public interface IteratorE {//看看是否还有下一个元素boolean hasNext();//遍历当前元素并将下一个元素作为待遍历元素E next();//移除上一个被遍历的元素某些集合不支持这种操作default void remove() {throw new UnsupportedOperationException(remove);}//对剩下的元素进行自定义遍历操作default void forEachRemaining(Consumer? super E action) {Objects.requireNonNull(action);while (hasNext())action.accept(next());}
}在Java8提供了一个支持Lambda表达式的forEach方法这个方法接受一个Consumer也就是对遍历的每一个元素进行的操作
public static void main(String[] args) {ListString list Arrays.asList(A, B, C);list.forEach(System.out::println);
}default void forEach(Consumer? super T action) {Objects.requireNonNull(action);for (T t : this) { //foreach语法遍历每一个元素action.accept(t); //调用Consumer的accept来对每一个元素进行消费}
}实际上只要是实现了迭代器接口的类我们自己写的都行都可以使用foreach语法
ListIterator迭代器是针对于List的强化版本增加了更多方便的操作因为List是有序集合所以它支持两种方向的遍历操作不仅能从前向后也可以从后向前
public interface ListIteratorE extends IteratorE {//原本就有的boolean hasNext();//原本就有的E next();//查看前面是否有已经遍历的元素boolean hasPrevious();//跟next相反这里是倒着往回遍历E previous();//返回下一个待遍历元素的下标int nextIndex();//返回上一个已遍历元素的下标int previousIndex();//原本就有的void remove();//将上一个已遍历元素修改为新的元素void set(E e);//在遍历过程中插入新的元素到当前待遍历元素之前void add(E e);
}Queue和Deque
LinkedList除了可以直接当做列表使用之外还可以当做其他的数据结构使用可以看到它不仅仅实现了List接口
public class LinkedListEextends AbstractSequentialListEimplements ListE, DequeE, Cloneable, java.io.Serializable
{Deque接口的继承结构 public interface QueueE extends CollectionE {//队列的添加操作是在队尾进行插入只不过List也是一样的默认都是尾插//如果插入失败会直接抛出异常boolean add(E e);//同样是添加操作但是插入失败不会抛出异常boolean offer(E e);//移除队首元素但是如果队列已经为空那么会抛出异常E remove();//同样是移除队首元素但是如果队列为空会返回nullE poll();//仅获取队首元素不进行出队操作但是如果队列已经为空那么会抛出异常E element();//同样是仅获取队首元素但是如果队列为空会返回nullE peek();
}LinkedList当做一个队列来使用
public static void main(String[] args) {QueueString queue new LinkedList(); //当做队列使用还是很方便的queue.offer(AAA);queue.offer(BBB);System.out.println(queue.poll());System.out.println(queue.poll());
}双端队列就是队列的升级版允许在队列的两端进行入队和出队操作普通队列中从队尾入队队首出队
双端队列既可以当做普通队列使用也可以当做栈来使用
Deque双端队列接口的
//在双端队列中所有的操作都有分别对应队首和队尾的
public interface DequeE extends QueueE {//在队首进行插入操作void addFirst(E e);//在队尾进行插入操作void addLast(E e);boolean offerFirst(E e);boolean offerLast(E e);//在队首进行移除操作E removeFirst();//在队尾进行移除操作E removeLast();E pollFirst();E pollLast();//获取队首元素E getFirst();//获取队尾元素E getLast();E peekFirst();E peekLast();//从队列中删除第一个出现的指定元素boolean removeFirstOccurrence(Object o);//从队列中删除最后一个出现的指定元素boolean removeLastOccurrence(Object o);// *** 队列中继承下来的方法操作是一样的这里就不列出了 ***...// *** 栈相关操作已经帮助我们定义好了 ***//将元素推向栈顶void push(E e);//将元素从栈顶出栈E pop();// *** 集合类中继承的方法这里也不多种介绍了 ***...//生成反向迭代器这个迭代器也是单向的但是是next方法是从后往前进行遍历的IteratorE descendingIterator();}除了LinkedList实现了队列接口之外还有其他的实现类但是并不是很常用
public static void main(String[] args) {DequeString deque new ArrayDeque(); //数组实现的栈和队列QueueString queue new PriorityQueue(); //优先级队列
}优先级队列可以根据每一个元素的优先级对出队顺序进行调整默认情况按照自然顺序
public static void main(String[] args) {QueueInteger queue new PriorityQueue();queue.offer(10);queue.offer(4);queue.offer(5);System.out.println(queue.poll());System.out.println(queue.poll());System.out.println(queue.poll());
}优先级队列并不是队列中所有的元素都是按照优先级排放的优先级队列只能保证出队顺序是按照优先级进行的
Set集合
Set支持的功能其实也就和Collection中定义的差不多只不过
不允许出现重复元素不支持随机访问不允许通过下标访问
public interface SetE extends CollectionE {// Set集合中基本都是从Collection直接继承过来的方法只不过对这些方法有更加特殊的定义int size();boolean isEmpty();boolean contains(Object o);IteratorE iterator();Object[] toArray();T T[] toArray(T[] a);//添加元素只有在当前Set集合中不存在此元素时才会成功如果插入重复元素那么会失败boolean add(E e);//这个同样是删除指定元素boolean remove(Object o);boolean containsAll(Collection? c);//同样是只能插入那些不重复的元素boolean addAll(Collection? extends E c);boolean retainAll(Collection? c);boolean removeAll(Collection? c);void clear();boolean equals(Object o);int hashCode();//这个方法我们同样会放到多线程中进行介绍Overridedefault SpliteratorE spliterator() {return Spliterators.spliterator(this, Spliterator.DISTINCT);}
}HashSet它的底层就是采用哈希表实现的可以非常高效的从HashSet中存取元素
在Set接口中并没有定义支持指定下标位置访问的添加和删除操作只能简单的删除Set中的某个对象
由于底层采用哈希表实现无法维持插入元素的顺序
想要使用维持顺序的Set集合可以使用LinkedHashSetLinkedHashSet底层维护的不再是一个HashMap而是LinkedHashMap它能够在插入数据时利用链表自动维护顺序因此这样就能够保证我们插入顺序和最后的迭代顺序一致了
public static void main(String[] args) {SetString set new LinkedHashSet();set.addAll(Arrays.asList(A, 0, -, ));System.out.println(set);
}TreeSet它会在元素插入时进行排序可以自定义排序规则
public static void main(String[] args) {TreeSetInteger set new TreeSet((a, b) - b - a); //同样是一个Comparatorset.add(1);set.add(3);set.add(2);System.out.println(set);
}Map映射
通过保存键值对的形式来存储映射关系就可以轻松地通过键找到对应的映射值在Map中这些映射关系被存储为键值对
//Map并不是Collection体系下的接口而是单独的一个体系因为操作特殊
//这里需要填写两个泛型参数其中K就是键的类型V就是值的类型比如上面的学生信息ID一般是int那么键就是Integer类型的而值就是学生信息所以说值是学生对象类型的
public interface MapK,V {//-------- 查询相关操作 --------//获取当前存储的键值对数量int size();//是否为空boolean isEmpty();//查看Map中是否包含指定的键boolean containsKey(Object key);//查看Map中是否包含指定的值boolean containsValue(Object value);//通过给定的键返回其映射的值V get(Object key);//-------- 修改相关操作 --------//向Map中添加新的映射关系也就是新的键值对V put(K key, V value);//根据给定的键移除其映射关系也就是移除对应的键值对V remove(Object key);//-------- 批量操作 --------//将另一个Map中的所有键值对添加到当前Map中void putAll(Map? extends K, ? extends V m);//清空整个Mapvoid clear();//-------- 其他视图操作 --------//返回Map中存放的所有键以Set形式返回SetK keySet();//返回Map中存放的所有值CollectionV values();//返回所有的键值对这里用的是内部类Entry在表示SetMap.EntryK, V entrySet();//这个是内部接口Entry表示一个键值对interface EntryK,V {//获取键值对的键K getKey();//获取键值对的值V getValue();//修改键值对的值V setValue(V value);//判断两个键值对是否相等boolean equals(Object o);//返回当前键值对的哈希值int hashCode();...}...
}最常见的HashMap它的底层采用哈希表实现
Map中无法添加相同的键同样的键只能存在一个即使值不同。如果出现键相同的情况那么会覆盖掉之前的
为了防止意外将之前的键值对覆盖掉可以使用
public static void main(String[] args) {MapInteger, String map new HashMap();map.put(1, 小明);map.putIfAbsent(1, 小红); //Java8新增操作只有在不存在相同键的键值对时才会存放System.out.println(map.get(1));
}在获取一个不存在的映射时默认会返回null作为结果
public static void main(String[] args) {MapInteger, String map new HashMap();map.put(1, 小明); //Map中只有键为1的映射System.out.println(map.get(3)); //此时获取键为3的值那肯定是没有的所以说返回null
}当Map中不存在时可以返回一个备选的返回值
public static void main(String[] args) {MapInteger, String map new HashMap();map.put(1, 小明);System.out.println(map.getOrDefault(3, 备胎)); //Java8新增操作当不存在对应的键值对时返回备选方案
}HashMap底层采用哈希表实现所以不维护顺序我们在获取所有键和所有值时可能会是乱序的
如果需要维护顺序我们同样可以使用LinkedHashMap它的内部对插入顺序进行了维护
哈希表可能会出现哈希冲突这样保存的元素数量就会存在限制而我们可以通过连地址法解决这种问题
public class HashMapK,V extends AbstractMapK,Vimplements MapK,V, Cloneable, Serializable {...static class NodeK,V implements Map.EntryK,V { //内部使用结点实际上就是存放的映射关系final int hash;final K key; //跟我们之前不一样我们之前一个结点只有键而这里的结点既存放键也存放值当然计算哈希还是使用键V value;NodeK,V next;...}...transient NodeK,V[] table; //这个就是哈希表本体了可以看到跟我们之前的写法是一样的也是头结点数组只不过HashMap中没有设计头结点相当于没有头结点的链表final float loadFactor; //负载因子这个东西决定了HashMap的扩容效果public HashMap() {this.loadFactor DEFAULT_LOAD_FACTOR; //当我们创建对象时会使用默认的负载因子值为0.75}...
}实际上底层大致结构跟我们之前学习的差不多只不过多了一些特殊的东西
HashMap支持自动扩容哈希表的大小并不是一直不变的否则太过死板HashMap并不是只使用简单的链地址法当链表长度到达一定限制时会转变为效率更高的红黑树结构
put方法
public V put(K key, V value) {//这里计算完键的哈希值之后调用的另一个方法进行映射关系存放return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;if ((tab table) null || (n tab.length) 0) //如果底层哈希表没初始化先初始化n (tab resize()).length; //通过resize方法初始化底层哈希表初始容量为16后续会根据情况扩容底层哈希表的长度永远是2的n次方//因为传入的哈希值可能会很大这里同样是进行取余操作//(n - 1) hash 等价于 hash % n 这里的i就是最终得到的下标位置了if ((p tab[i (n - 1) hash]) null)tab[i] newNode(hash, key, value, null); //如果这个位置上什么都没有那就直接放一个新的结点else { //这种情况就是哈希冲突了NodeK,V e; K k;if (p.hash hash //如果上来第一个结点的键的哈希值跟当前插入的键的哈希值相同键也相同说明已经存放了相同键的键值对了那就执行覆盖操作((k p.key) key || (key ! null key.equals(k))))e p; //这里直接将待插入结点等于原本冲突的结点一会直接覆盖else if (p instanceof TreeNode) //如果第一个结点是TreeNode类型的说明这个链表已经升级为红黑树了e ((TreeNodeK,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) //如果当前链表的长度已经很长了达到了阈值treeifyBin(tab, hash); //那么就转换为红黑树来存放break; //直接结束}if (e.hash hash ((k e.key) key || (key ! null key.equals(k)))) //同样的如果在向下找的过程中发现已经存在相同键的键值对了直接结束让p等于e一会覆盖就行了break;p e;}}if (e ! null) { // 如果e不为空只有可能是前面出现了相同键的情况其他情况e都是null所有直接覆盖就行V oldValue e.value;if (!onlyIfAbsent || oldValue null)e.value value;afterNodeAccess(e);return oldValue; //覆盖之后会返回原本的被覆盖值}}modCount;if (size threshold) //键值对size计数自增如果超过阈值会对底层哈希表数组进行扩容resize(); //调用resize进行扩容afterNodeInsertion(evict);return null; //正常插入键值对返回值为null
}当HashMap的一个链表长度过大时会自动转换为红黑树
这样始终治标不治本受限制的始终是底层哈希表的长度还需要进一步对底层的这个哈希表进行扩容才可以从根本上解决问题来看看resize()方法
final NodeK,V[] resize() {NodeK,V[] oldTab table; //先把下面这几个旧的东西保存一下int oldCap (oldTab null) ? 0 : oldTab.length;int oldThr threshold;int newCap, newThr 0; //这些是新的容量和扩容阈值if (oldCap 0) { //如果旧容量大于0那么就开始扩容if (oldCap MAXIMUM_CAPACITY) { //如果旧的容量已经大于最大限制了那么直接给到 Integer.MAX_VALUEthreshold Integer.MAX_VALUE;return oldTab; //这种情况不用扩了}else if ((newCap oldCap 1) MAXIMUM_CAPACITY oldCap DEFAULT_INITIAL_CAPACITY) //新的容量等于旧容量的2倍同样不能超过最大值newThr oldThr 1; //新的阈值也提升到原来的两倍}else if (oldThr 0) // 旧容量不大于0只可能是还没初始化这个时候如果阈值大于0直接将新的容量变成旧的阈值newCap oldThr;else { // 默认情况下阈值也是0也就是我们刚刚无参new出来的时候newCap DEFAULT_INITIAL_CAPACITY; //新的容量直接等于默认容量16newThr (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值为负载因子乘以默认容量负载因子默认为0.75也就是说只要整个哈希表用了75%的容量那么就进行扩容至于为什么默认是0.75原因很多这里就不解释了反正作为新手这些都是大佬写出来的我们用就完事。}...threshold newThr;SuppressWarnings({rawtypes,unchecked})NodeK,V[] newTab (NodeK,V[])new Node[newCap];table newTab; //将底层数组变成新的扩容之后的数组if (oldTab ! null) { //如果旧的数组不为空那么还需要将旧的数组中所有元素全部搬到新的里面去... //详细过程就不介绍了}
}LinkedHashMap是直接继承自HashMap具有HashMap的全部性质同时得益于每一个节点都是一个双向链表在插入键值对时同时保存了插入顺序
static class EntryK,V extends HashMap.NodeK,V { //LinkedHashMap中的结点实现EntryK,V before, after; //这里多了一个指向前一个结点和后一个结点的引用Entry(int hash, K key, V value, NodeK,V next) {super(hash, key, value, next);}
}TreeMap内部直接维护了一个红黑树因为它会将我们插入的结点按照规则进行排序所以说直接采用红黑树会更好我们在创建时直接给予一个比较规则即可跟之前的TreeSet是一样的
HashSet几乎都在操作内部维护的一个HashMap也就是说HashSet只是一个表壳而内部维护的HashMap才是灵魂
Map中定义的compute方法
public static void main(String[] args) {MapInteger, String map new HashMap();map.put(1, A);map.put(2, B);map.compute(1, (k, v) - { //compute会将指定Key的值进行重新计算若Key不存在v会返回nullreturn vM; //这里返回原来的valueM});map.computeIfPresent(1, (k, v) - { //当Key存在时存在则计算并赋予新的值return vM; //这里返回原来的valueM});System.out.println(map);
}使用computeIfAbsent当不存在Key时计算并将键值对放入Map中
merge方法用于处理数据
public static void main(String[] args) {ListStudent students Arrays.asList(new Student(yoni, English, 80),new Student(yoni, Chiness, 98),new Student(yoni, Math, 95),new Student(taohai.wang, English, 50),new Student(taohai.wang, Chiness, 72),new Student(taohai.wang, Math, 41),new Student(Seely, English, 88),new Student(Seely, Chiness, 89),new Student(Seely, Math, 92));MapString, Integer scoreMap new HashMap();//merge方法可以对重复键的值进行特殊操作比如我们想计算某个学生的所有科目分数之后那么就可以像这样students.forEach(student - scoreMap.merge(student.getName(), student.getScore(), Integer::sum));scoreMap.forEach((k, v) - System.out.println(key: k 总分 value: v));
}static class Student {private final String name;private final String type;private final int score;public Student(String name, String type, int score) {this.name name;this.type type;this.score score;}public String getName() {return name;}public int getScore() {return score;}public String getType() {return type;}
}replace方法可以快速替换某个映射的值
public static void main(String[] args) {MapInteger , String map new HashMap();map.put(0, 单走);map.replace(0, ); //直接替换为新的map.replace(0, 巴卡, 玛卡); //只有键和值都匹配时才进行替换System.out.println(map);
}remove方法
public static void main(String[] args) {MapInteger , String map new HashMap();map.put(0, 单走);map.remove(0, 单走); //只有同时匹配时才移除System.out.println(map);
}Stream流
Java 8 API添加了一个新的抽象称为流Stream可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流 流在管道中传输 并且可以在管道的节点上进行处理 比如筛选 排序聚合等。
元素流在管道中经过中间操作intermediate operation的处理最后由最终操作(terminal operation)得到前面处理的结果。 可以把一个Stream当做流水线处理
public static void main(String[] args) {ListString list new ArrayList();list.add(A);list.add(B);list.add(C);//移除为B的元素IteratorString iterator list.iterator();while (iterator.hasNext()){if(iterator.next().equals(B)) iterator.remove();}//Stream操作list list //链式调用.stream() //获取流.filter(e - !e.equals(B)) //只允许所有不是B的元素通过流水线.collect(Collectors.toList()); //将流水线中的元素重新收集起来变回ListSystem.out.println(list);list list.stream().distinct() //去重使用equals判断.sorted((a, b) - b - a) //进行倒序排列.map(e - e1) //每个元素都要执行1操作.limit(2) //只放行前两个元素.collect(Collectors.toList());
}当遇到大量的复杂操作时我们就可以使用Stream来快速编写代码这样不仅代码量大幅度减少而且逻辑也更加清晰明了
stream会先记录每一步操作而不是直接开始执行内容当整个链式调用完成后才会依次进行
随机数来进行更多流操作的演示
public static void main(String[] args) {Random random new Random(); //没想到吧Random支持直接生成随机数的流random.ints(-100, 100) //生成-100~100之间的随机int型数字本质上是一个IntStream.limit(10) //只获取前10个数字这是一个无限制的流如果不加以限制将会无限进行下去.filter(i - i 0) //只保留小于0的数字.sorted() //默认从小到大排序.forEach(System.out::println); //依次打印
}生成一个统计实例来帮助我们快速进行统计
public static void main(String[] args) {Random random new Random(); //Random是一个随机数工具类IntSummaryStatistics statistics random.ints(0, 100).limit(100).summaryStatistics(); //获取语法统计实例System.out.println(statistics.getMax()); //快速获取最大值System.out.println(statistics.getCount()); //获取数量System.out.println(statistics.getAverage()); //获取平均值
}只通过Stream来完成所有数字的和使用reduce方法
public static void main(String[] args) {ListInteger list new ArrayList();list.add(1);list.add(2);list.add(3);int sum list.stream().reduce((a, b) - a b) //计算规则为a是上一次计算的值b是当前要计算的参数这里是求和.get(); //我们发现得到的是一个Optional类实例通过get方法返回得到的值System.out.println(sum);
}通过flat来对整个流进行进一步细分
public static void main(String[] args) {ListString list new ArrayList();list.add(A,B);list.add(C,D);list.add(E,F); //我们想让每一个元素通过,进行分割变成独立的6个元素list list.stream() //生成流.flatMap(e - Arrays.stream(e.split(,))) //分割字符串并生成新的流.collect(Collectors.toList()); //汇成新的ListSystem.out.println(list); //得到结果
}Collections工具类
快速求得List中的最大值和最小值
public static void main(String[] args) {ListInteger list new ArrayList();Collections.max(list);Collections.min(list);
}对一个集合进行二分搜索注意集合的具体类型必须是实现Comparable接口的类
public static void main(String[] args) {ListInteger list Arrays.asList(2, 3, 8, 9, 10, 13);System.out.println(Collections.binarySearch(list, 8));
}对集合的元素进行快速填充
public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1,2,3,4,5));Collections.fill(list, 6);System.out.println(list);
}注意这个填充是对集合中已有的元素进行覆盖如果集合中本身没有元素那么fill操作不会生效。
使用emptyXXX来快速生成一个只读的空集合
public static void main(String[] args) {ListInteger list Collections.emptyList();//Collections.singletonList() 会生成一个只有一个元素的Listlist.add(10); //不支持会直接抛出异常
}将一个可修改的集合变成只读的集合
public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1,2,3,4,5));ListInteger newList Collections.unmodifiableList(list);newList.add(10); //不支持会直接抛出异常
}寻找子集合的位置
public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1,2,3,4,5));System.out.println(Collections.indexOfSubList(list, Arrays.asList(4, 5)));
}由于泛型机制上的一些漏洞实际上对应类型的集合类有可能会存放其他类型的值泛型的类型检查只存在于编译阶段只要我们绕过这个阶段在实际运行时并不会真的进行类型检查要解决这种问题很简单就是在运行时进行类型检查
public static void main(String[] args) {//使用原始类型接收一个Integer类型的ArrayListList list new ArrayList(Arrays.asList(1,2,3,4,5));list.add(aaa); //我们惊奇地发现这玩意居然能存字符串进去System.out.println(list);
}public static void main(String[] args) {List list new ArrayList(Arrays.asList(1,2,3,4,5));list Collections.checkedList(list, Integer.class); //这里的.class关键字我们会在后面反射中介绍表示Integer这个类型list.add(aaa);System.out.println(list);
}List(); //Collections.singletonList() 会生成一个只有一个元素的List list.add(10); //不支持会直接抛出异常 } 将一个可修改的集合变成只读的集合~~~java
public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1,2,3,4,5));ListInteger newList Collections.unmodifiableList(list);newList.add(10); //不支持会直接抛出异常
}寻找子集合的位置
public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1,2,3,4,5));System.out.println(Collections.indexOfSubList(list, Arrays.asList(4, 5)));
}由于泛型机制上的一些漏洞实际上对应类型的集合类有可能会存放其他类型的值泛型的类型检查只存在于编译阶段只要我们绕过这个阶段在实际运行时并不会真的进行类型检查要解决这种问题很简单就是在运行时进行类型检查
public static void main(String[] args) {//使用原始类型接收一个Integer类型的ArrayListList list new ArrayList(Arrays.asList(1,2,3,4,5));list.add(aaa); //我们惊奇地发现这玩意居然能存字符串进去System.out.println(list);
}public static void main(String[] args) {List list new ArrayList(Arrays.asList(1,2,3,4,5));list Collections.checkedList(list, Integer.class); //这里的.class关键字我们会在后面反射中介绍表示Integer这个类型list.add(aaa);System.out.println(list);
}checkedXXX可以将给定集合类进行包装在运行时同样会进行类型检查如果通过上面的漏洞插入一个本不应该是当前类型集合支持的类型那么会直接抛出类型转换异常