塔防生存防御
77.24 MB · 2025-10-03
按照流的流向分,可以分为输入流和输出流; 按照操作单元划分,可以划分为字节流和字符流; 按照流的角色划分为节点流和处理流。 Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼 此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类 中派生出来的。
简答
详细回答
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
集合框架: 用于存储数据的容器。
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算 法。
接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现, 从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。
实现:集合接口的具体实现,是重用性很高的数据结构。
算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方 法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个 接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。 它减少了程序设计的辛劳。
集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部 分上,而不是为了让程序能正常运转而将注意力于底层设计上。
通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以 便联合这些API而去写大量的代码。 它提高了程序速度和质量。
集合的特点主要有如下两点:
数据结构:就是容器中存储数据的方式。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个 容器的内部数据结构不同。
集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参 阅顶层内容。建立底层对象。
Map接口和Collection接口是所有集合框架的父接口:
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、 List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是 collection的子接口。
Collection集合主要有List和Set两大接口
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不 要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元 素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap
List
Set
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作 时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中 的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简 单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount 的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测 modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出 异常,终止遍历。
解决办法:
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。 示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
运行本项目java
运行
12345
ArrayList的优点如下:
ArrayList 的缺点如下:
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
补充:数据结构基础之双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
1 List<String> synchronizedList = Collections.synchronizedList(list);
2 synchronizedList.add("aaa");
3 synchronizedList.add("bbb");
4
5 for (int i = 0; i < synchronizedList.size(); i++) {
6 System.out.println(synchronizedList.get(i));
7 }
运行本项目java
运行
1234567
另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
Set和List对比
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成。
Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。
BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。
Java提供了集中 BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。
在 Queue 中 poll()和 remove()有什么区别?
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: 在Java编程语言中, 基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
HashMap 基于 Hash 算法实现的:
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。
JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
JDK1.8主要解决或优化了一下问题:
计算哈希值:首先调用键(key)的hashCode()方法获取原始哈希值,接着对该值进行二次哈希处理(即(h = key.hashCode()) ^ (h >>> 16)),以此降低哈希冲突的出现几率。
存储位置:利用处理后的哈希值和哈希表的长度((n - 1) & hash)来确定键值对在数组中的具体位置。
处理空桶情况:要是指定位置为空,就会创建一个新的节点并将其放入该位置。
处理哈希冲突:若该位置已存在节点,就需要进一步处理:
扩容操作:在插入元素之后,若哈希表的大小超过了阈值(负载因子乘以容量),哈希表的容量就会翻倍,并且所有元素会重新进行哈希计算并分布到新的位置。
下面是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) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 若哈希表为空或长度为0,则进行扩容
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;
}
}
// 结构修改次数加1
++modCount;
// 若超过阈值,进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
运行本项目java
运行
77.24 MB · 2025-10-03
1044.43M · 2025-10-03
133.4M · 2025-10-03