【JAVA基础篇】集合框架

一、集合框架图

å¨è¿éæå¥å¾çæè¿°

å¨è¿éæå¥å¾çæè¿°

Java集合框架主要包含两种类型的容器,一是集合(Collection),存储元素集合,二是图(Map),存储键(key)-值(value)对.Collection接口下面有两个重要的子接口List和Set,再下面是一些抽象类,最后是实现类,常用的实现类有HashSet、ArrayList、HashMap等等。

二、Set和List的区别

Set接口实例存储的是一组唯一,无序的对象。List实例存储的是一组不唯一,有序的对象。

Set遍历效率低,删除和插入效率高,删除和插入不会引起对象位置改变。

List遍历效率高,删除和插入效率低,因为删除和插入会引起对象位置改变。

三、集合实现类

1、ArrayList

继承AbstractList,实现了List接口。底层基于数组实现容量动态变化,允许元素为null,下面我们来看JDK1.8版本的ArrayList源码。

重要属性

    private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = {};private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};transient Object[] elementData;private int size;
  1. DEFAULT_CAPACITY:默认容量大小
  2. EMPTY_ELEMENTDATA :空数组
  3. DEFAULTCAPACITY_EMPTY_ELEMENTDATA :默认容量的空数组
  4. elementData:ArrayList的内部结构,是一个Object[]类型的数组
  5. 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 ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {this.elementData = EMPTY_ELEMENTDATA;}}
  1. ArrayList(int initialCapacity):实例化ArrayList对象时指定容量大小。
  2. ArrayList():无参构造方法,通过此构造器创建的ArrayList,第一次使用add方法添加元素时,容量为10。
  3. ArrayList(Collection<? extends E> c):接收一个Collection的实例,将该实例转化成ArrayList对象。

add(E e)方法

先看add(E e)方法相关的源码:

    public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!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++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}

我们可以看到add(E e)方法首先会调用ensureCapacityInternal方法来检查容量大小,如果发现elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即ArrayList通过无参构造器来实例化并且是第一次调用add方法,那么会将容量大小设置为默认容量大小10。

ensureExplicitCapacity方法判断如果所需的最小容量大于数组当前长度,那么需要扩容

grow方法来进行实际的扩容操作,新的数组长度是原来的3/2倍,并且新数组长度不能大于Integer.MAX_VALUE - 8,然后使用Arrays.copyOf方法将原来的数据复制到新数组中

使用ArrayList遇到的坑

如下代码中,在遍历List时,调用了remove方法,删除元素a

//arrayList中的值为 [a,a,c,a,a]
for (int i = 0; i < arrayList.size(); i++) {if (arrayList.get(i) == "a") {arrayList.remove(i);}
}
System.out.println(arrayList);

这段代码看似解决了删除列表中所有的a元素,但是删除后得出List的结果为[a, c, a],为什么这种方式没有达到想要的效果,其实仔细分析后会发现,在调用remove()方法时List的长度会发生变化而且元素的位置会发生移动,从而在遍历时list实际上是变化的,例如

  1. 当i=0时,此时list中的元素为[a,a,c,a,a],
  • 但当i=1时,此时List中的元素为[a,c,a,a],元素的位置发生了移动,从而导致在遍历的过程中不能达到删除的效果

解决方案

  1. 逆向遍历
  2. 使用迭代器遍历(推荐用此方法)
Iterator<String> ite = arrayList.listIterator();
while (ite.hasNext()){if(ite.next() == "a")ite.remove();
}
System.out.println(arrayList);

其他注意点

ArryList进行扩容时,需要将原有数组的元素拷贝到一个新数组中,非常耗时,所以建议在确定ArrayList元素数量的时候再使用它。

2、HashMap

继承AbstractMap,是基于哈希表的Map接口的非同步实现,存储的对象是Node(包含key和value),key和value都可以为null,最多存在一个键值对的key为null。不保证有序(比如插入顺序),并且不保证顺序不随时间发生变化。

