本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接http://item.jd.com/12299018.html


数组是存储多个同类型元素的基本数据结构,数组中的元素在内存连续存放,可以通过数组下标直接定位任意元素,相比我们在后续章节介绍的其他容器,效率非常高。

数组操作是计算机程序中的常见基本操作,Java中有一个类Arrays,包含一些对数组操作的静态方法,本节主要就来讨论这些方法,我们先来看怎么用,然后再来看它们的实现原理。学习Arrays的用法,我们就可以避免重新发明轮子,直接使用,学习它的实现原理,我们就可以在需要的时候,自己实现它不具备的功能。

用法

toString

Arrays的toString方法可以方便的输出一个数组的字符串形式,方便查看,它有九个重载的方法,包括八种基本类型数组和一个对象类型数组,这里列举两个:

  1. public static String toString(int[] a)
  2. public static String toString(Object[] a)

例如:

  1. int[] arr = {9,8,3,4};
  2. System.out.println(Arrays.toString(arr));
  3.  
  4. String[] strArr = {"hello", "world"};
  5. System.out.println(Arrays.toString(strArr));

输出为

  1. [9, 8, 3, 4]
  2. [hello, world]

如果不使用Arrays.toString,直接输出数组自身,即代码改为:

  1. int[] arr = {9,8,3,4};
  2. System.out.println(arr);
  3.  
  4. String[] strArr = {"hello", "world"};
  5. System.out.println(strArr);

则输出会变为如下所示:

  1. [I@1224b90
  2. [Ljava.lang.String;@728edb84

这个输出就难以阅读了,@后面的数字表示的是内存的地址。

数组排序 - 基本类型

排序是一个非常常见的操作,同toString一样,对每种基本类型的数组,Arrays都有sort方法(boolean除外),如:

  1. public static void sort(int[] a)
  2. public static void sort(double[] a)

排序按照从小到大升序排,看个例子:

  1. int[] arr = {4, 9, 3, 6, 10};
  2. Arrays.sort(arr);
  3. System.out.println(Arrays.toString(arr));

输出为:

  1. [3, 4, 6, 9, 10]

数组已经排好序了。

sort还可以接受两个参数,对指定范围内的元素进行排序,如:

  1. public static void sort(int[] a, int fromIndex, int toIndex)

包括fromIndex位置的元素,不包括toIndex位置的元素,如:

  1. int[] arr = {4, 9, 3, 6, 10};
  2. Arrays.sort(arr,0,3);
  3. System.out.println(Arrays.toString(arr));

输出为:

  1. [3, 4, 9, 6, 10]

只对前三个元素排序。

数组排序 - 对象类型

除了基本类型,sort还可以直接接受对象类型,但对象需要实现Comparable接口。

  1. public static void sort(Object[] a)
  2. public static void sort(Object[] a, int fromIndex, int toIndex)

我们看个String数组的例子:

  1. String[] arr = {"hello","world", "Break","abc"};
  2. Arrays.sort(arr);
  3. System.out.println(Arrays.toString(arr));

输出为:

  1. [Break, abc, hello, world]

"Break"之所以排在最前面,是因为大写字母比小写字母都小。那如果排序的时候希望忽略大小写呢?

数组排序 - 自定义比较器

sort还有另外两个重载方法,可以接受一个比较器作为参数:

  1. public static <T> void sort(T[] a, Comparator<? super T> c)
  2. public static <T> void sort(T[] a, int fromIndex, int toIndex,
  3. Comparator<? super T> c)

方法声明中的T表示泛型,泛型我们在后续章节再介绍,这里表示的是,这个方法可以支持所有对象类型,只要传递这个类型对应的比较器就可以了。Comparator就是比较器,它是一个接口,定义是:

  1. public interface Comparator<T> {
  2. int compare(T o1, T o2);
  3. boolean equals(Object obj);
  4. }

最主要的是compare这个方法,它比较两个对象,返回一个表示比较结果的值,-1表示o1小于o2,0表示相等,1表示o1大于o2。

排序是通过比较来实现的,sort方法在排序的过程中,需要对对象进行比较的时候,就调用比较器的compare方法。

String类有一个public静态成员,表示忽略大小写的比较器:

  1. public static final Comparator<String> CASE_INSENSITIVE_ORDER
  2. = new CaseInsensitiveComparator();

我们通过这个比较器再来对上面的String数组排序:

  1. String[] arr = {"hello","world", "Break","abc"};
  2. Arrays.sort(arr, String.CASE_INSENSITIVE_ORDER);
  3. System.out.println(Arrays.toString(arr));

这样,大小写就忽略了,输出变为了:

  1. [abc, Break, hello, world]

为进一步理解Comparator,我们来看下String的这个比较器的主要实现代码:

  1. private static class CaseInsensitiveComparator
  2. implements Comparator<String> {
  3. public int compare(String s1, String s2) {
  4. int n1 = s1.length();
  5. int n2 = s2.length();
  6. int min = Math.min(n1, n2);
  7. for (int i = 0; i < min; i++) {
  8. char c1 = s1.charAt(i);
  9. char c2 = s2.charAt(i);
  10. if (c1 != c2) {
  11. c1 = Character.toUpperCase(c1);
  12. c2 = Character.toUpperCase(c2);
  13. if (c1 != c2) {
  14. c1 = Character.toLowerCase(c1);
  15. c2 = Character.toLowerCase(c2);
  16. if (c1 != c2) {
  17. // No overflow because of numeric promotion
  18. return c1 - c2;
  19. }
  20. }
  21. }
  22. }
  23. return n1 - n2;
  24. }
  25. }

代码比较直接,就不解释了。

sort默认都是从小到大排序,如果希望按照从大到小排呢?对于对象类型,可以指定一个不同的Comparator,可以用匿名内部类来实现Comparator,比如可以这样:

  1. String[] arr = {"hello","world", "Break","abc"};
  2. Arrays.sort(arr, new Comparator<String>() {
  3. @Override
  4. public int compare(String o1, String o2) {
  5. return o2.compareToIgnoreCase(o1);
  6. }
  7. });
  8. System.out.println(Arrays.toString(arr));

程序输出为:

  1. [world, hello, Break, abc]

以上代码使用一个匿名内部类实现Comparator接口,返回o2与o1进行忽略大小写比较的结果,这样就能实现,忽略大小写,且按从大到小排序。为什么o2与o1比就逆序了呢?因为默认情况下,是o1与o2比。

Collections类中有两个静态方法,可以返回逆序的Comparator,如

  1. public static <T> Comparator<T> reverseOrder()
  2. public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)

关于Collections类,我们在后续章节再详细介绍。

这样,上面字符串忽略大小写逆序排序的代码可以改为:

  1. String[] arr = {"hello","world", "Break","abc"};
  2. Arrays.sort(arr, Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER));
  3. System.out.println(Arrays.toString(arr));

