Java源码阅读ArrayList
1简介
- public class ArrayList<E> extends AbstractList<E>
- implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList使用一个可变数组实现List接口,实现了List接口的所有可选操作。ArrayList除了是非线程安全的之外,其他的与Vector类似。
2成员属性
- //序列化版本号
- private static final long serialVersionUID = 8683452581122892189L;
- //默认容量
- private static final int DEFAULT_CAPACITY = 10;
- //默认数组,没有元素,空数组
- private static final Object[] EMPTY_ELEMENTDATA = {};
- //默认容量数组
- private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- //存放元素的数组
- transient Object[] elementData;
- //当前ArrayList里面元素存放数量
- private int size;
3构造函数
- 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) {
- // c.toArray might (incorrectly) not return Object[] (see 6260652)
- if (elementData.getClass() != Object[].class)
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- } else {
- // replace with empty array.
- this.elementData = EMPTY_ELEMENTDATA;
- }
- }
上面几个构造函数没什么好讲的,但有一个疑问,正常来说DEFAULTCAPACITY_EMPTY_ELEMENTDATA容量应该为10,可是我们在属性的里声明的时候却是为空的,为什么会这样?不急,接下来的扩容代码给出了很好的解释。与Vector一样,ArrayList可以调用trimToSize将多余的空间释放,也可以扩充容量。
4可收缩容量
(1)trimToSize方法
- public void trimToSize() {
- modCount++;
- if (size < elementData.length) {
- elementData = (size == 0)
- ? EMPTY_ELEMENTDATA
- : Arrays.copyOf(elementData, size);
- }
- }
size属性是集合里现有元素的个数,elementData.length是数组的实际长度。我们可以trimToSize将数组标号为[size,elementData.length-1]的空间释放。
(2)扩容
- public void ensureCapacity(int minCapacity) {
- int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
- // any size if not default element table
- ? 0
- // larger than default for default empty table. It's already
- // supposed to be at default size.
- : DEFAULT_CAPACITY;
- if (minCapacity > minExpand) {
- ensureExplicitCapacity(minCapacity);
- }
- }
- private static int calculateCapacity(Object[] elementData, int minCapacity) {
- if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
- return Math.max(DEFAULT_CAPACITY, minCapacity);
- }
- return minCapacity;
- }
- private void ensureCapacityInternal(int minCapacity) {
- ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
- }
- private void ensureExplicitCapacity(int minCapacity) {
- modCount++;
- // overflow-conscious code
- if (minCapacity - elementData.length > 0)
- grow(minCapacity);
- }
- private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- private void grow(int minCapacity) {
- // overflow-conscious code
- int 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);
- }
- private static int hugeCapacity(int minCapacity) {
- if (minCapacity < 0) // overflow
- throw new OutOfMemoryError();
- return (minCapacity > MAX_ARRAY_SIZE) ?
- Integer.MAX_VALUE :
- MAX_ARRAY_SIZE;
- }
我们先解释一下DEFAULTCAPACITY_EMPTY_ELEMENTDATA容量问题:
当调用add方法添加元素时,首先会执行ensureCapacityInternal(size + 1),进入该方法后只有一条ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)),当elementData是DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,calculateCapacity方法会返回DEFAULT_CAPACITY和size + 1的较大值。size默认从0开始的,所以第一次调用add方法时且当elementData是DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,就会对进行扩容操作,而扩容的容量正好是DEFAULT_CAPACITY(10)。
然后我们分析手动调用ensureCapacity(minCapacity)进行扩容的过程:
进入该方法后,第一步是求minExpand,这里会判读实际数组是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,若是的话minExpand赋值为DEFAULT_CAPACITY(10),否则为0。然后确保参数minCapacity大于minExpand,才进行后续的扩容操作,调用ensureExplicitCapacity(minCapacity)。
ensureExplicitCapacity方法确保minCapacity大于当前容量elementData.length才进行扩容操作,调用 grow(minCapacity)。
因为扩容原理是复制数组,这是个是个很费计算机资源的操作,应该尽量减少扩容次数,所以每次扩容都会保证最小扩容(旧容量*1.5),grow方法的主要功能就是这个。
5查找
- public boolean contains(Object o) {
- return indexOf(o) >= 0;
- }
- //查找第一个索引
- public int indexOf(Object o) {
- if (o == null) {
- for (int i = 0; i < size; i++)
- if (elementData[i]==null)
- return i;
- } else {
- for (int i = 0; i < size; i++)
- if (o.equals(elementData[i]))
- return i;
- }
- return -1;
- }
- //查找最后一个索引
- public int lastIndexOf(Object o) {
- if (o == null) {
- for (int i = size-1; i >= 0; i--)
- if (elementData[i]==null)
- return i;
- } else {
- for (int i = size-1; i >= 0; i--)
- if (o.equals(elementData[i]))
- return i;
- }
- return -1;
- }
- //获取索引位置元素
- public E get(int index) {
- rangeCheck(index);
- return elementData(index);
- }
- //set放着一起看吧
- public E set(int index, E element) {
- rangeCheck(index);
- E oldValue = elementData(index);
- elementData[index] = element;
- return oldValue;
- }
- private void rangeCheck(int index) {
- if (index >= size)
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
查找的时候注意判断null,因为null.equals()是会报空指针异常的。indexOf是从前往后遍历数组,lastIndexOf则是从后往前遍历。
6集合基本操作
(1)添加元素
- //在末尾添加一个元素
- public boolean add(E e) {
- ensureCapacityInternal(size + 1); // 检查是否需要扩容
- elementData[size++] = e;
- return true;
- }
- //在指定位置添加一个元素
- public void add(int index, E element) {
- rangeCheckForAdd(index);
- ensureCapacityInternal(size + 1); // Increments modCount!!
- System.arraycopy(elementData, index, elementData, index + 1,size - index); // index后的所有元素均后移一位
- elementData[index] = element;
- size++;
- }
- //添加一堆元素
- public boolean addAll(Collection<? extends E> c) {
- Object[] a = c.toArray();
- int numNew = a.length;
- ensureCapacityInternal(size + numNew); // Increments modCount
- System.arraycopy(a, 0, elementData, size, numNew);
- size += numNew;
- return numNew != 0;
- }
- //从指定位置开始添加一堆元素
- public boolean addAll(int index, Collection<? extends E> c) {
- rangeCheckForAdd(index);
- Object[] a = c.toArray();
- int numNew = a.length;
- ensureCapacityInternal(size + numNew); // Increments modCount
- int numMoved = size - index;
- if (numMoved > 0)
- System.arraycopy(elementData, index, elementData, index + numNew,
- numMoved);
- System.arraycopy(a, 0, elementData, index, numNew);
- size += numNew;
- return numNew != 0;
- }
- private void rangeCheckForAdd(int index) {
- if (index > size || index < 0)
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
(2)删除元素
- //删除指定位置的元素
- 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; // clear to let GC do its work
- return oldValue;
- }
- //删除匹配的元素
- public boolean remove(Object o) {
- if (o == null) {
- for (int index = 0; index < size; index++)
- if (elementData[index] == null) {
- fastRemove(index);
- return true;
- }
- } else {
- for (int index = 0; index < size; index++)
- if (o.equals(elementData[index])) {
- fastRemove(index);
- return true;
- }
- }
- return false;
- }
- private void fastRemove(int index) {
- modCount++;
- int numMoved = size - index - 1;
- if (numMoved > 0)
- System.arraycopy(elementData, index+1, elementData, index,
- numMoved);
- elementData[--size] = null; // clear to let GC do its work
- }
- //删除索引为[fromIndex,toIndex]范围内的所以元素
- protected void removeRange(int fromIndex, int toIndex) {
- modCount++;
- int numMoved = size - toIndex;
- System.arraycopy(elementData, toIndex, elementData, fromIndex,
- numMoved);
- // clear to let GC do its work
- int newSize = size - (toIndex-fromIndex);
- for (int i = newSize; i < size; i++) {
- elementData[i] = null;
- }
- size = newSize;
- }
- //删除一堆元素
- public boolean removeAll(Collection<?> c) {
- Objects.requireNonNull(c); //检查集合c不能为空,否则报空指针异常
- return batchRemove(c, false);
- }
- private boolean batchRemove(Collection<?> c, boolean complement) {
- final Object[] elementData = this.elementData;
- int r = 0, w = 0;
- boolean modified = false;
- try {
- for (; r < size; r++)
- if (c.contains(elementData[r]) == complement)
- elementData[w++] = elementData[r];
- } finally {
- // Preserve behavioral compatibility with AbstractCollection,
- // even if c.contains() throws.
- if (r != size) {
- System.arraycopy(elementData, r,
- elementData, w,
- size - r);
- w += size - r;
- }
- if (w != size) {
- // clear to let GC do its work
- for (int i = w; i < size; i++)
- elementData[i] = null;
- modCount += size - w;
- size = w;
- modified = true;
- }
- }
- return modified;
- }
- //清空(删除所有元素)
- public void clear() {
- modCount++;
- // clear to let GC do its work
- for (int i = 0; i < size; i++)
- elementData[i] = null;
- size = 0;
- }
remove和fastRemove的区别是后者没有范围检查,只有确定不会越界的情况下才会调用fastRemove来提升效率。
值得注意的是与Vector不同,这里删除一堆元素不是通过迭代器实现的,而是通过batchRemove方法,该批量删除方法会遍历数据数组。当complement参数为false时,把不属于参数集合里的元素替换到原数组里面。为true时,把原集合中属于参数集合里的元素替换到原数组里面。
7序列化方法
由于ArrayList实现了Serializable接口,我们知道它是可序列化的,而且该类自己实现了序列化writeObject和反序列化readObject方法,当ObjectOutputStream调用writeObject进行序列化时,会调用该类自己的writeObject。同理,ObjectInputStream也会调用该类自己的readObject方法实现反序列化。
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException{
- // Write out element count, and any hidden stuff
- int expectedModCount = modCount;
- s.defaultWriteObject();
- // Write out size as capacity for behavioural compatibility with clone()
- s.writeInt(size);
- // Write out all elements in the proper order.
- for (int i=0; i<size; i++) {
- s.writeObject(elementData[i]);
- }
- if (modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- }
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- elementData = EMPTY_ELEMENTDATA;
- // Read in size, and any hidden stuff
- s.defaultReadObject();
- // Read in capacity
- s.readInt(); // ignored
- if (size > 0) {
- // be like clone(), allocate array based upon size not capacity
- int capacity = calculateCapacity(elementData, size);
- SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
- ensureCapacityInternal(size);
- Object[] a = elementData;
- // Read in all elements in the proper order.
- for (int i=0; i<size; i++) {
- a[i] = s.readObject();
- }
- }
- }
writeObject方法第一步是获得当前modCount,这样做的目的是确保在方法执行过程中,ArrayList对象没有其他线程修改。然后是调用ObjectOutputStream 的defaultWriteObject
方法将ArrayList对象的非static 和非transient的属性写出到当前流。下一步是将集合中当前数据量size写出,最后遍历数组,将每个位置的对象写出。
readObject方法的工作稍复杂,在进行反序列化之前需要2个工作,第一个是数据检查,确保读过来的是Object数组。第二个是开辟合理的空间存储取回来的数据。
8迭代器实现
ArrayList的祖先Collection继承了Iterable接口,ArrayList是通过内部类实现迭代器的。
- public Iterator<E> iterator() {
- return new Itr();
- }
- /**
- * An optimized version of AbstractList.Itr
- */
- private class Itr implements Iterator<E> {
- int cursor; // index of next element to return
- int lastRet = -1; // index of last element returned; -1 if no such
- int expectedModCount = modCount;
- Itr() {}
- public boolean hasNext() {
- return cursor != size;
- }
- @SuppressWarnings("unchecked")
- public E next() {
- checkForComodification();
- int i = cursor;
- if (i >= size)
- throw new NoSuchElementException();
- Object[] elementData = ArrayList.this.elementData;
- if (i >= elementData.length)
- throw new ConcurrentModificationException();
- cursor = i + 1;
- return (E) elementData[lastRet = i];
- }
- public void remove() {
- if (lastRet < 0)
- throw new IllegalStateException();
- checkForComodification();
- try {
- ArrayList.this.remove(lastRet);
- cursor = lastRet;
- lastRet = -1;
- expectedModCount = modCount;
- } catch (IndexOutOfBoundsException ex) {
- throw new ConcurrentModificationException();
- }
- }
- @Override
- @SuppressWarnings("unchecked")
- public void forEachRemaining(Consumer<? super E> consumer) {
- Objects.requireNonNull(consumer);
- final int size = ArrayList.this.size;
- int i = cursor;
- if (i >= size) {
- return;
- }
- final Object[] elementData = ArrayList.this.elementData;
- if (i >= elementData.length) {
- throw new ConcurrentModificationException();
- }
- while (i != size && modCount == expectedModCount) {
- consumer.accept((E) elementData[i++]);
- }
- // update once at end of iteration to reduce heap write traffic
- cursor = i;
- lastRet = i - 1;
- checkForComodification();
- }
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
- }
关于ArrayList迭代的实现请参考我的另一篇文章:Java迭代器
对于ArrayList不仅有其Iterator实现,还有ListIterator的实现。ListIterator不仅可以向后遍历,而且可以向前遍历。
- private class ListItr extends Itr implements ListIterator<E> {
- ListItr(int index) {
- super();
- cursor = index;
- }
- public boolean hasPrevious() {
- return cursor != 0;
- }
- public int nextIndex() {
- return cursor;
- }
- public int previousIndex() {
- return cursor - 1;
- }
- @SuppressWarnings("unchecked")
- public E previous() {
- checkForComodification();
- int i = cursor - 1;
- if (i < 0)
- throw new NoSuchElementException();
- Object[] elementData = ArrayList.this.elementData;
- if (i >= elementData.length)
- throw new ConcurrentModificationException();
- cursor = i;
- return (E) elementData[lastRet = i];
- }
- public void set(E e) {
- if (lastRet < 0)
- throw new IllegalStateException();
- checkForComodification();
- try {
- ArrayList.this.set(lastRet, e);
- } catch (IndexOutOfBoundsException ex) {
- throw new ConcurrentModificationException();
- }
- }
- public void add(E e) {
- checkForComodification();
- try {
- int i = cursor;
- ArrayList.this.add(i, e);
- cursor = i + 1;
- lastRet = -1;
- expectedModCount = modCount;
- } catch (IndexOutOfBoundsException ex) {
- throw new ConcurrentModificationException();
- }
- }
- }
ListIterator向后迭代的过程继承自Iterator,这里没什么好说的。这里主要分析previous()、set(E e)、add(E e)三个方法。
(1)previous()方法
该方法定义前向迭代的过程,首先将当前游标cursor向前移动一位,再把游标移动后指向的位置赋值给lastRet,最后返回lastRet指向的值。
(2)set(E e)方法
该方法将lastRet指向位置的值改为指定值(参数)。
(3)add(E e)方法
该方法在cursor位置插入指定值,然后将游标位置后移1位,并将lastRet置为-1。
9其他
(1)排序
ArryList内部提供了排序方法sort,sort方法就是接受了一个Comparator比较器对象,然后将List集合转换成数组,然后再调用数组工具类Arrays的sort()方法进行排序,最后把排好序的数组遍历并赋值到原来的List集合上。
- public void sort(Comparator<? super E> c) {
- final int expectedModCount = modCount;
- Arrays.sort((E[]) elementData, 0, size, c);
- if (modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- modCount++;
- }
(2)removeIf方法
该方法的调用依赖于函数编程,需要传入一个函数作为参数,符合该函数判定为true的元素进行删除操作。
- public boolean removeIf(Predicate<? super E> filter) {
- Objects.requireNonNull(filter);
- // figure out which elements are to be removed
- // any exception thrown from the filter predicate at this stage
- // will leave the collection unmodified
- int removeCount = 0;
- final BitSet removeSet = new BitSet(size);
- final int expectedModCount = modCount;
- final int size = this.size;
- for (int i=0; modCount == expectedModCount && i < size; i++) {
- @SuppressWarnings("unchecked")
- final E element = (E) elementData[i];
- if (filter.test(element)) {
- removeSet.set(i);
- removeCount++;
- }
- }
- if (modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- // shift surviving elements left over the spaces left by removed elements
- final boolean anyToRemove = removeCount > 0;
- if (anyToRemove) {
- final int newSize = size - removeCount;
- for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
- i = removeSet.nextClearBit(i);
- elementData[j] = elementData[i];
- }
- for (int k=newSize; k < size; k++) {
- elementData[k] = null; // Let gc do its work
- }
- this.size = newSize;
- if (modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- modCount++;
- }
- return anyToRemove;
- }
line13的代码filter.test(element)判断符合条件的元素,这里通过位运算记录需要操作的位置,然后进行数组复制。(关于如何通过位运算记录删除位请详细阅读BitSet类源码)
(3)forEach方法
该方法和前面迭代器一节中的forEachRemaining类似,也是Java8函数编程新特性。forEach的作用是为集合中的每个元素执行一些操作(操作函数作为参数传入)
- public void forEach(Consumer<? super E> action) {
- Objects.requireNonNull(action);
- final int expectedModCount = modCount;
- @SuppressWarnings("unchecked")
- final E[] elementData = (E[]) this.elementData;
- final int size = this.size;
- for (int i=0; modCount == expectedModCount && i < size; i++) {
- action.accept(elementData[i]);
- }
- if (modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- }
(4)subList
- List<E> subList(int fromIndex, int toIndex);
- 该方法返回的是父list的一个视图,从fromIndex(包含),到toIndex(不包含)。fromIndex=toIndex 表示子list为空
- 父子list做的非结构性修改(non-structural changes)都会影响到彼此:所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。
- 对于结构性修改,子list的所有操作都会反映到父list上。但父list的修改将会导致返回的子list失效。
- 如何删除list中的某段数据:list.subList(from, to).clear();
代码这里我就不贴了。
Java源码阅读ArrayList的更多相关文章
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- JDK 1.8源码阅读 ArrayList
一,前言 ArrayList是Java开发中使用比较频繁的一个类,通过对源码的解读,可以了解ArrayList的内部结构以及实现方法,清楚它的优缺点,以便我们在编程时灵活运用. 二,ArrayList ...
- 【JDK1.8】JDK1.8集合源码阅读——ArrayList
一.前言 在前面几篇,我们已经学习了常见了Map,下面开始阅读实现Collection接口的常见的实现类.在有了之前源码的铺垫之后,我们后面的阅读之路将会变得简单很多,因为很多Collection的结 ...
- java源码阅读Hashtable
1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...
- Java源码阅读Stack
Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...
- Java源码阅读之ArrayList
基于jdk1.8的ArrayList源码分析. 实现List接口最常见的大概就四种,ArrayList, LinkedList, Vector, Stack实现,今天就着重看一下ArrayList的源 ...
随机推荐
- 常用shell脚本命令
常用shell脚本命令 1.显示包含文字aaa的下一行的内容: sed -n '/aaa/{n;p;}' filename 2.删除当前行与下一行的内容: sed -i '/aaa/{N;d;}' f ...
- 有趣的数(number)
有趣的数(number) 题目描述 这些天 nodgd 在研究一类有趣的数.定义函数 f(n) f(n) f(n) 表示 n n n 在十进制表示下的数字之和.如果一个正整数 n n n 满足 f(n ...
- Heine-Borel定理
前:开始学数学..来写写理解和补充吧.. 书:M.A.Armstrong著<Basic Topology> Heine-Borel定理:实轴上闭区间是紧集. 证法(1)延伸法: 思想 闭区 ...
- iOS-android-windowsphone等移动终端平台开发流程图
到了公司后,半个月时间就是在熟悉下面这张图里的流程, 项目流程图: 下面是我对这张图的一些理解:
- C++自带栈与队列_stack_queue_C++
栈和队列我们可以用C++里自带的函数使用,就不必手写了 1.栈,需要开头文件 #include<stack> 定义一个栈s:stack<int> s; 具体操作: s.emp ...
- session和xsrf
1.pip install pycket 2.pip install redis 防止xsrf攻击只需在模板form标签加入: {% module xsrf_form_html() %} <!D ...
- [Oracle] 关系型数据库排序算法和数据结构以及关联查询
关系型数据库排序算法和数据结构以及关联查询 1. Merge sort 理解merge sort算法将有助于更好地理解数据库join操作 - merge join 算法逻辑 将2个有序的大小为N/2的 ...
- python update数据
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Time : 2017/11/23 23:57 # @Author : lijunjiang # @Fi ...
- Nginx 兼容IE8
前言 前段时间由于业务需要,在服务器上新增一个服务专门接收各个门店的业务结算数据,接口文档指明需要使用https协议.这本不是什么问题,因为之前服务器已经有配置过https.但等到服务部署之后才发现这 ...
- 【linux高级程序设计】(第十三章)Linux Socket网络编程基础 3
使用之前的函数实现的简单聊天程序 TCP协议 双方实时发送/接收消息 实现后的问题: 可能是我虚拟机的IP地址配得有问题吧.在一台电脑上面开两个终端,用127.0.0.1的IP收发可以互通.但是两个虚 ...