JDK1.7以前由数组+链表组成,JDK1.8由数组+链表+红黑树组成,使用链表是为了解决哈希冲突(两个对象的hashCode方法计算的哈希码值一致导致计算出的数组下标相同),当链表长度大于阈值(默认8)并且当前数组长度大于64时,此时此下标位置上的所有元素使用红黑树存储,注意如果仅仅是某个链表长度大于阈值,选择进行扩容。

底层数据结构:

1,new HashMap<>()

jdk1.7之前,创建了一个长度16的Entry数组,jdk1.8是在首次调用put方法时,创建一个长度16的Node数组。

2.put方法

对key做hash操作(先调用key的hashCode方法得到哈希码值,然后哈希码值跟它无符号右移16位的值做异或运算),再调用putVal方法

先判断Node数组是否为null或者长度等于0,如果是那么resize方法进行扩容

hash操作得到的值与数组长度-1做与运算得到数组下标

得到下标后,判断数组当前位置是否有元素,如果没有,将键值对放到数组当前位置

如果已有元素,那么调用新key的equals方法跟原有key进行比较,如果相同替换原有value,如果不相同将新的键值对放到链表的末尾或者放到红黑树中

上面流程完成后,如果size>threshold,那么调用resize方法扩容

3.resize方法

由于每次扩容都是翻倍,与原来的n-1&hash相比,只是多了一个二进制位,所以节点要么是原来位置,要么是原来位置+原容量这个位置。因此只需要判断原来的hash值新增的bit位是1还是0

问题:

1,为什么HashMap的容量是2的幂次方?容量是2的幂次方有什么好处?如果创建HashMap对象时实参不是2的幂次方会怎么样?

jdk1.8putVal方法计算数组下标的时候使用(数组长度 - 1) & hash,数组长度是2的幂次方的时候这个表达式的结果与hash%数组长度相同,并且能够让数据均匀分布减少哈希碰撞

tableSizeFor方法会计算出一个大于且最接近的2的幂次方数,通过一系列的无符号右移运算和与运算得出(思想是将这个数的最高位下面的bit位全部变成1,第一次运算保证最高位至少有两个连续的1,第二次运算保证最高位至少有四个连续的1。。。,最后一次运算保证最高位有十六个连续的1)

2.loadFactor负载因子为什么是0.75

当size>capacity*loadFactor时哈希表会扩容,如果太大会导致查询效率低,过小会导致数组的利用率低,存放的数据会很分散。0.75是官方经过大量测试得出的最优的结果。

JDK1.8HashMap的源码

重要属性

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始化容量16,HashMap容器的容量必须是2的次方static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的负载因子,构造函数没有指定时使用static final int TREEIFY_THRESHOLD = 8;//树化的阈值,哈希桶元素的数量至少为8时才会转化成红黑树static final int UNTREEIFY_THRESHOLD = 6;//链表化的阈值,当进行resize操作时,哈希桶元素的数量不超过6时才会转化成链表static final int MIN_TREEIFY_CAPACITY = 64;//树化最小容量,容量至少为64时才会转化成红黑树transient Node<K,V>[] table;//HashMap容器实现存储功能的数组,必须是2的次方transient Set<Map.Entry<K,V>> entrySet;//保存缓存的entrySettransient int size;//HashMap容器包含的键值对数量transient int modCount;//HashMap结构被修改的次数(是指键值对数量改变或者rehash)int threshold;//进行resize操作的大小,=当前最大容量*负载因子final float loadFactor;//负载因子

重要的内部类

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;}}

TreeNode:继承LinkedHashMap的内部类Entry,而LinkedHashMap.Entry又继承Node,所以TreeNode算是Node的孙子类

TreeNode的方法太多,这里只贴出它的属性

        TreeNode<K,V> parent;  // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // needed to unlink next upon deletionboolean red;