传递比较器Comparator给sort方法,体现了程序设计中一种重要的思维方式,将不变和变化相分离,排序的基本步骤和算法是不变的,但按什么排序是变化的,sort方法将不变的算法设计为主体逻辑,而将变化的排序方式设计为参数,允许调用者动态指定,这也是一种常见的设计模式,它有一个名字,叫策略模式,不同的排序方式就是不同的策略。

二分查找

Arrays包含很多与sort对应的查找方法,可以在已排序的数组中进行二分查找,所谓二分查找就是从中间开始找,如果小于中间元素,则在前半部分找,否则在后半部分找,每比较一次,要么找到,要么将查找范围缩小一半,所以查找效率非常高。

二分查找既可以针对基本类型数组,也可以针对对象数组,对对象数组,也可以传递Comparator,也都可以指定查找范围,如下所示:

针对int数组

  1. public static int binarySearch(int[] a, int key)
  2. public static int binarySearch(int[] a, int fromIndex, int toIndex,
  3. int key)

针对对象数组

  1. public static int binarySearch(Object[] a, Object key)

自定义比较器

  1. public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)

如果能找到,binarySearch返回找到的元素索引,比如说:

  1. int[] arr = {3,5,7,13,21};
  2. System.out.println(Arrays.binarySearch(arr, 13));

输出为3。如果没找到,返回一个负数,这个负数等于:-(插入点+1),插入点表示,如果在这个位置插入没找到的元素,可以保持原数组有序,比如说:

  1. int[] arr = {3,5,7,13,21};
  2. System.out.println(Arrays.binarySearch(arr, 11));

