Java 容器详解:使用与案例
Java容器是一套工具,用于存储数据和对象。可以与C++的STL类比。Java容器也称为Java Collection Framework (JCF)。除了存储对象的容器之外,还提供了一套工具类,用于处理和操作容器中的对象。总体来说,这是一个框架,它包含了Java对象容器和工具类。一、概览容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。Collection1. SetTreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。2. ListArrayList:基于动态数组实现,支持随机访问。Vector:和 ArrayList 类似,但它是线程安全的。LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。3. QueueLinkedList:可以用它来实现双向队列。PriorityQueue:基于堆结构实现,可以用它来实现优先队列。MapTreeMap:基于红黑树实现。HashMap:基于哈希表实现。HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。二、容器中的设计模式迭代器模式Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String item : list) {
System.out.println(item);
}适配器模式java.util.Arrays#asList() 可以把数组类型转换为 List 类型。@SafeVarargs
public static <T> List<T> asList(T... a)应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。Integer[] arr = {1, 2, 3};
List list = Arrays.asList(arr);也可以使用以下方式调用 asList():List list = Arrays.asList(1, 2, 3);三、源码分析如果没有特别说明,以下源码分析基于 JDK 1.8。在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。ArrayList1. 概览因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable数组的默认大小为 10。private static final int DEFAULT_CAPACITY = 10;2. 扩容添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),即 oldCapacity+oldCapacity/2。其中 oldCapacity >> 1 需要取整,所以新容量大约是旧容量的 1.5 倍左右。(oldCapacity 为偶数就是 1.5 倍,为奇数就是 1.5 倍-0.5)扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}3. 删除元素需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}4. 序列化ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。transient Object[] elementData;ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt();
if (size > 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);5. Fail-FastmodCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。Vector1. 同步它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}2. 扩容Vector 的构造函数可以传入 capacityIncrement 参数,它的作用是在扩容时使容量 capacity 增长 capacityIncrement。如果这个参数的值小于等于 0,扩容时每次都令 capacity 为原来的两倍。public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}调用没有 capacityIncrement 的构造函数时,capacityIncrement 值被设置为 0,也就是说默认情况下 Vector 每次扩容时容量都会翻倍。public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}3. 与 ArrayList 的比较Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。4. 替代方案可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。List<String> list = new ArrayList<>();
List<String> synList = Collections.synchronizedList(list);也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。List<String> list = new CopyOnWriteArrayList<>();CopyOnWriteArrayList1. 读写分离写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。写操作需要加锁,防止并发写入时导致写入数据丢失。写操作结束之后需要把原始数组指向新的复制数组。public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}2. 适用场景CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。但是 CopyOnWriteArrayList 有其缺陷:内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。LinkedList1. 概览基于双向链表实现,使用 Node 存储链表节点信息。private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}每个链表存储了 first 和 last 指针:transient Node<E> first;
transient Node<E> last;2. 与 ArrayList 的比较ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:数组支持随机访问,但插入删除的代价很高,需要移动大量元素;链表不支持随机访问,但插入删除只需要改变指针。HashMap为了便于理解,以下源码分析以 JDK 1.7 为主。1. 存储结构内部包含了一个 Entry 类型的数组 table。Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。transient Entry[] table;static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
}2. 拉链法的工作原理HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");新建一个 HashMap,默认大小为 16;插入 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。插入 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。插入 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 前面。应该注意到链表的插入是以头插法方式进行的,例如上面的 不是插在 后面,而是插入在链表头部。查找需要分成两步进行:计算键值对所在的桶;在链表上顺序查找,时间复杂度显然和链表的长度成正比。3. put 操作public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 键为 null 单独处理
if (key == null)
return putForNullKey(value);
int hash = hash(key);
// 确定桶下标
int i = indexFor(hash, table.length);
// 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 插入新键值对
addEntry(hash, key, value, i);
return null;
}HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}使用链表的头插法,也就是新的键值对插在链表的头部,而不是链表的尾部。void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
// 头插法,链表头部指向新的键值对
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}4. 确定桶下标很多操作都需要先确定一个键值对所在的桶下标。int hash = hash(key);
int i = indexFor(hash, table.length);4.1 计算 hash 值final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}4.2 取模令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:x : 00010000
x-1 : 00001111令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位以上数:y : 10110010
x-1 : 00001111
y&(x-1) : 00000010这个性质和 y 对 x 取模效果是一样的:y : 10110010
x : 00010000
y%x : 00000010我们知道,位运算的代价比求模运算小的多,因此在进行这种计算时用位运算的话能带来更高的性能。确定桶下标的最后一步是将 key 的 hash 值对桶个数取模:hash%capacity,如果能保证 capacity 为 2 的 n 次方,那么就可以将这个操作转换为位运算。static int indexFor(int h, int length) {
return h & (length-1);
}5. 扩容-基本原理设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此查找的复杂度为 O(N/M)。为了让查找的成本降低,应该使 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。参数含义capacitytable 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。size键值对数量。thresholdsize 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。loadFactor装载因子,table 能够使用的比例,threshold = (int)(capacity* loadFactor)。static final int DEFAULT_INITIAL_CAPACITY = 16;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Entry[] table;
transient int size;
int threshold;
final float loadFactor;
transient int modCount;从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}6. 扩容-重新计算桶下标在进行扩容时,需要把键值对重新计算桶下标,从而放到对应的桶上。在前面提到,HashMap 使用 hash%capacity 来确定桶下标。HashMap capacity 为 2 的 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度。假设原数组长度 capacity 为 16,扩容之后 new capacity 为 32:capacity : 00010000
new capacity : 00100000对于一个 Key,它的哈希值 hash 在第 5 位:为 0,那么 hash%00010000 = hash%00100000,桶位置和原来一致;为 1,hash%00010000 = hash%00100000 + 16,桶位置是原位置 + 16。7. 计算数组容量HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。先考虑如何求一个数的掩码,对于 10010000,它的掩码为 11111111,可以使用以下方法得到:mask |= mask >> 1 11011000
mask |= mask >> 2 11111110
mask |= mask >> 4 11111111mask+1 是大于原始数字的最小的 2 的 n 次方。num 10010000
mask+1 100000000以下是 HashMap 中计算数组容量的代码:static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}8. 链表转红黑树从 JDK 1.8 开始,一个桶存储的链表长度大于等于 8 时会将链表转换为红黑树。9. 与 Hashtable 的比较Hashtable 使用 synchronized 来进行同步。HashMap 可以插入键为 null 的 Entry。HashMap 的迭代器是 fail-fast 迭代器。HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。ConcurrentHashMap1. 存储结构static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁(Segment),每个分段锁维护着几个桶(HashEntry),多个线程可以同时访问不同分段锁上的桶,从而使其并发度更高(并发度就是 Segment 的个数)。Segment 继承自 ReentrantLock。static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
}final Segment<K,V>[] segments;默认的并发级别为 16,也就是说默认创建 16 个 Segment。static final int DEFAULT_CONCURRENCY_LEVEL = 16;2. size 操作每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。transient int count;在执行 size 操作时,需要遍历所有 Segment 然后把 count 累计起来。ConcurrentHashMap 在执行 size 操作时先尝试不加锁,如果连续两次不加锁操作得到的结果一致,那么可以认为这个结果是正确的。尝试次数使用 RETRIES_BEFORE_LOCK 定义,该值为 2,retries 初始值为 -1,因此尝试次数为 3。如果尝试的次数超过 3 次,就需要对每个 Segment 加锁。static final int RETRIES_BEFORE_LOCK = 2;
public int size() {
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow;
long sum;
long last = 0L;
int retries = -1;
try {
for (;;) {
// 超过尝试次数,则对每个 Segment 加锁
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock();
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
// 连续两次得到的结果一致,则认为这个结果是正确的
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}3. JDK 1.8 的改动JDK 1.7 使用分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock,并发度与 Segment 数量相等。JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败时使用内置锁 synchronized。并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。LinkedHashMap存储结构继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;accessOrder 决定了顺序,默认为 false,此时维护的是插入顺序。final boolean accessOrder;LinkedHashMap 最重要的是以下用于维护顺序的函数,它们会在 put、get 等方法中调用。void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }afterNodeAccess()当一个节点被访问时,如果 accessOrder 为 true,则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}afterNodeInsertion()在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。evict 只有在构建 Map 的时候才为 false,在这里为 true。void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}removeEldestEntry() 默认为 false,如果需要让它为 true,需要继承 LinkedHashMap 并且覆盖这个方法的实现,这在实现 LRU 的缓存中特别有用,通过移除最近最久未使用的节点,从而保证缓存空间足够,并且缓存的数据都是热点数据。protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}LRU 缓存以下是使用 LinkedHashMap 实现的一个 LRU 缓存:设定最大缓存空间 MAX_ENTRIES 为 3;使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true,开启 LRU 顺序;覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 3;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
LRUCache() {
super(MAX_ENTRIES, 0.75f, true);
}
}public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>();
cache.put(1, "a");
cache.put(2, "b");
cache.put(3, "c");
cache.get(1);
cache.put(4, "d");
System.out.println(cache.keySet());
}[3, 1, 4]WeakHashMap存储结构WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收。private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>ConcurrentCacheTomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。ConcurrentCache 采取的是分代缓存:经常使用的对象放入 eden 中,eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);不常用的对象放入 longterm,longterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。当调用 put() 方法时,如果 eden 的大小超过了 size,那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。public final class ConcurrentCache<K, V> {
private final int size;
private final Map<K, V> eden;
private final Map<K, V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
v = this.longterm.get(k);
if (v != null)
this.eden.put(k, v);
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
this.longterm.putAll(this.eden);
this.eden.clear();
}
this.eden.put(k, v);
}
}总结本文介绍了Java容器的基本知识,其中包括容器的使用方法和注意事项。虽然这些知识已经足够入门,但要真正掌握Java容器,建议深入了解容器的内部实现方式。建议多查阅Java容器的API和源码,学习容器的算法和数据结构。当你能够自己动手实现这些容器时,才能真正掌握Java容器,并在使用Java容器时更加得心应手。在学习Java容器时,需要注意的是,不同的容器适用于不同的场景,我们需要根据自己的实际需求选择合适的容器。总之,学习Java容器是Java开发者必备的技能之一,只有掌握了Java容器的使用和实现方式,才能在开发中更加得心应手,提高开发效率和代码质量。最后为了方便其他设备和平台的小伙伴观看往期文章,链接奉上:牛客,知乎,开源中国,CSDN,思否,掘金,InfoQ,简书,博客园,慕课,51CTO,helloworld,腾讯开发者社区,阿里开发者社区看完如果觉得有帮助,帮忙点个赞👍
C++开发者必读经典书籍推荐
如果你正在学习C++,那么一本好的教材或参考书可以事半功倍。以下是几本我个人推荐的C++书籍或视频:C++基础看书C++ PrimerC++程序设计语言Effective C++More Effective C++Effective STLSTL源码分析深度探索C++对象模型看视频黑马程序员(B站)C++内存管理(候捷)STL源码分析(候捷)C++ STL与泛型编程高级(候捷)C++11 新特性(候捷)C++进阶书籍C++语言的设计与演化C++沉思录C++ TemplatesC++ 模版元编程视频CppCon数据结构与算法书籍大话数据结构算法图解数据结构与算法分析算法第4版算法导论视频浙大数据结构网课刷题LeetCode操作系统深入理解计算机系统操作系统精髓与设计原理现代操作系统(选读)程序员的自我修养LinuxLinux/UNIX系统编程手册Linux内核设计与实现深入理解Linux内核计算机网络计算机网络自顶向下TCP/IP详解:卷1网络是怎样连接的图解HTTP网络编程Unix网络编程Unix环境高级编程Linux多线程服务器端编程数据库数据库系统概念mysql必知必会高性能MySQLMqSQL技术内幕设计模式大话设计模式Head First设计模式其他Redis设计与实现总结以上是我个人推荐的几本C++书籍。希望这些推荐对你有所帮助!最后为了方便其他设备和平台的小伙伴观看往期文章,链接奉上:牛客,知乎,开源中国,CSDN,思否,掘金,InfoQ,简书,博客园,慕课,51CTO,helloworld,腾讯开发者社区,阿里开发者社区看完如果觉得有帮助,帮忙点个赞👍
被裁后半月面试8家公司无果,凭借这份Java面试指南成功入职阿里
前言上个月班上的好好的突然被通知"毕业了",现在工作也确实不好找。之前近一个月面了很多大大小小的公司降薪太严重都没考虑去,最后没办法本来都打算随便去一家了却偶然得到一个阿里的面试机会,足足面了七面(我太难了)因为我的工程项目经验基本为0 所以被死磕Java,下面我简单说下面试经过:一面: 90mins (基础知识与集合框架)二面:40mins (线程池设计模式等)三面coding:2h(千万级数据量的list找一个数据)四面:50mins(主要调优问JVM)五面coding:2h(百万级int数据量array求和)六面交叉面:20mins(深挖项目)七面hr:20min(聊人生)这一套组合拳打下来所幸我扛住了,周围有很多朋友都问我咋备战面试的,其实我就是每次面试都会把自己面试没答好的一些点简单的复盘总结,并在文档中随手记录,常年累积下来也不知不觉码了206页了,每次面试之前我都会按着文档大概梳理下思路,对我来说挺有用的,能相对提高我的面试通过率,周围很多朋友也都找我要这份“面试复盘笔记”大概涵盖了:大数据与高并发、分布式、中间件、数据库、设计模式与实践、数据结构与算法六大内容,要是有跟我一样今年被迫“毕业”或者正在面试的好兄弟,认识下,发你笔记。笔记一共有206页,篇幅限制无法将所有内容全部展示出来,下面只展示部分内容,需要的小伙伴可以点击此处来获取就可以了! Java面试复盘笔记分布式中间件高并发数据库设计模式算法面试题举例分布式环境下全局唯一的发号器带有过期时间的RU缓存分布式锁分布式环境下的统一配置中心最后需要的小伙伴可以点击此处来获取就可以了!
Rust 笔记、设计模式发布订阅模式及其 在 Rust 语言中的使用
Rust 笔记、设计模式发布订阅模式及其 在 Rust 语言中的使用作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130877457【介绍】:本文介绍发布订阅模式的相关思想,以及第三方模块 EventEmitter 的使用。推荐阅读:《发布订阅模式原理及其应用(多种语言实现)》 这篇是我早前的博客,里面使用了 Powershell、Dart、Python、TypeScript 讲解或实现了一个 EventEmitter 对象。但是当时还没有考虑使用 Rust。本文多数内容直接来源于该博文,主要是将语言替换成了 Rust。上一节:《?Rust 文件 I/O 的使用?》?|?下一节:《 有限状态机原理及其在Rust 语言中的应用?》目 录1. 引例:从我的一个经历说起1.1 从 订阅 到 发布1.2 如果我不想继续订阅了2. 发布-订阅 的 实践、应用、思考2.1 实践:用 Rust 来复现上面的场景3. 通用型发布者对象的改进3.1 从 Subscriber 的服务员 到 事件的发布者3.2 一个比较初步的功能增强4. 使用现成的第三方模块:EventEmitter4.1 EventEmitter 的安装4.2 在你的项目中使用 EventEmitter4.3 EventEmitter 实例上的方法4.3.1 set_max_listeners 方法4.3.2 set_max_listeners 方法4.3.3 on 方法4.3.4 add_listener 方法4.3.5 off 方法4.3.6 remove_listener 方法4.3.7 emit 方法4.3.8 remove_all_listeners 方法4.3.9 prepend_listener 方法4.3.10 listeners 方法4.3.11 listener_count 方法1. 引例:从我的一个经历说起1.1 从 订阅 到 发布记得一九年的时候我刚刚来到深圳工作,众所周知那时候还没有爆发 新冠疫情,身边的同事们组队去香港购物是常有的事情。但是那会儿我还没有办理港澳通行证,于是年底回老家的时候去当地政务中心办理了。办证是需要时间的,万万没想到的是二零年春节前夕——新冠疫情爆发了。当我回到深圳后的某一天接到老家政务中心的电话,通知我由于疫情的原因,通信证的办理已经被暂停了并且什么时候恢复办理还不能确定,如果愿意等待,则需要到恢复办证的时候,再通知我们。—— 这就是一个 发布-订阅模式的典型例子。发布-订阅 模式 模式中的多方可以分为两类,一类是消息的 发布者,另外一类是消息的 订阅者。在上面的案例中,政务中心的工作人员就是 发布者,当我表示愿意等到恢复通信证办理时,我就 向发布者订阅了 恢复办理的通知(消息),因此我时消息的 订阅者。这样有什么好处呢:对于我(订阅者)来说,不需要每隔几天就打电话到政务服务中心(发布者)去询问是否恢复办理的消息;对于政务服务中心(发布者)同样也不需要每天回答相同的问题——毕竟何时恢复办理他们也不能确定。一旦恢复办理,政务服务中心(发布者)可以一次性地通知所有和我一样地广大订阅者。看到了吗——相比于我们去轮询以获取消息,改用发布-订阅 模式 同时节省了我们双方地时间!多么棒地思想!——运用于程序设计中岂不秒哉?1.2 如果我不想继续订阅了有一种情况也是非常常见的,那就是我不愿意继续等待消息了,也有可能是这个消息对我来说已经不重要了。这时我不再希望继续收到来自发布者的恢复办理通知,那就需要 退订。还记得吗——当我们订阅的时候,是将我们的订阅意愿登记在发布者那边的,这样就能实现发布者在适当的时候通过查询 所有的登记记录 然后逐一通知。因此如果一旦有用户需要退订,其实很简单,只需要订阅者在他们所登记订阅的“订阅者登陆表”中将订阅信息删除掉即可,这样下一次广播通知的时候就不会再将消息发送给退订的用户。2. 发布-订阅 的 实践、应用、思考2.1 实践:用 Rust 来复现上面的场景如果现在你好像明白 发布-订阅模式 的基本思想了——那么就请成热打铁,跟着我用程序来模拟一下证件办理的情景。use std::collections::HashSet;
#[derive(Eq, Hash, PartialEq, Clone)]
struct Subscriber {
name: String,
}
struct Publisher {
subscribers: HashSet<Subscriber>,
name: String,
}
impl Publisher {
fn new(name: &str) -> Publisher {
Publisher {
subscribers: HashSet::new(),
name: name.to_string(),
}
}
fn add_subscriber(&mut self, subscriber: &Subscriber) {
self.subscribers.insert(subscriber.clone());
}
fn remove_subscriber(&mut self, subscriber: &Subscriber) {
self.subscribers.remove(subscriber);
println!("\n=> {} 已取消订阅。\n", subscriber.name);
}
fn notify_all(&self, arg: &str) {
for subscriber in &self.subscribers {
subscriber.notify(self, arg);
}
}
}
impl Subscriber {
fn new(name: &str) -> Subscriber {
Subscriber {
name: name.to_string(),
}
}
fn notify(&self, publisher: &Publisher, arg: &str) {
println!(
"\"{}\"(订阅者) 收到的通知来自 \"{}\"(发布者)的通知: {}",
self.name, publisher.name, arg
);
}
}
fn main() {
let mut publisher = Publisher::new("政务服务中心");
let jack_lee = Subscriber::new("jackLee");
let jack_ma = Subscriber::new("jackMa");
publisher.add_subscriber(&jack_lee);
publisher.add_subscriber(&jack_ma);
println!("------- 第一次发布消息 -------");
publisher.notify_all("[通知] 恢复证件办理!");
// 用户 jackMa 取消订阅
publisher.remove_subscriber(&jack_ma);
println!("------- 第二次发布消息 -------");
publisher.notify_all("[通知] 恢复证件办理!");
}运行该脚本,输出如下:------- 第一次发布消息 -------
"jackLee"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通知] 恢复证件办理!
"jackMa"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通 知] 恢复证件办理!
=> jackMa 已取消订阅。
------- 第二次发布消息 -------
"jackLee"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通知] 恢复证件办理!可以看到订阅者有两个订阅者实例:jackLee(本人)、jackMa(可能是阿里出来的)共同订阅了证件办理信息。政务服务中心(发布者)第一次发布恢复通知的时候,jackLee 和 jackMa 这两个同学都订阅了消息,因此都受到了来自该中心的通知。后来,jackMa 可能由于已经派小弟火速前往该中心取走了他的证件不需要继续订阅了,于是该中心的工作人(发布者)员调用publisher.remove_subscriber(jackMa) 从该中心的订阅者记录表中移除了 jackMa 的订阅记录。于是,到了该中心第二次发布消息的时候,jackMa 已经不会再收到恢复证件办理消息,而 jackLee 还可以接收到恢复证件办理的消息。3. 通用型发布者对象的改进3.1 从 Subscriber 的服务员 到 事件的发布者在阅读本小节前请读者先自己尝试回答这个问题:Subscriber 类真的有必要实现吗?在我们上面的代码中,Subscriber 类 实现了几乎唯一一个有用的方法:update,它的作用却是给 Publisher 类的 notifyAll 方法进行调用。从现实生活中给一个解释:notifyAll 是消息的发布这发布消息的工具,update 是订阅用户接受到的新的定制化消息,比如同样是订阅了售房信息,但是由于不同类别的购房者订阅时所选定的楼层、大小等参数不一样,则这些不同订阅者接收到的发布结果不一样——也就表面原始的消息需要为不同的订阅者做一些 定制化 处理。在之前的代码实现中,这个消息的定制化工作就是使用 Subscriber 类的 update 方法实现的。很显然,上表面的代码要真正实现定制化,往往不仅是参数值的不同,可能对参数的处理也不一样。因此仅仅依赖参数data是不合理的。因此我们大概是需要写多个仅仅 update 方法的实现不同的 Subscriber 类——这不太好。略好一点的办法是,让 update 方法接受的不是单纯的数据 data,而是一个 回调函数 传入 update 方法中。先不着急修改我们的代码。对于发布者来说,似乎可以提供 更加周到 的服务——直接登记好订阅着的定制化需求处理方式,使用订阅者要求的处理方式处理好定制的消息后,直接告诉订阅者。——因此 update 这个接受表示用户定制化需求处理方式的方法可以直接合并到 发布者那边。于是 Subscriber 类 就不需要了,现在我们只需要更新一下我们的 Publisher。更新的思路是这样的:添加订阅者时(Publisher.addSubscriber)不仅需要记录订阅者名字,还要记录一个对应的响应函数用以消息发布后给订阅者提供定制化服务。从 Publisher 看,需要登记的内容又多了一些。不过好在 订阅者名称(认为是唯一标识符)和 与之对于的服务(回调函数),是对应的关系,既可以一对一,也可以一对多(表示这个订阅者需要多个定制化服务)。因此我们将 Publisher 的 “记录本”改成下面的类型:HashMap<String, Vec<EventCallback>>这个映射(Map)的 key 就表示 订阅者名称,而value 部分是一组函数,表示该订阅者需要的各种服务。另外,到了这里,对于 Publisher来说,添加订阅者就转化为了 为订阅者订阅各种定制化服务 。同时反过来看,对于某个具体的订阅者 Subscriber,一旦它的服务定制数组 (Function[])为空数组,表明他已经没有任何订阅,也不再需要接收发布者的任何消息了。因此先前我们使用的方法名addSubscriber 不适用了,从含以上换成 addListener 似乎更加合适。为什么呢? 我们接下来对此做进一步说明。一直以来,我们聚焦点都在于 发布者 和 订阅者,而忽略了 引起发布者发布的事件。 这个方法接受两个参数,一个是用户名,一个是为用户新增的回调。同时必须指出的是,这个 回调 往往是需要再其调用时接受一些数据的,比如由发布者发布的某些原始数据,他们就像是时时刻刻地 监听着、守候着 发布者 发布一个事件,一旦这个 事件/消息 被发布,就 完成消息发布后为 订阅者 所提供地服务。换一下思路,我们接着把聚焦点转移到 事件 上来。其实从现实中看,同一个事件发生,可能意味着可能需 要干很多事,既可以 服务更多的订阅者,也可以干其它任何的工作——我们一味地想着在发布者处登记订阅者的id然后完成订阅者的需求,那么 没有区别为何事件而需要去发布这些消息!更好的做法是 不再记录订阅者,而是记录为什么要发布消息给订阅者——也就是记录 事件。这样我们就可以在同一个事件发生的时候,通过一系列的属于该事件函数(可能一个或多个回调函数服务于同一个订阅者),完成该事件的响应,也就是回调函数们。从这个意义上看,我们所关注所谓的 订阅者 可以看作 一个事件发布后,发布者需要调用的一组函数。而所谓 发布,实际上就是调用这组函数以 完成事件(的回调)。因此我们接下来该用 listener 表示监听事件以待执行的回调函数, event 表示事件名, emit 表示这个事件发生后需要由发布者调用函数的过程。至此,我们的 Publisher 从一个 Subscriber 的服务员 转型成为了职业 事件的管理者,不妨给它改个名——EventEmitter。现在我们实现一个最基础的 EventEmitter 对象:use std::collections::HashMap;
type EventCallback = Box<dyn Fn()>;
struct EventEmitter {
/// 事件-监听器数组容器
_events: HashMap<String, Vec<EventCallback>>,
}
impl EventEmitter {
fn new() -> EventEmitter {
EventEmitter {
events: HashMap::new(),
}
}
/// 添加事件监听器,监听器是一个回调函数,表示用户订阅的具体服务
fn add_listener(&mut self, event: &str, callback: EventCallback) {
let callbacks = self._events.entry(event.to_string()).or_insert(Vec::new());
callbacks.push(callback);
}
/// 移除事件监听器:相当于用户取消订阅
fn remove_listener(&mut self, event: &str, callback: &EventCallback) {
if let Some(callbacks) = self._events.get_mut(event) {
callbacks.retain(|cb| cb != callback);
}
}
/// 触发事件:相当于发布消息或服务,也就是事件发生时,将订阅者订阅的服务一一为订阅者执行
fn emit(&self, event: &str) {
if let Some(callbacks) = self._events.get(event) {
for callback in callbacks {
callback();
}
}
}
}3.2 一个比较初步的功能增强不过很多时候我们还不满足于此,比如能够限制监听器的数量。从现实生活中打个比方,就像我们只服务一定数量的客户,一旦订满,由于资源有限,不再接收其它订阅。更贴切实际地说,就像节假日你去旅游景区地酒店订房间,对于酒店来说,一旦所有房间都预定满了,就不再接收新的订阅了——除非,有已经订阅地客人退订了它们先前已经预定地房间。实现这样一个功能,只需要一个变量 _max_listeners 作为最大监听器数量的控制变量。在外部相应的我们需要允许用户修改和读取该变量的值,因此还要提供 set_max_listeners 和 get_max_listeners 两个方法。use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type EventCallback = Arc<dyn Fn() + Send + Sync>;
pub struct EventEmitter {
_events: Mutex<HashMap<String, Vec<EventCallback>>>,
_max_listeners: usize,
}
impl EventEmitter {
pub fn new() -> Self {
EventEmitter {
_events: Mutex::new(HashMap::new()),
_max_listeners: usize::MAX,
}
}
/// 设置最大监听器数量
/// Set the maximum number of listeners
pub fn set_max_listeners(&mut self, max_listeners: usize) {
self._max_listeners = max_listeners;
}
/// 获取最大监听器数量
pub fn get_max_listeners(&self) -> usize {
self._max_listeners
}
/// 添加事件监听器
pub fn add_listener(&self, event: &str, callback: EventCallback) {
let mut events = self._events.lock().unwrap();
let callbacks = events.entry(event.to_string()).or_insert(Vec::new());
callbacks.push(callback);
}
/// 移除事件监听器
pub fn remove_listener(&self, event: &str, callback: &EventCallback) {
let mut events = self._events.lock().unwrap();
if let Some(callbacks) = events.get_mut(event) {
callbacks.retain(|cb| !Arc::ptr_eq(cb, callback));
}
}
/// 触发事件
pub fn emit(&self, event: &str) {
let events = self._events.lock().unwrap();
if let Some(callbacks) = events.get(event) {
for callback in callbacks {
let callback_clone = callback.clone();
// Spawn a new thread to run each callback asynchronously
std::thread::spawn(move || {
(*callback_clone)();
});
}
}
}
}4. 使用现成的第三方模块:EventEmitter4.1 EventEmitter 的安装你可以直接使用 cargo 包管理器安装 EventEmitter:cargo add EventEmitter4.2 在你的项目中使用 EventEmitter以下是一段包括引入 EventEmitter 和使用的例子:use std::sync::{Arc};
use EventEmitter::EventEmitter;
fn main() {
let emitter = EventEmitter::new();
let callback1 = Arc::new(|| println!("[event1 emitted]: The first callback of event1 has been called."));
let callback2 = Arc::new(|| println!("[event1 emitted]: The second callback of event1 has been called."));
let callback3 = Arc::new(|| println!("[event2 emitted]: The only one callbask of event2 has been called."));
// Add event listener
emitter.on("event1", callback1);
emitter.on("event1", callback2);
emitter.on("event2", callback3);
let ct1 = emitter.listener_count("event1");
let ct2 = emitter.listener_count("event2");
println!("Number of Listeners for event1 is: {ct1}, \nNumber of Listeners for event2 is: {ct2}");
emitter.emit("event1"); // Emit event1
emitter.emit("event2"); // Emit event1
}运行项目:cargo run可以看到控制台打印结果:Number of Listeners for event1 is: 2,
Number of Listeners for event2 is: 14.3 EventEmitter 实例上的方法4.3.1 set_max_listeners 方法pub fn set_max_listeners(&mut self, max_listeners: usize)设置最大监听器数量。4.3.2 set_max_listeners 方法pub fn get_max_listeners(&self) -> usize获取最大监听器数量。4.3.3 on 方法pub fn on(&self, event: &str, callback: Arc<dyn Fn() + Send + Sync>)添加事件监听器。4.3.4 add_listener 方法pub fn add_listener(&self, event: &str, callback: Arc<dyn Fn() + Send + Sync>)添加事件监听器,是 on 方法的别名。4.3.5 off 方法pub fn off(&self, event: &str, callback: &Arc<dyn Fn() + Send + Sync>)移除事件监听器。4.3.6 remove_listener 方法pub fn remove_listener(
&self,
event: &str,
callback: &Arc<dyn Fn() + Send + Sync>
)移除事件监听器,是 off 方法的别名。4.3.7 emit 方法pub fn emit(&self, event: &str)触发事件。触发相当于“发布-订阅”模式中的“发布”,一但某个事件被触发,该事件对应得所有监听器函数都会被执行。监听器就相当于“订阅者”。4.3.8 remove_all_listeners 方法pub fn remove_all_listeners(&self, event: &str)移除所有事件的所有监听器。4.3.9 prepend_listener 方法pub fn prepend_listener(
&self,
event: &str,
callback: Arc<dyn Fn() + Send + Sync>
)从指定事件监听器向量的前方插入新的监听器。该方法与使用 on、add_listener 方法添加新的监听器时,插入监听器向量的方向相反。4.3.10 listeners 方法pub fn listeners(&self, event: &str) -> Vec<Arc<dyn Fn() + Send + Sync>>获取指定事件的监听器。4.3.11 listener_count 方法pub fn listener_count(&self, event: &str) -> usize获取指定事件的监听器数量。
Python 元编程
一个高级 Python 知识点是元编程(Metaprogramming),它指的是编写代码来操作程序本身(例如,动态修改代码、添加新的类或函数等)。元编程可以通过以下方法实现:
使用元类(Metaclasses):元类是用于创建类的类。在 Python 中,元类是派生自 type 的类,并定义了 __new__ 和 __init__ 两个方法。使用元类可以动态创建类、添加特定功能以及自定义类的行为。
使用装饰器(Decorators):装饰器是一种用于修改函数或类的行为的语法结构。装饰器可以用于创建缓存、跟踪执行时间、执行前后的处理等。装饰器也可以增加或删除函数的参数或者修改函数的返回值等。
动态创建类和函数:Python 是一种动态语言,可以动态创建类或函数。通过使用 type() 函数,可以在运行时创建一个新的类。通过使用 exec() 函数或者 eval() 函数,可以在运行时动态创建函数,这可以在构建 DSL 时非常有用。
使用协议(Protocols):协议指的是一套规范,用于描述对象应该具有的属性或者方法。在 Python 中,协议是一种非正式的接口,它定义了哪些方法或属性需要以特定方式实现。通过使用协议,可以实现多态(Polymorphism)或者混入(Mixin)的设计模式。
元编程在 Python 中被广泛使用,在许多框架和库中都有应用。例如,Django 中的 ORM 使用元类来生成 SQL 查询;Flask 中的装饰器用于定义路由;PyTorch 中的动态图机制使用了 Python 的动态创建类和函数等技术。
【21天python打卡】第1天 python预备知识(1)
1、基本概念1.1、模块(包)??模块 module:一般情况下,是一个以.py为后缀的文件。其他可作为module的文件类型还有".pyo"、".pyc"、".pyd"、".so"、".dll",但Python初学者几乎用不到。module 可看作一个工具类,可共用或者隐藏代码细节,将相关代码放置在一个module以便让代码更好用、易懂,让coder重点放在高层逻辑上。module能定义函数、类、变量,也能包含可执行的代码。module来源有3种:①Python内置的模块(标准库);②第三方模块;③自定义模块。包 package: 为避免模块名冲突,Python引入了按目录组织模块的方法,称之为 包(package)。包 是含有Python模块的文件夹。1.2、表达式表达式,是由数字、算符、数字分组符号(括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合1.2.1、表达式特点? 表达式一般仅仅用于计算一些结果,不会对程序产生实质性的影响? 如果在交互模式中输入一个表达式,解释器会自动将表达式的结果输出1.3、?语句一个语法上自成体系的单位,它由一个词或句法上有关连的一组词构成语句的执行一般会对程序产生一定的影响,在交互模式中不一定会输出语句的执行结果1.4、程序(program)程序就是由一条一条的语句和一条一条的表达式构成的。1.5函数(function)函数就是一种语句,函数专门用来完成特定的功能。函数长的形如:xxx()1.5.1 函数的分类:? ?内置函数 : 或者内建函数,就是由语法规定存在的函数,这些函数,包含在编译器的运行时库中,程序员不比单独书写代码实现它,只需要调用既可。? ?自定义函数 ?: ?由程序员自主的创建的函数 ?当我们需要完成某个功能时,就可以去调用内置函数,或者自定义函数1.5.2 函数的2个要素? ?参数? ?返回值2、Python编程思想Python是一种面向对象oop(Object Oriented Programming)的脚本语言。面向对象是采用基于对象(实体)的概念建立模型,模拟客观世界分析、设计、实现软件的办法。在面向对象程序设计中,对象包含两个含义,其中一个是数据,另外一个是动作。面向对象的方法把数据和方法组合成一个整体,然后对其进行系统建模。2.1、基本的程序设计模式任何的程序设计都包含IPO,它们分别代表如下:I:Input 输入,程序的输入P:Process 处理,程序的主要逻辑过程O:Output 输出,程序的输出因此如果想要通过计算机实现某个功能,那么基本的程序设计模式包含三个部分,如下:确定IPO:明确需要实现功能的输入和输出,以及主要的实现逻辑过程;编写程序:将计算求解的逻辑过程通过编程语言进行设计展示;调试程序:对编写的程序按照逻辑过程进行调试,确保程序按照正确逻辑正确运行。也是所有语言都会设计这三部分。2.2、解决复杂问题的有效方法:自顶向下(设计)所有的语言都是为了帮助我们更好的解决复杂问题,python解决复杂问题的有效方法:自顶向下(设计)。下面我具体介绍。2.2.1 自顶向下-分而治之如果要实现功能的逻辑比较复杂的时候,就需要对其进行模块化设计,将复杂问题进行分解,转化为多个简单问题,其中简单问题又可以继续分解为更加简单的问题,直到功能逻辑可以通过模块程序设计实现,这也是程序设计的自顶向下特点。总结如下:将一个总问题表达为若干个小问题组成的形式使用同样方法进一步分解小问题直至,小问题可以用计算机简单明了的解决2.2.2?举例:的斐波那契数列的斐波那契数列是一道金典的数学题,相信大家再也熟悉不过了。今天通过举例介绍,具体了解python在解决实际问题的便利,更好的解决问题。自顶向下的方式其实就是使用递归来求解子问题,最终解只需要调用递归式,子问题逐步往下层递归的求解。程序设计:cache = {}
def fib(number):
if number in cache:
return cache[number]
if number == 0 or number == 1:
return 1
else:
cache[number] = fib(number - 1) + fib(number - 2)
return cache[number]
if __name__ == '__main__':
print(fib(35))运行结果:1. 14930352
2. >>>理解自顶向下的设计思维:分而治之3、Python安装我们来介绍怎么安装python,以Windows为例。3.1、 Windows系统下python安装此次安装主要针对windows开发,因此是在windows 10系统下进行安装。3.1.1 python下载安装首先我们去官网下载python,官网网址如下。Python官网:Python Releases for Windows | Python.org?在这里找到自己需要的版本进行下载即可,但是建议大家不要下载最新版本,懂得都懂。。。不够稳定,bug比较多。下载完成后运行安装即可。傻瓜式安装,我就不多说。3.2 、Python环境变量配置?手动添加python环境变量过程找到计算机,点击鼠标右键在弹出的选项中点击【属性】,然后点击【高级系统配置】点击【环境变量】在系统变量中,找到Path,双击,在打开的编辑系统变量中,在末尾添加一个英文的分号,将python软件安装路径复制进去就可以了4、Python运行方式4.1、交互模式下执行 Python这种模式下,无需创建脚本文件,直接在 Python解释器的交互模式下编写对应的 Python 语句即可。1)打开交互模式的方式:Windows下:在开始菜单找到“命令提示符”,打开,就进入到命令行模式;在命令行模式输入: python 即可进入 Python 的交互模式Linux 下:直接在终端输入 python,如果是按装了 python3 ,则根据自己建的软连接的名字进入对应版本的 Python 交互环境,例如我建立软连接使用的 python3,这输入 python3。2)在交互模式下输出: Hello World!3)退出交互模式,直接输入 exit() 或者quit() 即可。4.2、通过脚本输出通过文本编辑器,编写脚本文件,命名为 hello.py,在命令行模式下输入 python hello.py 即可。4.3、交互模式和脚本文件方式的比较1)在交互模式下,会自动打印出运算结果,而通过脚本文件的方式不会交互模式:[node02@localhost code]$ python3
Python 3.6.5 (default, Oct 19 2018, 10:46:59)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 100+200
300
>>> exit()脚本文件:没有任何输出,此时要想输出,必须使用 print 函数进行打印。[node02@localhost code]$ vi cal.py
[node02@localhost code]$ cat cal.py
100+200
[node02@localhost code]$ python3 cal.py
[node02@localhost code]$ 2)在交互模式下,每次输入的语句不会被保存,退出交互环境之后即消失,但是通过脚本文件我们可以保存我们写过的所有语句。所以通常都是通过编写 脚本文件的方式来编写 Python 代码。注意:在编写脚本文件的时候不要使用 word 和 windows 自带的笔记本,因为他们在保存的时候会保存为 utf-8 BOM 格式,这会导致脚本执行错误。可以使用 sublime,editplus,notepad++今天就介绍到这里,相信到这里,你肯定学到了不少的东西。下一篇我们继续介绍关于python的预备知识。谢谢大家。
java202303java学习笔记第二十三天-接口中的设计模式适配器模式
一些计算机基础知识的考试复习题
2013Excel 里用 AND 在开头连接多个条件。立即寻址访问速度最快。直接寻址方式下,操作数在内存中,指令中给出操作数的地址,需要再访问一次内存来得到操作数。立即寻址方式下,操作数在指令中,所以在取得指令时就得到操作数,是速度最快的。寄存器寻址方式下,操作数在CPU的寄存器中,与在内存中取得操作数相比,该方式下获取操作数的速度是很快的。寄存器间接寻址方式下,操作数的地址在CPU的寄存器中,还需要访问一次内存来得到操作数。CPU中有一些重要的寄存器,其中程序计数器中存放待执行指令的内存地址,指令寄存器则存放正在执行的指令,状态寄存器用于保存指令执行完成后产生的条件码,通用寄存器则作为暂时存放数据的存储设备,相对于主存储器,访问寄存器的速度要快得多。表示是文件格式,表现是具体设备。数字签名(Digital Signature)技术是不对称加密算法的典型应用,其主要功能是保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。数字签名的应用过程是:数据源发送方使用自己的私钥对数据校验和其他与数据内容有关的变量进行加密处理,完成对数据的合法“签名”,数据接收方则利用对方的公钥来解读收到的“数字签名”,并将解读结果用于对数据完整性的检验,以确认签名的合法性。利用数字签名技术将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用Hash函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。数字签名是加密的过程,而数字签名验证则是解密的过程。设数组a1…n,1…m中的元素以列为主序存放,每个元素占用1个存储单元,则数组元素a[i,j](1≤i≤n, 相对于数组空间首地址的偏移量为(35)。(35)A.(i-1)*m+j—1 B.(i-1)*n+j-1 C.(j-1)*m+i-1 D.(j-1)*n+i-1存储数组元素时,需要将元素按照某种顺序排列。对于二维及多维数组,则有按行存储和按列存储两种方式,其不同在于同一个元素相对于数组空间起始位置的偏移量不同。本问题中n行m列的二维数组a[1…n,1…m]是按列存储,贝树于元素a[i,j]来说,它之前有完整的j-1列、每列n个元素,在第j列上排在a[i,j]之前的元素个数是i-1个,因此排列在ahj]之前的元素个数为(j-1)*n+i-1,由于每个元素占一个单元,该表达式的值就是偏移量。用链表作为栈的存储结构,不存在栈满的情况出现。地址0.0.0.0表示本地地址,只能作为源地址使用,不能用作目标地址。地址127.0.0.1表示本地环路地址,通常作为目标地址,用于测试本地TCP/IP回路。上半年二进制 八进制000 0001 1010 2011 3100 4101 5110 6111 7二进制转换成八进制的方法是,取三合一法,即从二进制的小数点为分界点,向左(或向右)每三位取成一位,分好组以后,对照二进制与八进制数的对应表(上面的表),将三位二进制按权相加,得到的数就是一位八进制数,然后按顺序排列,小数点的位置不变,最后得到的就是一个八进制数。8进制转2进制 - 1分为315.计算机软件既是作品,又是一种使用工具,还是一种工业产品(商品),具备作品性、工具性、商业性特征。因此对于计算机软件保护来说,仅依靠某项法律或法规不能解决软件的所有知识产权问题,需要利用多层次的法律保护体系对计算机软件实施保护。我国已形成了比较完备的计算机软件知识产权保护的法律体系,即已形成以著作权法、计算机软件保护条例、计算机软件著作权登记办法保护为主,以专利法、反不正当竞争法、合同法、商标法、刑法等法律法规为辅的多层次保护体系,可对计算机软件实施交叉和重叠保护。在这样的保护体系下,计算机软件能够得到全面的、适度的保护。例如,计算机软件符合专利法所保护的法定主题,就可以申请专利,利用专利法来保护其中符合发明创造条件的创造性成果。对于那些为极少数专门用户开发的专用软件,可以利用反不正当竞争法中的商业秘密权和合同法来保护其中的技术秘密。我国没有专门针对知识产权制定统一的法律(知识产权法),而是在民法通则规定的原则下,根据知识产权的不同类型制定了不同的单项法律及法规,如著作权法、商标法、专利法、计算机软件保护条例等,这些法律、法规共同构成了我国保护知识产权的法律体系。数值X的补码记作[X]补,如果机器字长为n,则最高位为符号位,0表示正号,1表示负号,表示的整数范围为-2[n-1]?+(2[n-1]-1)。正数的补码与其原码和反码相同,负数的补码则等于其反码的末尾加1。因此字长为64时,用补码表示时的最小整数为-2[63]。对于容量为32KX32位、按字编址(字长为32)的存储器,其地址线的位数应为多少个?问题在求 32K是2的多少次方?在段页式管理中,如果地址长度为32位,并且地址划分如下图所示:在这种情况下,系统页面的大小应为4KB,且最多有1024个段,每段最大为4096KB页内的地址长度为12位,所以页面的大小应该为212=4096=4KB。段号的地址长度为10位时,最多有2[10]=1024个段。又因为页号的地址长度为10位,故每个段最多允许有2[10]=1024个页面,由于页面的大小=4KB,故段的大小最大为4096KB。程序设计语言的基本成分有数据成分、运算成分、控制成分和传输成分。其中,数据成分用于描述程序所涉及的数据;运算成分用以描述程序中所包含的运算;控制成分用以描述程序中所包含的控制;传输成分,用以表达程序中数据的传输。控制成分指明语言允许表述的控制结构,程序员使用控制成分来构造处理数据时的控制逻辑。理论上己经证明可计算问题的程序都可以用顺序、选择和循环这三种控制结构来描述。由二叉树的先序遍历序列和中序序列进行二叉树的重构要点是:根据先序遍历序列可以找出整棵树及各个子树的根结点,然后根据中序序列划分左、右子树中的结点。快速排序的基本思想是:通过一趟排序将待排的记录划分为独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再分别对这两部分记录继续进行快速排序,以达到整个序列有序。一趟快速排序的具体做法是:附设两个位置指示变量i和j,它们的初值分别指向序列的第一个记录和最后一个记录。设枢轴记录(通常是第一个记录)的关键字为pivotkey,则首先从j所指位置起向前搜索,找到第一个关键字小于pivotkey的记录,将其向前移,然后从i所指位置起向后搜索,找到第一个关键字大于pivotkey的记录,将其向后移,重复这两步直至i与j相等为止。显然,上述的过程需要顺序存储,以利于对元素迅速地定位。Peter Coad和Edward Yourdon提出用下面的等式识别面向对象方法:面向对象=对象(object)+分类(classification)+继承(inheritance)+通过消息的通信(communication with messages)可以说,采用这4个概念开发的软件系统是面向对象的。45.UML2.0中提供了多种图形。序列图是场景的图形化表示,描述了以时间顺序组织的对象之间的交互活动,对用例中的场景可以采用序列图进行描述。状态图展现了一个状态机,用于建模时间如何改变对象的状态以及引起对象从一个状态向另一个状态转换的事件,关注系统的动态视图。对象图展现了一组对象以及它们之间的关系,描述了在类图中所建立的事物实例的静态快照,从真实的或原型案例的角度给出系统的静态设计视图或静态进程视图。通信图强调收发消息的对象之间的结构组织。类图展现了一组对象、接口、协作和它们之间的关系,在开发软件系统时,类图用于对系统的静态设计视图建模。组件图展现了一组组件之间的组织和依赖,专注于系统的静态实现视图,与类图相关,通常把组件映射为一个或多个类、接口或协作。包图描述类或其他UML构件如何组织成包,以及这些包之间的依赖关系。类的继承支持多态的实现。多态有参数多态、包含多态、过载多态和强制多态四类。参数多态是应用比较广泛的多态,被称为最纯的多态,包含多态在许多语言中都存在,最常见的例子就是子类型化,即一个类型是另一个类型的子类型。过载多态是同一个名字在不同的上下文中所代表的含义不同。分层数据流图是结构化分析方法的重要组成部分,顶层数据流图表示目标系统与外部环境的关系,仅有目标系统一个加工。在进行软件设计的时候,模块独立性是创建良好设计的一个重要原则,一般采用模块间的耦合和模块的内聚两个准则来进行度量。内聚是模块功能强度的度量,一个模块内部各个元素之间的联系越紧密,则它的内聚性就越高,模块独立性就越强,一般来说,模块内聚性由低到高有偶然内聚、逻辑内聚、时间内聚、过程内聚、通信内聚、信息内聚和功能内聚七种。若一个模块把几种相关的功能组合在一起,每次被调用时,由传送给模块的判定参数来确定该模块应执行哪一种功能,则该模块的内聚类型为逻辑内聚。若一个模块内的处理是相关的,而且必须以特定次序执行,则称这个模块为过程内聚模块。信息内聚模块完成多个功能,各个功能都在同一个数据结构上操作,每一项功能有一个唯一的入口点。若一个模块中各个部分都是完成某一个具体功能必不可少的组成部分,则该模块为功能内聚模块,根据上述分析,本题的模块内聚类型为信息内聚。A级:10.0.0.1 - 10.255.255.254B级:172.16.0.1 - 172.31.255.254C级:192.168.0.1 - 192.168.255.254virtual double getGPA()=0 c++abstract double getGPA() javaStudent(stuNo,name,gs)super(stuNo,name,grades): public Instrument 继承(2)implements Instrument(3)extends Windword是数组名,因此可以直接作为地址。CPU主要由运算器、控制器、寄存器组和内部总线等部件组成。计算机软件著作权的保护对象是指(17)。A.软件开发思想与设计方案B.计算机程序及其文档C.计算机程序及算法D.软件著作权权利人CRC表示循环冗余检验码。模2除法与算术除法类似,但每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在循环冗余校验码(CRC)的计算中有应用到模2除法。二分查找向下取整,不四舍五入在面向对象系统设计中,每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计约束条件下是否还能使用,以及使用的效果和如何取舍。按照设计模式的目的可以分为创建型模式、结构型模式和行为型模式3大类。创建型模式与对象的创建有关:结构型模式处理类或对象的组合,涉及如何组合类和对象以获得更大的结构:行为型模式对类或对象怎样交互和怎样分配职责进行描述。创建型模式包括Factory Method、Abstract Factory、Builder、Prototype和Singleton;结构型模式包括Adapter(类)、Adapter(对象)、Bridge、Composite、Decorator、Fa?ade、Flyweight和Proxy;行为型模式包括Interpreter、Template Method、Chain of Responsibility、Command、Iterator、Mediator、Memento Observer State Strategy和Visitor。语句覆盖很弱。若关系R与S具有相同的关系模式,即关系R与S的结构相同,则关系R与S可以进行并、交、差运算。下半年从用户角度看, 该系统所具有的主存容量将比实际主存容量大得多, 人们把这样的存储器称为虚拟存储器。因此,虚拟存储器是为了扩大用户所使用的主存容量而采用的一种设计方法。软件著作权从软件作品性的角度保护其表现形式, 源代码(程序)、 目标代码(程序)、 软件文档是计算机软件的基本表达方式(表现形式), 受著作权保护;专利权从软件功能性的角度保护软件的思想内涵, 即软件的技术构思、程序的逻辑和算法等的思想内涵,涉及计算机程序的发明, 可利用专利权保护;商标权可从商品(软件产品)、 商誉的角度为软件提供保护, 利用商标权可以禁止他人使用相同或者近似的商标, 生产(制作)或销售假冒软件产品, 商标权保护的力度大于其他知识产权, 对软件侵权行为更容易受到行政查处。 商业秘密权可保护软件的经营信息和技术信息, 我国《反不正当竞争法》中对商业秘密的定义为 “不为公众所知悉、 能为权利人带来经济利益、 具有实用性并经权利人采取保密措施的技术信息和经营信息”。软件技术信息是指软件中适用的技术情报、 数据或知识等, 包括程序、 设计方法、 技术方案、 功能规划、 开发情况、 测试结果及使用方法的文字资料和图表, 如程序设计说明书、 流程图、 用户手册等。 软件经营信息指经营管理方法以及与经营管理方法密切相关的信息和情报, 包括管理方法、 经营方法、 产销策略、 客户情报(客户名单、 客户需求〉, 以及对软件市场的分析、 预测报告和未来的发展规划、 招投标中的标底及标书内容等。防火墙通常分为内网、 外网和DMZ三个区域, 按照默认受保护程度,从低到高正确的排列次序为外网、 DMZ和内网。安全传输电子邮件通常采用PGP系统。-31/64的原码:长度为0字符串称为空串(即不包含字符的串〉, 而空白串是指由空白符号(空格、 制表符等〉构成的串, 其长度不为0。UML2.0中提供了13种图形,一部分图给出了系统的动态视图,一部分图则给出系统的静态视图。活动图展现了在系统内从一个活动到另一个活动的流程, 专注于系统的动态视图,它对于系统的功能建模特别重要,并强调对象间的控制流程,是状态图的一种特殊情况。通信图强调收发消息的对象之间的结构组织, 强调参加交互的对象的组织。序列图是场景的图形化表示, 描述了以时间顺序组织的对象之间的交互活动, 对用例中的场景可以采用序列图进行描述。类图展现了一组对象、 接口、 协作及其之间的关系, 属于静态视图;对象图展现了某一时刻一组对象以及它们之间的关系, 描述了在类图中所建立的事物的实例的静态快照;组件图/构件图展现了一组构件之间的组织和依赖, 专注于系统的静态实现视图, 它与类图相关, 通常把构件映射为一个或多个类、 接口或协作:包图是用于把模型本身组织成层次结构的通用机制, 不能执行, 展现由模型本身分解而成的组织单元以及其间的依赖关系。正确性维护,是指改正在系统开发阶段已发生而系统测试阶段尚未发现的错误;适应性维护,是指使应用软件适应新技术变化和管理需求变化而进行的修改;完善性维护,是指为扩充功能和改善性能而进行的修改,主要是指对已有的软件系统增加一些在系统分析和设计阶段中没有规定的功能与性能特征;预防性维护,是指为了改进应用软件的可靠性和可锥护性,为了适应未来的软硬件环境的变化,主动增加预防性的功能,以使应用系统适应各类变化而不被淘汰。下面列出4个IP地址中,不能作为主机地址的是(67)。(67)A.127.0.10.1 B.192.168.192.168 C.10.0.0.10 D.210.224.10.1选 A常用的IP地址有三种基本类型,由网络号的第一个字节来区分。A类地址的第一个字节为1?126,数字0和127不能作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机。B类地址的第一个字节为128?191。C类地址的第 一个字节为192?223。D类地址(组播)的第一个字节为224?239。E类地址(保留) 的第一个字节为240?254。HTTP、Telnet、SMTP传输层均采用TCP, SNMP传输层采用SNMP。上半年MIPS是单字长定点指令平均执行速度Million Instructions Per Second的缩写,每秒处理百万级的机器语言指令数。这是衡量CPU速度的一个指标。MFLOPS (Million Floating-point Operations per Second,每秒百万个浮点操作)是衡量计算机系统的技术指标,不能反映整体情况,只能反映浮点运算情况。CPI是指每条指令的时钟周期数(Clockcycle Per Instruction)。王某按照其所属公司要求而编写的软件文档著作权公司享有。表现媒体是指进行信息输入和输出的媒体,如键盘、鼠标、话筒,以及显示器、打印机、喇叭等;表示媒体指传输感觉媒体的中介媒体,即用于数据交换的编码,如图像编码、文本编码和声音编码等;传输媒体指传输表示媒体的物理介质,如电缆、光缆、电磁波等;存储媒体指用于存储表示媒体的物理介质,如硬盘、光盘等。声音是通过空气传播的一种连续的波,称为声波。声波在时间和幅度上都是连续的模拟信号。音频信号主要是人耳能听得到的模拟声音(音频)信号,音频信号经计算机系统处理后送到扬声器的信号是模拟信号。上半年中间代码生成阶段的工作是根据语义分析的输出生成中间代码。“中间代码”是一种简单且含义明确的记号系统,可以有若干种形式,它们的共同特征是与具体的机器无关。中间代码的设计原则主要有两点:一是容易生成,二是容易被翻译成目标代码。后缀式表示法的优点是根据运算对象和运算符的出现次序进行计算,不需要使用括号,也便于用栈实现求值。UML中有4种事物:结构事物、行为事物、分组事物和注释事物。结构事物是UML模型中的名词,通常是模型的静态部分,描述概念或物理元素。结构事物包括类(Class)、 接口(Interface)、协作(Collaboration)、用例(Use Case)、主动类(Active Class)、构件(Component)、制品〈Artifact)和结点(Node)。行为事物是UML模型的动态部分。 它们是模型中的动词,描述了跨越时间和空间的行为。行为事物包括:交互(Interaction)、 状态机(State Machine)和活动(Activity)。分组事物是UML模型的组织部分,是一些由模型分解成的“盒子”,最主要的分组事物是包(Package)。注释事物是UML模的解释部分。这些注释事物用来描述、说明和标注模型的任何元素。注解(Note)是一种 主要的注释事物。UML2.0中提供了多种图形,描述系统的静态和动态方面。交互图用于对系统的动态方面进行建模。一张交互图表现的是一个交互,由一组对象和它们之间的关系组成,包含它们之间可能传递的消息。交互图表现为序列图、通信图、交互概览图和时序图, 每种针对不同的目的,适用于不同的情况。序列图是强调消息时间顺序的交互图;通信图是强调接收和发送消息的对象的结构组织的交互图;交互概览图强调控制流的交互图。 时序图(TimingDiagram)关注沿着线性时间轴、生命线内部和生命线之间的条件改变。对象图展现了某一时刻一组对象以及它们之间的关系。对象图描述了在类图中所建立的事物的实例的静态快照,给出系统的静态设计视图或静态进程视图。结构化程序设计方法中使用结构图来描述软件系统的体系结构,指出一个软件系统由哪些模块组成,以及模块之间的调用关系。其基本成分有模块、调用和数据。模块是指具有一定功能并可以用模块名调用的一组程序语句,是组成程序的基本单元,用矩形表示。模块之间的调用关系用从一个模块指向另一个模块的箭头表示,表示前者调用了后者。模块之间还可以用带注释的短箭头表示模块调用过程中来回传递的信息,箭头尾部带空心圆表示传递的是数据,带实心圆表示传递的是控制信息。结构图有四种特征,其中:深度指结构图控制的层次,即模块的层数;宽度指一层中最大的模块数;扇出指一个模块的直接下属模块数;扇入指一个模块的直接上属模块数。关系数据库系统采用关系模型作为数据的组织方式,在关系模型中用表格结构表达实体集,以及实体集之间的联系,其最大特色是描述的一致性。可见,关系数据库是表的集合,其结构是由关系模式定义的。CHECK(性别 IN (’M’,F))进行完整性约束。计算机系统中,虚拟存储体系由(7)两级存储器构成。(7)A.主存一辅存 B.寄存器一Cache C.寄存器一主存 D.Cache一主存先申请原则,是指当两个以上的人就同一发明分别提出申请时,不问其作出该项发明的时间的先后,而按提出专利申请时间的先后为准,即把专利权授予最先提出申请的人,我国和世界上大多数国家都采用这一原则。频带宽度或称为带宽,它是描述组成复合信号的频率范围。音频信号的频带越宽,所包含的音频信号分量越丰富,音质越好。根据浮点数的表示法,尾数决定位数。决定精度。53/64等于0.828125,用这个数不断乘以2,取每一次结果的整数部分,小数部分继续乖以2,取足8位即可,即0.82815X2=1.65625,取1,0.65625X2=1.3125,取1,0.3125X2=0.625,取0,0.625X2=1.25,取1,0.25X2=0.5,取0,0.5X2=1.0,取1,不足部分后面补0,得1101010,因为是负数,符号位用1表示,即变成了11101010,这是原码。在原码的基础上,数据位取反得10010101,然后加1,得10010110。解释方式执行程序时,并不产生中间代码,而是直接分析执行。源程序中的注释会在代码优化时被删除。C语言基础,取余运算%只能是整形数据参与,其他类型在编译时会出错。这种错误属于语法错误,因为只有语法错误编译时肯定通不过,系统也会报错,指出错误在哪个地方,语义错误是指所表达的意思错误,在编译时不一定能检查出来。程序代码中的错误可分为语法错误和语义错误。程序语言的语法表述的是语言的形式,或者说是语言的样子和结构。程序语言还有更重要的一个方面,就是附着于语言结构上的语义。语义揭示了程序本身的含义、施加于语言结构上的限制或者要执行的动作。程序语言的语义分为静态语义和动态语义。编译时进行的是静态语义的分析,主要包括:检查语言结构的语义是否正确,即是否结构正确的句子所表示的意思也合法;执行规定的语义动作,如表达式的求值、符号表的填写、中间代码的生成等。整除取余运算符“%”的有效运算对象是两个整数。在形式上,只要“%”的两个运算对象存在,其语法就是正确的;在语义上,“%”的运算对象中有浮点数则不符合整除取余运算的含义。因此,这是运算对象与运算符的类型不匹配错误,届于静态语义错误,在编译阶段可以发现该错误。路径覆盖是指程序中所有可能执行的语句至少执行一次,设计的测试用例要保证在测试中程序的每一条独立路径都执行过.不要被 if 条件判断即菱形框迷惑了。Program(73)describes program’s objectives, desired output, input data required, processing requirement, and documentation.(73)A.specification B.flowchart C.structure D.address十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为零时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。例如把 (173)10 转换为二进制数。解:2.十进制小数转换为二进制小数十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。例如把(0.8125)转换为二进制小数。解:下半年磁盘存储器由盘片、驱动器、控制器和接口组成。盘片用来存储信息。驱动器用于驱动磁头沿盘面作径向运动以寻找目标磁道位置,驱动盘片以额定速率稳定旋转,并且控制数据的写入和读出。硬盘中可记录信息的磁介质表面叫做记录面。每一个记录面上都分布着若干同心的闭合圆环,称为磁道。数据就记录在磁道上。使用时要对磁道进行编号,按照半径递减 的次序从外到里编号,最外一圈为0道,往内道号依次增加。为了便于记录信息,磁盘上的每个磁道又分成若干段,每一段称为一个扇区。位密度是指在磁道圆周上单位长度内存储的二进制位的个数。虽然每个磁道的周长 不同,但是其存储容量却是相同的,因此,同一个磁盘上每个磁道的位密度都是不同的。 最内圈的位密度称为最大位密度。磁盘的容量有非格式化容量和格式化容量之分。一般情况下,磁盘容量是指格式化 容量。非格式化容量=位密度X内圈磁道周长X每个记录面上的磁道数X记录面数格式化容量=每个扇区的字节数X每道的扇区数X每个记录面的磁道数X记录面数寻道时间是指磁头移动到目标磁道(或柱面)所需要的时间,由驱动器的性能决定, 是个常数,由厂家给出。等待时间是指等待读写的扇区旋转到磁头下方所用的时间,一 般选用磁道旋转一?周所用时间的一半作为平均等待时间。提高磁盘转速缩短的是平均等 待时间。13.注册商标所有人是指(13)。(13)A.商标使用人 B.商标设计人 C.商标权人 D.商标制作人【答案】C【解析】商标权人是指依法享有商标专用权的人。在我国,商标专用权是指注册商标专用权。注册商标是指经国家主管机关核准注册而使用的商标,注册人享有专用权。未注册商标是指未经核准注册而自行使用的商标,其商标使用人不享有法律赋予的专用权。商标所有人只有依法将自己的商标注册后,商标注册人才能取得商标权,其商标才能得到法律的保护。商标权不包括商标设计人的权利,商标设计人的发表权、署名权等人身权在商标的使用中没有反映,它不受商标法保护,商标设计人可以通过其他法律来保护属于自己的权利。例如,可以将商标设计图案作为美术作品通过著作权法来保护;与产品外观关系密切的商标图案还可以申请外观设计专利通过专利法保护。如果浮点数的尾数用补码表示,则(20)是规格化的数。(20)A.1.01000 B.1.11110 C.0.01001 D.1.11001页内地址20位用户编写的源程序不可避免地会有一些错误,这些错误大致可分为静态错误和动态错误。动态错误也称动态语义错误,它们发生在程序运行时,例如变景取零时作除数、引用数组元素下标越界等错误。静态错误是指编译时所发现的程序错误,可分为语法错误和静态语义错误,如单词拼写错误、标点符号错误、表达式中缺少操作数、括号不匹配等有关语言结构上的错误称为语法错误;而语义分析时发现的运算符与运算对象类型不合法等错误属于静态语义错误。循环队列是指采用顺序存储结构实现的队列。在顺序队列中,为了降低运算的复杂度,元素入队时,只修改队尾指针;元素出队时,只修改队头指针。由于顺序队列的存储空间是提前设定的,因此队尾指针会有一个上限值,当队尾指针达到其上限时,就不能只通过修改队尾指针来实现新元素的入队操作了。此时,可将顺序队列假想成一个环状结构,称之为循环队列,并仍然保持队列操作的简便性。邻接矩阵非0元素个数等于边数序列图是场景的图形化表示,描述了以时间顺序组织的对象之间的交互活动,对用例中的场景可以采用序列图进行描述。状态图展现了一个状态机,用于对对象的状态变化进行建模。活动图专注于系统的动态视图,它对于系统的功能建模特别重要,并强调对象间的控制流程,是状态图的一种特殊情况。通信图强凋收发消息的对象之间的结构组织。结构化开发方法由结构化分析、结构化设计和结构化程序设计构成,是一种面向数据流的开发方法。结构化方法总的指导思想是自顶向下、逐层分解,基本原则是功能的分解与抽象。它是软件工程中最早出现的开发方法,特别适合于数据处理领域的问题,但是不适合解决大规模的、特别复杂的项目,而且难以适应需求的变化。②完整性(integrality)是指数据库正确性和相容性,是防止合法用户使用数据库时向数据库加入不符合语义的数据。保证数据库中数据是正确的,避免非法的更新。ICMP (Internet control Message Protocol)与IP协议同属于网络层,用于传送有关通信问题的消息,例如数据报不能到达目标站,路由器没有足够的缓存空间,或者路由器向发送主机提供最短通路信息等。ICMP报文封装在IP数据报中传送,因而不保证可靠的提交。匿名FTP访问通常使用的用户名是anonymous。Many computer languages provide a mechanism to call (73) provided by libraries such as in .dlls.(73)A.instructions B.functions C.subprograms D.subroutines上半年一个汉字占两个字节。LEFTB,参数指定的是字节数CPU内部结构大概可以分为控制单元、运算单元、存储单元和时钟等几个主要部分。运算器是计算机对数据进行加工处理的中心,它主要由算术逻辑部件(ALU:Arithmetic and Logic Unit)、寄存器组和状态寄存器组成。ALU主要完成对二进制信息的定点算术运算、逻辑运算和各种移位操作。通用寄存器组(典型代表是累加寄存器)来保存参加运算的操作数和运算的中间结果。状态寄存器在不同的机器中有不同的规定,程序中,状态位通常作为转移指令的判断条件。控制器是计算机的控制中心,它决定了计算机运行过程的自动化。它不仅要保证程序的正确执行,而且要能够处理异常事件。主要有程序计数器PC、指令译码器、指令寄存器IR和操作控制器组成。其中PC又称为“指令计数器”,它保存了下一条要执行指令的地址,由于大多数指令的地址与前指令的修改的过程通常只是简单的将PC加1。如果遇到转移指定的时候,直接修改为转移指令给出的地址。指令寄存器用于储存现在正在被运行的指令。一条指令包括操作码和操作数,操作码就是表明了此指令要干什么,而这便是指令译码器的作用。时序控制逻辑要为每条指令按时间顺序提供应有的控制信号。数字信封是将对称密钥通过非对称加密(即:有公钥和私钥两个)的结果分发对称密钥的方法。数字信封是实现信息完整性验证的技术。数字信封是一种综合利用了对称加密技术和非对称加密技术两者的优点进行信息安全传输的一种技术。数字信封既发挥了对称加密算法速度快、安全性好的优点,又发挥了非对称加密算法密钥管理方便的优点。数字信封以发送方向接收方传递一段交易信息(如电子合同、支付通知单等)为例,发送方先在本地用对称密钥对交易信息进行加密,形成密文,再用接收方的公钥将用于加密交易信息的对称密钥加密,并将加密后的对称密钥信息和密文一同传递给接收方。接收方接收信息后,先用自己的私钥解密加密的对称密钥信息,得到用于加密交易信息的对称密钥,再用其解密密文得到交易信息原文。由于在传递过程中,加密后的对称密钥就像是被封装在一个"信封"里传递一样,因此被称为数字信封。语句覆盖。被测程序的每个语句至少执行一次。是一种很弱的覆盖标准。(2)判定覆盖。也称为分支覆盖,判定表达式至少获得一次“真”、“假”值。判定覆盖比语句覆盖强。(3)条件覆盖。每个逻辑条件的各种可能的值都满足一次。外模式/模式映象:定义在外模式描述中,把描述局部逻辑结构的外模式与描述全局逻辑结构的模式联系起来 ,保证逻辑独立性:当模式改变时,只要对外模式/模式映象做相应的改变,使外模式保持不变,则以外模式为依据的应用程序不受影响,从而保证了数据与程序之间的逻辑独立性,也就是数据的逻辑独立性。感觉在说数据库和程序交互的接口。模式/内模式映象:定义在模式描述中,把描述全局逻辑结构的模式与描述物理结构的内模式联系起来 ,保证物理独立性:当内模式改变时,比如存储设备或存储方式有所改变,只要模式/内模式映象做相应的改变,使模式保持不变,则应用程序保持不变。感觉在说数据库模型和底层存储的接口。202.116.1.12/21表示有21位为网络位,另外11位是主机位。因此,在子网掩码中,第三个字节的后面3位是0,因此子网掩码是255.255.248.0。下半年ROUND 四舍五入,保留N位小数主存和Cache 之间地址转换由硬件自动完成CPU 对主存访问:随机访问原码: -127 ~ 127;反码: -127 ~ 127;补码: -128 ~ 127其中-128的补码为10000000是人为规定。后缀式:运算符写在运算对象后面用栈检查括号是否匹配.左括号入栈,右括号出栈.需要出栈,栈空,说明不匹配.创建型设计模式与对象的创建有关,按照所用的范围分为面向类和面向对象两种。其中,(48)模式是创建型类模式。(48)A.工厂方法(Factory Method) B.构建器(Builder)C.原型(Prototype) D.单例(Singleton )A伪代码和流程图的区别:可以采用类似于程序设计语言的语法结构,也易于转换为程序拓扑排序SMTP 发送,POP3 接收DRAM 使用电容定期刷新,SRAM 静态不需要刷新.CPU 数据总线:一次能传递的二进制位数计算机软件著作权:《著作权法》+《计算机软件保护条例》-1023需要几个二进制位表示?2的10次方等于1024,需要11位表示。1023需要10位,加上符号位需要11位。-1023~1023循环队列rear 插入,front 删除。(rear-front+m)%m 元素个数队头:(read-size+M)%m对象:标识,属性和方法序列图描述了在一个用例或操作的执行过稈中以时间顺序组织的 对象之间的交互活动,图中对象发送和接收的消息沿垂直方向按时间顺序从上到下放置。判断不算语句数据库模型三要素:数据结构、数据操作、完整性约束自然连接运算就是去重候选键:是某个关系变量的一组属性所组成的集合,需要同时满足下列两个条件:这个属性集合始终能够确保在关系中唯一标识在属性集合中找不出合适的真子集能满足条件排他锁 X 和共享锁 S.对数据进行写操作时加写锁。T 对A加了写锁后,只有T能够读取和修改它,其他事务既不能读,也不能写。读锁:上了读锁后,只能读,不能写,其他事务可以继续上读锁来读取。插值公式:f(x)=y1+ (y2-y1)/(x2-x1)*(x-x1)hub:集线器,物理层。
Java设计模式_工厂设计模式
工厂设计模式interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car {
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂类
class CarFactory {
//方式一
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null;
}
}
//方式二
// public static Car getAudi() {
// return new Audi();
// }
//
// public static Car getByd() {
// return new BYD();
// }
}
public class Client02 {
public static void main(String[] args) {
Car a = CarFactory.getCar("奥迪");
a.run();
Car b = CarFactory.getCar("比亚迪");
b.run();
}
}
Java设计模式_单例模式
单例(Singleton)设计模式-饿汉式class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
单例(Singleton)设计模式-懒汉式(线程不安全)class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
单例设计模式之懒汉式(线程安全)package com.jerry.java;
/**
* @author jerry_jy
* @create 2022-10-01 19:27
*/
public class SingletonTest {
// 单例设计模式之懒汉式(线程安全)
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2);//true
}
}
class Singleton {
// 2.内部提供一个当前类Singleton的实例singleton
// 4.此实例也必须静态化static
private static Singleton singleton;
// 1.私有化构造器
private Singleton() {
}
// 3.提供公共的静态的方法getInstance,返回当前类的对象return singleton
public static Singleton getInstance() {
if (singleton == null) {
//synchronized同步监视器:每次只new 一个当前类的对象
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}