重要方法

    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;}//计算hashstatic final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//先计算hashCode,然后将得到的值与它的高16位做异或运算,得到hash}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;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)//计算index,数组长度减一并与hash做与运算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) //如果链表的长度大于等于树化阈值8,将链表转换成红黑树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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}//此方法只是将链表转换成双向链表,真正树化操作在treeify方法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);}}//将双向链表转换成平衡二叉查找树final void treeify(Node<K,V>[] tab) {TreeNode<K,V> root = null;for (TreeNode<K,V> x = this, next; x != null; x = next) {next = (TreeNode<K,V>)x.next;x.left = x.right = null;if (root == null) {x.parent = null;x.red = false;root = x;}else {K k = x.key;int h = x.hash;Class<?> kc = null;for (TreeNode<K,V> p = root;;) {int dir, ph;K pk = p.key;if ((ph = p.hash) > h)dir = -1;else if (ph < h)dir = 1;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)dir = tieBreakOrder(k, pk);TreeNode<K,V> xp = p;if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0)xp.left = x;elsexp.right = x;root = balanceInsertion(root, x);break;}}}}moveRootToFront(tab, root);}final Node<K,V>[] resize() {//把没插入之前的哈希数组做我诶oldTalNode<K,V>[] oldTab = table;//old的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//old的临界值int oldThr = threshold;//初始化new的长度和临界值int newCap, newThr = 0;//oldCap > 0也就是说不是首次初始化,因为hashMap用的是懒加载if (oldCap > 0) {//大于最大值if (oldCap >= MAXIMUM_CAPACITY) {//临界值为整数的最大值threshold = Integer.MAX_VALUE;return oldTab;}//标记##,其它情况,扩容两倍,并且扩容后的长度要小于最大值,old长度也要大于16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//临界值也扩容为old的临界值2倍newThr = oldThr << 1; }/**如果oldCap<0,但是已经初始化了,像把元素删除完之后的情况,那么它的临界值肯定还存在,        如果是首次初始化,它的临界值则为0**/else if (oldThr > 0) newCap = oldThr;//首次初始化,给与默认的值else {               newCap = DEFAULT_INITIAL_CAPACITY;//临界值等于容量*加载因子newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//此处的if为上面标记##的补充,也就是初始化时容量小于默认值16的,此时newThr没有赋值if (newThr == 0) {//new的临界值float ft = (float)newCap * loadFactor;//判断是否new容量是否大于最大值,临界值是否大于最大值newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//把上面各种情况分析出的临界值,在此处真正进行改变,也就是容量和临界值都改变了。threshold = newThr;//表示忽略该警告@SuppressWarnings({"rawtypes","unchecked"})//初始化Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//赋予当前的tabletable = newTab;//此处自然是把old中的元素,遍历到new中if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {//临时变量Node<K,V> e;//当前哈希桶的位置值不为null,也就是数组下标处有值,因为有值表示可能会发生冲突if ((e = oldTab[j]) != null) {//把已经赋值之后的变量置位null,当然是为了好回收,释放内存oldTab[j] = null;//如果下标处的节点没有下一个元素if (e.next == null)//把该变量的值存入newCap中,e.hash & (newCap - 1)并不等于jnewTab[e.hash & (newCap - 1)] = e;//该节点为红黑树结构,也就是存在哈希冲突,该哈希桶中有多个元素else if (e instanceof TreeNode)//把此树进行转移到newCap中((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { /**此处表示为链表结构,同样把链表转移到newCap中,就是把链表遍历后,把值转过去,在置位null**/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;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.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;}}}}}//返回扩容后的hashMapreturn newTab;}

关于更详细的HashMap红黑树知识,请参考【Java入门提高篇】Day25 史上最详细的HashMap红黑树解析 - 弗兰克的猫 - 博客园

遍历HashMap

        Map<String,String> map = new HashMap<String,String>();map.put("郭如飞", "yz");map.put("余豪", "lazy");for (Entry<String, String> entry : map.entrySet()){System.out.println("键:"+entry.getKey()+",值:"+entry.getValue());}

3、HashSet

HashSet继承AbstractSet,实现Set接口,内部存储是HashMap。当HashSet中存储元素时,直接将这个元素作为key,默认的Object常量作为value,存储在map中。

4、LinkedList

LinkedList继承了AbstractSequentialList,实现了List和Deque接口。本质上是一个双向链表。

由于实现了Deque接口,可以将LinkedList当做一个队列,去处理其中的元素。