输出为-4,表示插入点为3,如果在3这个索引位置处插入11,可以保持数组有序,即数组会变为:{3,5,7,11,13,21}

需要注意的是,binarySearch针对的必须是已排序数组,如果指定了Comparator,需要和排序时指定的Comparator保持一致,另外,如果数组中有多个匹配的元素,则返回哪一个是不确定的。

数组拷贝

与toString一样,也有多种重载形式,如:

  1. public static long[] copyOf(long[] original, int newLength)
  2. public static <T> T[] copyOf(T[] original, int newLength)

后面那个是泛型用法,这里表示的是,这个方法可以支持所有对象类型,参数是什么数组类型,返回结果就是什么数组类型。

newLength表示新数组的长度,如果大于原数组,则后面的元素值设为默认值。回顾一下默认值,对于数值类型,值为0,对于boolean,值为false,对于char,值为'\0',对于对象,值为null。

来看个例子:

  1. String[] arr = {"hello", "world"};
  2. String[] newArr = Arrays.copyOf(arr, 3);
  3. System.out.println(Arrays.toString(newArr));

输出为:

  1. [hello, world, null]

除了copyOf方法,Arrays中还有copyOfRange方法,以支持拷贝指定范围的元素,如:

  1. public static int[] copyOfRange(int[] original, int from, int to)

from表示要拷贝的第一个元素的索引,新数组的长度为to-from,to可以大于原数组的长度,如果大于,与copyOf类似,多出的位置设为默认值。

来看个例子:

  1. int[] arr = {0,1,3,5,7,13,19};
  2. int[] subArr1 = Arrays.copyOfRange(arr,2,5);
  3. int[] subArr2 = Arrays.copyOfRange(arr,5,10);
  4. System.out.println(Arrays.toString(subArr1));
  5. System.out.println(Arrays.toString(subArr2));

输出为:

  1. [3, 5, 7]
  2. [13, 19, 0, 0, 0]

subArr1是正常的子数组,subArr2拷贝时to大于原数组长度,后面的值设为了0。

数组比较

支持基本类型和对象类型,如下所示:

  1. public static boolean equals(boolean[] a, boolean[] a2)
  2. public static boolean equals(double[] a, double[] a2)
  3. public static boolean equals(Object[] a, Object[] a2)

只有数组长度相同,且每个元素都相同,才返回true,否则返回false。对于对象,相同是指equals返回true。

填充值

Arrays包含很多fill方法,可以给数组中的每个元素设置一个相同的值:

  1. public static void fill(int[] a, int val)

也可以给数组中一个给定范围的每个元素设置一个相同的值:

  1. public static void fill(int[] a, int fromIndex, int toIndex, int val)

比如说:

  1. int[] arr = {3,5,7,13,21};
  2. Arrays.fill(arr,2,4,0);
  3. System.out.println(Arrays.toString(arr));

将索引从2(含2)到4(不含4)的元素设置为0,输出为:

  1. [3, 5, 0, 0, 21]

哈希值

针对数组,计算一个数组的哈希值:

  1. public static int hashCode(int a[])

计算hashCode的算法和String是类似的,我们看下代码:

  1. public static int hashCode(int a[]) {
  2. if (a == null)
  3. return 0;
  4.  
  5. int result = 1;
  6. for (int element : a)
  7. result = 31 * result + element;
  8.  
  9. return result;
  10. }

回顾一下,String计算hashCode的算法也是类似的,数组中的每个元素都影响hash值,位置不同,影响也不同,使用31一方面产生的哈希值更分散,另一方面计算效率也比较高。

多维数组

之前我们介绍的数组都是一维的,数组还可以是多维的,先来看二维数组,比如:

  1. int[][] arr = new int[2][3];
  2. for(int i=0;i<arr.length;i++){
  3. for(int j=0;j<arr[i].length;j++){
  4. arr[i][j] = i+j;
  5. }
  6. }

arr就是一个二维数组,第一维长度为2,第二维长度为3,类似于一个长方形矩阵,或者类似于一个表格,第一维表示行,第二维表示列。arr[i]表示第i行,它本身还是一个数组,arr[i][j]表示第i行中的第j个元素。

除了二维,数组还可以是三维、四维等,但一般而言,很少用到三维以上的数组,有几维,就有几个[],比如说,一个三维数组的声明为:

  1. int[][][] arr = new int[10][10][10];