AbstractSequentialList类实现连续访问数据(链表)所需的工作,如果是随机访问(数组)建议用AbstractList,而不是AbstractSequentialList

重要属性

    transient int size = 0;//集合大小transient Node<E> first;//指向头结点transient Node<E> last;//指向尾结点

构造方法

    public LinkedList() {}public LinkedList(Collection<? extends E> c) {this();addAll(c);}

内部类

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(E e)

    public boolean add(E e) {linkLast(e);return true;}void linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);//创建一个Nodelast = newNode;//尾结点指向当前元素if (l == null)first = newNode;//如果last==null说明链表没有任何元素,此时添加的元素即为头结点elsel.next = newNode;//将链表的最后一个元素指向新的元素size++;modCount++;}

remove(Object o)、remove(int index)

    public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;}public E remove(int index) {checkElementIndex(index);return unlink(node(index));}E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;final Node<E> prev = x.prev;if (prev == null) {first = next;} else {prev.next = next;x.prev = null;}if (next == null) {last = prev;} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}Node<E> node(int index) {// assert isElementIndex(index);if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}

5、Vector

继承了AbstractList,实现了List接口,是一个线程安全的动态数组。

同ArrayList相比,除了线程安全(很多方法加了synchronized)外,其他地方基本一致。

6、Stack

继承了Vector,基于动态数组实现的一个线程安全的栈

7、Hashtable和HashMap的区别

1、 hashmap中key和value均可以为null,但是hashtable中key和value均不能为null。

2、 hashmap采用的是数组(桶位)+链表+红黑树结构实现(jdk1.8之后),而hashtable中采用的是数组(桶位)+链表实现。

3、 hashmap中出现hash冲突时,如果链表节点数小于8时是将新元素加入到链表的末尾,而hashtable中出现hash冲突时采用的是将新元素加入到链表的开头。

4、 hashmap中数组容量的大小要求是2的n次方,如果初始化时不符合要求会进行调整,必须为2的n次方,而hashtable中数组容量的大小可以为任意正整数。

5、 hashmap中的寻址方法采用的是位运算按位与,而hashtable中寻址方式采用的是求余数。

6、 hashmap不是线程安全的,而hashtable是线程安全的,hashtable中的get和put方法均采用了synchronized关键字进行了方法同步。

7、 hashmap中默认容量的大小是16,而hashtable中默认数组容量是11。

8、集合类中常用的方法

Java集合框架的实现源码,经常看到 Arrays.copy()、System.arraycopy() 方法,以下稍作整理。

Arrays.copyOf() 

作用:基于原数组复制一个新的数组并返回,不影响原有数组

我们这里看到copyOf方法有很多重载的方法,这里我只看public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType),因为其他方法同这个方法类似。

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {@SuppressWarnings("unchecked")T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]: (T[]) Array.newInstance(newType.getComponentType(), newLength);System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}

从源码可以看出来,copyOf方法是通过System.arraycopy方法来复制数组的。下面我们看一下

System.arraycopy

    public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

src:源数组

srcPos:源数组开始复制的索引

dest:目标数组

destPos:目标数组开始复制的索引

length:复制的长度

注意:System.arraycopy是浅复制,如果数组元素的类型是引用类型,复制的是引用类型对象的地址值,不是真的将原有对象复制一份。

Object[] toArray();<T> T[] toArray(T[] a);

Collection接口定义的两个抽象方法,集合实现类必须实现的两个方法,将集合转换成数组,区别是后者泛型方法。

9、Iterator迭代器实现原理

Java中Iterator(迭代器)实现原理 - wss96 - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/530416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JAVA基础篇】对象初始化过程

我们都知道&#xff0c;创建对象是由 new关键字调用构造方法 返回类实例&#xff08;实际上还可以通过反射来创建实例&#xff09;。 例如 : Person jack new Person(); 这句话到底做了什么事情呢 &#xff1f; 其实就是讲对象的初始化过程。 1、 new 用到了Person.class,所…

eclipse指定JDK版本启动,解决version XXX of the JVM is not suitable for this product.Version:XXX 问题

问题描述&#xff1a;启动eclipse时&#xff0c;提示version 1.7.0 of the JVM is not suitable for this product.Version:1.8 or greater is required. 原因分析&#xff1a;原因是我的笔记本安装了多个JDK版本&#xff0c;但是现在我的JAVA_HOME配置的是jdk1.7的路径&#x…

【JAVA基础篇】IO流

一、流的概念 “对语言设计人员来说&#xff0c;创建好的输入&#xff0f;输出系统是一项特别困难的任务。” ――《Think in Java》 无论是系统、还是语言的设计中IO的设计都是异常复杂的。面临的最大的挑战一般是如何覆盖所有可能的因素&#xff0c;我们不仅仅要考虑文件、…

【JAVA基础篇】运算符

一、表达式 表达式由运算符和操作数组成 例如&#xff1a; 5 num1 num1num2 sumnum1num2 二、运算符分类 算数运算符、赋值运算符、关系运算符、逻辑运算符、条件运算符、位运算符 三、算数运算符 四、赋值运算符 格式&#xff1a;变量表达式 例如&#xff1a;int n3…

a4纸网页打印 table_打印模板不愁人,你还在打印单调的A4纸吗?

软件介绍早在几年前&#xff0c;社会上就已经开始了数字化、无纸化的推广&#xff0c;但是就算再怎么无纸化&#xff0c;纸张还是有它必要的存在&#xff0c;在工作、学习过程中&#xff0c;打印的需求也必不可少的。但是一般的打印都是比较平庸的&#xff0c;要做会议记录&…

上证指数30年k线图_技术预判2020:上证指数要突破3500点才会“井喷”

2019年的行情很快就要收官了&#xff0c;截止目前&#xff0c;上证指数今年的涨幅是20.5%&#xff0c;不过可能有部分投资者今年的收益率还没达到大盘指数的平均水平。不管怎样&#xff0c;今年很快就要翻篇了&#xff0c;关键是看2020年股市能不能迎来更好的行情了。而总结得失…

的优缺点_浅谈桉木家具的优缺点

家具现在的材质是有很多的&#xff0c;木质的&#xff0c;石材的&#xff0c;还有真空充气的&#xff0c;都是很不错的类型。桉木家具是现在很多人都喜欢的一种材质&#xff0c;但是很多人对桉木家具的优缺点不是很清楚&#xff0c;为了能够让大家更加清楚的了解桉木家具&#…

客户说发货慢怎么回复_女生微信说身体不舒服怎么回复关心她?

当你不在女生身边&#xff0c;女生微信给你说身体不舒服&#xff0c;肯定需要说点话来安慰她了。多喝热水肯定是不行了&#xff0c;一点用处都没有&#xff0c;还会让女生觉得你根本不重视她&#xff0c;是在敷衍她&#xff0c;那女生微信说身体不舒服怎么回复关心她呢&#xf…

【算法篇】八种内排序算法

常用的八种内排序算法分别是&#xff1a; 交换排序&#xff1a;冒泡排序、快速排序选择排序&#xff1a;简单选择排序、堆排序插入排序&#xff1a;直接插入排序、希尔排序归并排序基数排序 内排序巧记&#xff1a;选(选择)舰(简单选择)队(堆)的时候脚(交换)毛(冒泡)快(快速)&…

数据分析专题报告范文6篇_小学生看图写话范文:小熊玩跷跷板?(6篇),让孩子参考练习...

​范文01&#xff1a;小熊跷跷板一天&#xff0c;天气晴朗&#xff0c;胖乎乎的小熊和小白兔一起玩跷跷板。小熊一屁股坐在地上&#xff0c;小白兔说&#xff1a;“啊&#xff01;我有恐高症哇&#xff01;”小熊说&#xff1a;“我比你重&#xff0c;所以你没有办法把我翘起来…

win10环境安装使用svn客户端和服务端

一、下载安装包 安装包下载传送门http://subversion.apache.org/packages.html 无法下载的童鞋去百度云下载 链接&#xff1a;https://pan.baidu.com/s/1EuGohoZKIPmRvynp5-Subw 提取码&#xff1a;ohna 链接&#xff1a;https://pan.baidu.com/s/1EJrd5DzGCBE4rRfdhuno6Q …