在创建数组时,除了第一维的长度需要指定外,其他维的长度不需要指定,甚至,第一维中,每个元素的第二维的长度可以不一样,看个例子:

  1. int[][] arr = new int[2][];
  2. arr[0] = new int[3];
  3. arr[1] = new int[5];

arr是一个二维数组,第一维的长度为2,第一个元素的第二维长度为3,而第二个为5。

多维数组到底是什么呢?其实,可以认为,多维数组只是一个假象,只有一维数组,只是数组中的每个元素还可以是一个数组,这样就形成二维数组,如果其中每个元素还都是一个数组,那就是三维数组。

多维数组的操作

Arrays中的toString,equals,hashCode都有对应的针对多维数组的方法:

  1. public static String deepToString(Object[] a)
  2. public static boolean deepEquals(Object[] a1, Object[] a2)
  3. public static int deepHashCode(Object a[])

这些deepXXX方法,都会判断参数中的元素是否也为数组,如果是,会递归进行操作。

看个例子:

  1. int[][] arr = new int[][]{
  2. {0,1},
  3. {2,3,4},
  4. {5,6,7,8}
  5. };
  6. System.out.println(Arrays.deepToString(arr));

输出为:

  1. [[0, 1], [2, 3, 4], [5, 6, 7, 8]]

实现原理

hashCode的实现我们已经介绍了,fill和equals的实现都很简单,循环操作而已,就不赘述了。

toString

toString的实现也很简单,利用了StringBuilder,我们列下代码,但不做解释了。

  1. public static String toString(int[] a) {
  2. if (a == null)
  3. return "null";
  4. int iMax = a.length - 1;
  5. if (iMax == -1)
  6. return "[]";
  7.  
  8. StringBuilder b = new StringBuilder();
  9. b.append('[');
  10. for (int i = 0; ; i++) {
  11. b.append(a[i]);
  12. if (i == iMax)
  13. return b.append(']').toString();
  14. b.append(", ");
  15. }
  16. }

拷贝

copyOf和copyOfRange利用了 System.arraycopy,逻辑也很简单,我们也只是简单列下代码:

  1. public static int[] copyOfRange(int[] original, int from, int to) {
  2. int newLength = to - from;
  3. if (newLength < 0)
  4. throw new IllegalArgumentException(from + " > " + to);
  5. int[] copy = new int[newLength];
  6. System.arraycopy(original, from, copy, 0,
  7. Math.min(original.length - from, newLength));
  8. return copy;
  9. }

二分查找

二分查找binarySearch的代码也比较直接,主要代码如下:

  1. private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
  2. T key, Comparator<? super T> c) {
  3. int low = fromIndex;
  4. int high = toIndex - 1;
  5.  
  6. while (low <= high) {
  7. int mid = (low + high) >>> 1;
  8. T midVal = a[mid];
  9. int cmp = c.compare(midVal, key);
  10. if (cmp < 0)
  11. low = mid + 1;
  12. else if (cmp > 0)
  13. high = mid - 1;
  14. else
  15. return mid; // key found
  16. }
  17. return -(low + 1); // key not found.
  18. }

有两个标志low和high,表示查找范围,在while循环中,与中间值进行对比,大于则在后半部分找(提高low),否则在前半部分找(降低high)。

排序

最后,我们来看排序方法sort,与前面这些简单直接的方法相比,sort要复杂的多,排序是计算机程序中一个非常重要的方面,几十年来,计算机科学家和工程师们对此进行了大量的研究,设计实现了各种各样的算法和实现,进行了大量的优化。一般而言,没有一个所谓最好的算法,不同算法往往有不同的适用场合。

那Arrays的sort是如何实现的呢?

对于基本类型的数组,Java采用的算法是双枢轴快速排序(Dual-Pivot Quicksort),这个算法是Java 1.7引入的,在此之前,Java采用的算法是普通的快速排序,双枢轴快速排序是对快速排序的优化,新算法的实现代码位于类java.util.DualPivotQuicksort中。

对于对象类型,Java采用的算法是TimSort, TimSort也是在Java 1.7引入的,在此之前,Java采用的是归并排序,TimSort实际上是对归并排序的一系列优化,TimSort的实现代码位于类java.util.TimSort中。

在这些排序算法中,如果数组长度比较小,它们还会采用效率更高的插入排序。