所选元素非联通_非固化橡胶沥青防水涂料与耐根穿刺防水卷材(沥青基)施工要点...

目前&#xff0c;非固化复合耐根穿刺防水卷材在车库顶板的应用逐渐受到客户及用户的认可&#xff0c;也有不少慕名而来的防水从业者打电话来咨询此系统的应用情况及优势。下面就由小编来给大家系统介绍此应用系统的特点吧。01性能优势1.两种材料高度的相容性非固化橡胶沥青防水…

【Java中级篇】动态代理机制

要想搞明白动态代理之前&#xff0c;我们先来了解一下代理是什么意思&#xff0c;先来谈谈设计模式中的代理模式。 什么是代理模式&#xff08;Proxy&#xff09; 定义&#xff1a;给目标对象提供一个代理对象&#xff0c;并由代理对象控制对目标对象的引用。 在代理模式中&…

什么叫大数据人物画像_大数据时代,如何构建精准用户画像,直击精细化运营...

移动互联网时代&#xff0c;精细化运营逐渐成为企业发展的重要竞争力&#xff0c;“用户画像”的概念也应运而生。用户画像是指&#xff0c;在大数据时代&#xff0c;企业通过对海量数据信息进行清洗、聚类、分析&#xff0c;将数据抽象成标签&#xff0c;再利用这些标签将用户…

【Java中级篇】使用itextpdf生成PDF

我们可以发现很多求职网站都会将我们录入的信息来生成一个PDF简历文件。所以我这里提供了用itextpdf生成的PDF的代码。 一、步骤 1.1、使用Adobe Acrobat Pro工具编辑PDF模板 1.2、根据PDF模板文件路径创建一个PDFReader对象 1.3、创建一个输出流对象&#xff0c;用于存放生…

adb bugreport保存位置_adb 常用命令---日常提升效率

做为 Android 开发&#xff0c;怎么能不懂点 adb 命令呢&#xff1f;速看~adb 重置、断连的状况这里不说了&#xff0c;先来说一些直观的命令吧1、adb devices查看当前连接的设备如果当前正在连接着设备&#xff0c;那么就可以进行后续的操作了&#xff0c;如果没有&#xff0c…

jsp springmvc 视图解析器_Java面试题整理——SpringMVC

SpringMVC1、什么是SpringMVCSpring MVC是一个MVC的开源框架&#xff0c;Spring MVC Struts2spring&#xff0c;Spring MVC就相当于是Struts2加上Spring的整合&#xff0c;但是这里有一疑惑是Spring MVC和Spring是什么样的关系呢。Spring MVC是Spring的一个后续产品&#xff0…

采集标签_分流器(二):灵活分流能力,数据采集无忧

数据采集分析难怎么办&#xff1f;昨天和大家分享了数据采集分析的最佳助手——分流器的一些基础知识&#xff0c;回顾链接&#xff0c;今天继续和您分享第二篇&#xff0c;带您更深入地了解分流器的强大功能。业务难点在网络安全和数据可视化趋势推动下&#xff0c;企业安全分…

用友邮件撤回怎么操作_用户体验原则——“操作可控”

对于用户的误操作&#xff0c;提供二次确认或者撤销的功能&#xff0c;这样可提高用户的操作可控性。好设计应该是值得信任&#xff0c;也容易被相信的。在要求用户执行某一动作时&#xff0c;尽量帮他们理解为什么这个操作是必要的。每一步都需要借助诚实和清晰的表述来建立信…

蛋白结构建模与优化_最终幻想: 无中生有的蛋白质从头设计

作者简介: 刘源 北京大学化学与分子工程学院/力文所零.导读近几年&#xff0c;蛋白质结构预测领域连续取得重大突破。首先是【AlphaFold】&#xff0c;在可以充分利用共进化信息结合深度神经网络生成空间约束条件并降低相空间的搜索&#xff0c;极大地帮助了蛋白质的结构建模&a…