为什么基本类型和对象类型的算法不一样呢?排序算法有一个稳定性的概念,所谓稳定性就是对值相同的元素,如果排序前和排序后,算法可以保证它们的相对顺序不变,那算法就是稳定的,否则就是不稳定的。

快速排序更快,但不稳定,而归并排序是稳定的。对于基本类型,值相同就是完全相同,所以稳定不稳定没有关系。但对于对象类型,相同只是比较结果一样,它们还是不同的对象,其他实例变量也不见得一样,稳定不稳定可能就很有关系了,所以采用归并排序。

这些算法的实现是比较复杂的,所幸的是,Java给我们提供了很好的实现,绝大多数情况下,我们会用就可以了。

更多方法

其实,Arrays中包含的数组方法是比较少的,很多常用的操作没有,比如,Arrays的binarySearch只能针对已排序数组进行查找,那没有排序的数组怎么方便查找呢?

Apache有一个开源包(http://commons.apache.org/proper/commons-lang/),里面有一个类ArrayUtils (位于包org.apache.commons.lang3),里面实现了更多的常用数组操作,这里列举一些,与Arrays类似,每个操作都有很多重载方法,我们只列举一个。

翻转数组元素

  1. public static void reverse(final int[] array)

对于基本类型数组,Arrays的sort只能从小到大排,如果希望从大到小,可以在排序后,使用reverse进行翻转。

查找元素

  1. //从头往后找
  2. public static int indexOf(final int[] array, final int valueToFind)
  3.  
  4. //从尾部往前找
  5. public static int lastIndexOf(final int[] array, final int valueToFind)
  6.  
  7. //检查是否包含元素
  8. public static boolean contains(final int[] array, final int valueToFind)

删除元素

因为数组长度是固定的,删除是通过创建新数组,然后拷贝除删除元素外的其他元素来实现的。

  1. //删除指定位置的元素
  2. public static int[] remove(final int[] array, final int index)
  3.  
  4. //删除多个指定位置的元素
  5. public static int[] removeAll(final int[] array, final int... indices)
  6.  
  7. //删除值为element的元素,只删除第一个
  8. public static boolean[] removeElement(final boolean[] array, final boolean element)

添加元素

同删除一样,因为数组长度是固定的,添加是通过创建新数组,然后拷贝原数组内容和新元素来实现的。

  1. //添加一个元素
  2. public static int[] add(final int[] array, final int element)
  3.  
  4. //在指定位置添加一个元素
  5. public static int[] add(final int[] array, final int index, final int element)
  6.  
  7. //合并两个数组
  8. public static int[] addAll(final int[] array1, final int... array2)

判断数组是否是已排序的

  1. public static boolean isSorted(int[] array)

小结

本节我们分析了Arrays类,介绍了其用法,以及基本实现原理,同时,我们介绍了多维数组以及Apache中的ArrayUtils类。对于带Comparator参数的排序方法,我们提到,这是一种思维和设计模式,值得学习。

数组是计算机程序中的基本数据结构,Arrays类以及ArrayUtils类封装了关于数组的常见操作,使用这些方法吧!

下一节,我们来看计算机程序中,另一种常见的操作,就是对日期的操作。

----------------

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心写作,原创文章,保留所有版权。

计算机程序的思维逻辑 (31) - 剖析Arrays的更多相关文章

  1. 计算机程序的思维逻辑 (29) - 剖析String

    上节介绍了单个字符的封装类Character,本节介绍字符串类.字符串操作大概是计算机程序中最常见的操作了,Java中表示字符串的类是String,本节就来详细介绍String. 字符串的基本使用是比 ...

  2. Java编程的逻辑 (31) - 剖析Arrays

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  3. 计算机程序的思维逻辑 (53) - 剖析Collections - 算法

    之前几节介绍了各种具体容器类和抽象容器类,上节我们提到,Java中有一个类Collections,提供了很多针对容器接口的通用功能,这些功能都是以静态方法的方式提供的. 都有哪些功能呢?大概可以分为两 ...

  4. 计算机程序的思维逻辑 (48) - 剖析ArrayDeque

    前面我们介绍了队列Queue的两个实现类LinkedList和PriorityQueue,LinkedList还实现了双端队列接口Deque,Java容器类中还有一个双端队列的实现类ArrayDequ ...

  5. 计算机程序的思维逻辑 (30) - 剖析StringBuilder

    上节介绍了String,提到如果字符串修改操作比较频繁,应该采用StringBuilder和StringBuffer类,这两个类的方法基本是完全一样的,它们的实现代码也几乎一样,唯一的不同就在于,St ...

  6. 计算机程序的思维逻辑 (38) - 剖析ArrayList

    从本节开始,我们探讨Java中的容器类,所谓容器,顾名思义就是容纳其他数据的,计算机课程中有一门课叫数据结构,可以粗略对应于Java中的容器类,我们不会介绍所有数据结构的内容,但会介绍Java中的主要 ...

  7. 计算机程序的思维逻辑 (51) - 剖析EnumSet

    上节介绍了EnumMap,本节介绍同样针对枚举类型的Set接口的实现类EnumSet.与EnumMap类似,之所以会有一个专门的针对枚举类型的实现类,主要是因为它可以非常高效的实现Set接口. 之前介 ...

  8. 计算机程序的思维逻辑 (54) - 剖析Collections - 设计模式

    上节我们提到,类Collections中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了第一类,本节我们介绍第二类. 第二类方法大概可以分为两组: 接受其他 ...

  9. 计算机程序的思维逻辑 (39) - 剖析LinkedList

    上节我们介绍了ArrayList,ArrayList随机访问效率很高,但插入和删除性能比较低,我们提到了同样实现了List接口的LinkedList,它的特点与ArrayList几乎正好相反,本节我们 ...

随机推荐

  1. Angular2入门系列教程3-多个组件,主从关系

    上一篇 Angular2项目初体验-编写自己的第一个组件 好了,前面简单介绍了Angular2的基本开发,并且写了一个非常简单的组件,这篇文章我们将要学会编写多个组件并且有主从关系 现在,假设我们要做 ...

  2. IE的F12开发人员工具不显示问题

    按下F12之后,开发人员工具在桌面上看不到,但是任务栏里有显示.将鼠标放在任务栏的开发人员工具上,出现一片透明的区域,选中之后却出不来.将鼠标移动到开发人员工具的缩略图上,右键-最大化,工具就全屏出现 ...

  3. CentOS7 重置root密码

    1- 在启动grub菜单,选择编辑选项启动 2 - 按键盘e键,来进入编辑界面 3 - 找到Linux 16的那一行,将ro改为rw init=/sysroot/bin/sh 4 - 现在按下 Con ...

  4. Linux上如何查看物理CPU个数,核数,线程数

    首先,看看什么是超线程概念 超线程技术就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的 ...

  5. 对Thoughtworks的有趣笔试题实践

    记得2014年在网上看到Thoughtworks的一道笔试题,当时觉得挺有意思,但是没动手去写.这几天又在网上看到了,于是我抽了一点时间写了下,我把程序运行的结果跟网上的答案对了一下,应该是对的,但是 ...

  6. JavaWeb——ServletContext

    一.基本概念 说起ServletContext,一些人会产生误解,以为一个servlet对应一个ServletContext.其实不是这样的,事实是一个web应用对应一个ServletContext, ...

  7. margin折叠-从子元素margin-top影响父元素引出的问题

    正在做一个手机端电商项目,顶部导航栈的布局是一个div包含一个子div,如果给在正常文档流中的子div一个垂直margin-top,神奇的现象出现了,两父子元素的边距没变,但父div跟着一起往下走了! ...

  8. 编译器开发系列--Ocelot语言2.变量引用的消解

    "变量引用的消解"是指确定具体指向哪个变量.例如变量"i"可能是全局变量i,也可能是静态变量i,还可能是局部变量i.通过这个过程来消除这样的不确定性,确定所引用 ...

  9. GitHub管理代码-随笔

    公司一直用的SVN进行项目管理,平时便自己折腾了下Git,这里做下GitHub的最简单的记录... 在git上创建仓库等就免谈了,网上也有好多教程,直接从创建之后记录: 在github的readme文 ...

  10. Atitit  godaddy 文件权限 root权限设置

    Atitit  godaddy 文件权限 root权限设置 1. ubuntu需要先登录,再su切换到root1 2. sudo 授权许可使用的su,也是受限制的su1 3. ubuntu默认吗roo ...