第一个_第一个程序 - CSDN
精华内容
参与话题
  • 第一个

    2016-10-11 17:51:40
    四、Java的数据类型 1、Java的基本数据类型都有哪些各占几字节 ...boolean 1(boolean类型比较特别可能只占一个bit,多boolean可能共同占用一个字节) 2、String是基本数据类型吗?可以被继承吗? String是引

    四、Java的数据类型

    1、Java的基本数据类型都有哪些各占几个字节

    Java有8种基本数据类型

    byte 1

    char 2

    sort 2

    int 4

    float 4

    double 8

    long 8

    boolean 1(boolean类型比较特别可能只占一个bit,多个boolean可能共同占用一个字节)

    2、String是基本数据类型吗?可以被继承吗?

    String是引用类型,底层用char数组实现的。因为String是final类,在java中被final修饰的类不能被继承,因此String当然不可以被继承。

     

    五、Java的IO

    1、Java中有几种类型的流

    字节流和字符流。字节流继承于InputStream和OutputStream,字符流继承于InputStreamReader 和OutputStreamWriter。

    2、字节流如何转为字符流

    字节输入流转字符输入流通过InputStreamReader实现,该类的构造函数可以传入InputStream对象。

    字节输出流转字符输出流通过OutputStreamWriter实现,该类的构造函数可以传入OutputStream对象。

    3、如何将一个java对象序列化到文件里

    java中能够被序列化的类必须先实现Serializable接口,该接口没有任何抽象方法只是起到一个标记作用。

    六、Java的集合

    1、HashMap排序题,上机题。(本人主要靠这道题入职的第一家公司)

    已知一个HashMap<Integer,User>集合, User有name(String)和age(int)属性。请写一个方法实现对HashMap的排序功能,该方法接收HashMap<Integer,User>为形参,返回类型为HashMap<Integer,User>,要求对HashMap中的User的age倒序进行排序。排序时key=value键值对不得拆散。

    :要做出这道题必须对集合的体系结构非常的熟悉。HashMap本身就是不可排序的,但是该道题偏偏让给HashMap排序,那我们就得想在API中有没有这样的Map结构是有序的,LinkedHashMap,对的,就是他,他是Map结构,也是链表结构,有序的,更可喜的是他是HashMap的子类,我们返回LinkedHashMap<Integer,User>即可,还符合面向接口(父类编程的思想)。

    但凡是对集合的操作,我们应该保持一个原则就是能用JDK中的API就有JDK中的API,比如排序算法我们不应该去用冒泡或者选择,而是首先想到用Collections集合工具类。

     

    2、集合的安全性问题

    请问ArrayList、HashSet、HashMap是线程安全的吗?如果不是我想要线程安全的集合怎么办?

    我们都看过上面那些集合的源码(如果没有那就看看吧),每个方法都没有加锁,显然都是线程不安全的。话又说过来如果他们安全了也就没第二问了。

    在集合中Vector和HashTable倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized关键字。

    Collections工具类提供了相关的API,可以让上面那3个不安全的集合变为安全的。

    上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理非常简单,就是将集合的核心方法添加上了synchronized关键字。

    3、ArrayList内部用什么实现的?(2015-11-24)

    (回答这样的问题,不要只回答个皮毛,可以再介绍一下ArrayList内部是如何实现数组的增加和删除的,因为数组在创建的时候长度是固定的,那么就有个问题我们往ArrayList中不断的添加对象,它是如何管理这些数组呢?)

    ArrayList内部是用Object[]实现的。接下来我们分别分析ArrayList的构造、add、remove、clear方法的实现原理。

    一、构造函数

    1)空参构造

    /**

         * Constructs a new {@code ArrayList} instance with zero initial capacity.

         */

        public ArrayList() {

            array = EmptyArray.OBJECT;

    }

    array是一个Object[]类型。当我们new一个空参构造时系统调用了EmptyArray.OBJECT属性,EmptyArray仅仅是一个系统的类库,该类源码如下:

    public final class EmptyArray {

        private EmptyArray() {}

     

        public static final boolean[] BOOLEAN = new boolean[0];

        public static final byte[] BYTE = new byte[0];

        public static final char[] CHAR = new char[0];

        public static final double[] DOUBLE = new double[0];

        public static final int[] INT = new int[0];

     

        public static final Class<?>[] CLASS = new Class[0];

        public static final Object[] OBJECT = new Object[0];

        public static final String[] STRING = new String[0];

        public static final Throwable[] THROWABLE = new Throwable[0];

        public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];

    }

    也就是说当我们new 一个空参ArrayList的时候,系统内部使用了一个new Object[0]数组。

    2)带参构造1

     /**

         * Constructs a new instance of {@code ArrayList} with the specified

         * initial capacity.

         *

         * @param capacity

         *            the initial capacity of this {@code ArrayList}.

         */

        public ArrayList(int capacity) {

            if (capacity < 0) {

                throw new IllegalArgumentException("capacity < 0: " + capacity);

            }

            array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);

    }

    该构造函数传入一个int值,该值作为数组的长度值。如果该值小于0,则抛出一个运行时异常。如果等于0,则使用一个空数组,如果大于0,则创建一个长度为该值的新数组。

    3)带参构造2

    /**

         * Constructs a new instance of {@code ArrayList} containing the elements of

         * the specified collection.

         *

         * @param collection

         *            the collection of elements to add.

         */

        public ArrayList(Collection<? extends E> collection) {

            if (collection == null) {

                throw new NullPointerException("collection == null");

            }

     

            Object[] a = collection.toArray();

            if (a.getClass() != Object[].class) {

                Object[] newArray = new Object[a.length];

                System.arraycopy(a, 0, newArray, 0, a.length);

                a = newArray;

            }

            array = a;

            size = a.length;

        }

    如果调用构造函数的时候传入了一个Collection的子类,那么先判断该集合是否为null,为null则抛出空指针异常。如果不是则将该集合转换为数组a,然后将该数组赋值为成员变量array,将该数组的长度作为成员变量size。这里面它先判断a.getClass是否等于Object[].class,其实一般都是相等的,我也暂时没想明白为什么多加了这个判断,toArray方法是Collection接口定义的,因此其所有的子类都有这样的方法,list集合的toArray和Set集合的toArray返回的都是Object[]数组。

    这里讲些题外话,其实在看Java源码的时候,作者的很多意图都很费人心思,我能知道他的目标是啥,但是不知道他为何这样写。比如对于ArrayList, array是他的成员变量,但是每次在方法中使用该成员变量的时候作者都会重新在方法中开辟一个局部变量,然后给局部变量赋值为array,然后再使用,有人可能说这是为了防止并发修改array,毕竟array是成员变量,大家都可以使用因此需要将array变为局部变量,然后再使用,这样的说法并不是都成立的,也许有时候就是老外们写代码的一个习惯而已。

    二、add方法

    add方法有两个重载,这里只研究最简单的那个。

          /**

         * Adds the specified object at the end of this {@code ArrayList}.

         *

         * @param object

         *            the object to add.

         * @return always true

         */

        @Override public boolean add(E object) {

            Object[] a = array;

            int s = size;

            if (s == a.length) {

                Object[] newArray = new Object[s +

                        (s < (MIN_CAPACITY_INCREMENT / 2) ?

                         MIN_CAPACITY_INCREMENT : s >> 1)];

                System.arraycopy(a, 0, newArray, 0, s);

                array = a = newArray;

            }

            a[s] = object;

            size = s + 1;

            modCount++;

            return true;

        }

    1、首先将成员变量array赋值给局部变量a,将成员变量size赋值给局部变量s。

    2、判断集合的长度s是否等于数组的长度(如果集合的长度已经等于数组的长度了,说明数组已经满了,该重新分配新数组了),重新分配数组的时候需要计算新分配内存的空间大小,如果当前的长度小于MIN_CAPACITY_INCREMENT/2(这个常量值是12,除以2就是6,也就是如果当前集合长度小于6)则分配12个长度,如果集合长度大于6则分配当前长度s的一半长度。这里面用到了三元运算符和位运算,s >> 1,意思就是将s往右移1位,相当于s=s/2,只不过位运算是效率最高的运算。

    3、将新添加的object对象作为数组的a[s]个元素。

    4、修改集合长度size为s+1

    5、modCotun++,该变量是父类中声明的,用于记录集合修改的次数,记录集合修改的次数是为了防止在用迭代器迭代集合时避免并发修改异常,或者说用于判断是否出现并发修改异常的。

    6、return true,这个返回值意义不大,因为一直返回true,除非报了一个运行时异常。

    三、remove方法

    remove方法有两个重载,我们只研究remove(int index)方法。

          /**

         * Removes the object at the specified location from this list.

         *

         * @param index

         *            the index of the object to remove.

         * @return the removed object.

         * @throws IndexOutOfBoundsException

         *             when {@code location < 0 || location >= size()}

         */

        @Override public E remove(int index) {

            Object[] a = array;

            int s = size;

            if (index >= s) {

                throwIndexOutOfBoundsException(index, s);

            }

            @SuppressWarnings("unchecked")

            E result = (E) a[index];

            System.arraycopy(a, index + 1, a, index, --s - index);

            a[s] = null;  // Prevent memory leak

            size = s;

            modCount++;

            return result;

        }

    1、先将成员变量array和size赋值给局部变量a和s。

    2、判断形参index是否大于等于集合的长度,如果成了则抛出运行时异常

    3、获取数组中脚标为index的对象result,该对象作为方法的返回值

    4、调用System的arraycopy函数,拷贝原理如下图所示。

     

    5、接下来就是很重要的一个工作,因为删除了一个元素,而且集合整体向前移动了一位,因此需要将集合最后一个元素设置为null,否则就可能内存泄露。

    6、重新给成员变量array和size赋值

    7、记录修改次数

    8、返回删除的元素(让用户再看最后一眼)

    四、clear方法

           /**

         * Removes all elements from this {@code ArrayList}, leaving it empty.

         *

         * @see #isEmpty

         * @see #size

         */

        @Override public void clear() {

            if (size != 0) {

                Arrays.fill(array, 0, size, null);

                size = 0;

                modCount++;

            }

        }

    如果集合长度不等于0,则将所有数组的值都设置为null,然后将成员变量size设置为0即可,最后让修改记录加1。

    4、并发集合和普通集合如何区别?(2015-11-24)

    并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并发集合位于java.util.concurrent包下,是jdk1.5之后才有的,主要作者是Doug Lea(http://baike.baidu.com/view/3141057.htm)完成的。

    java中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了synchronized同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。

    参考阅读:

     ConcurrentHashMap是线程安全的HashMap的实现,默认构造同样有initialCapacity和loadFactor属性,不过还多了一个concurrencyLevel属性,三属性默认值分别为16、0.75及16。其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。

    put操作:并没有在此方法上加上synchronized,首先对key.hashcode进行hash操作,得到key的hash值。hash操作的算法和map也不同,根据此hash值计算并获取其对应的数组中的Segment对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。

    ConcurrentHashMap基于concurrencyLevel划分出了多个Segment来对key-value进行存储,从而避免每次put操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。

    get(key)

        首先对key.hashCode进行hash操作,基于其值找到对应的Segment对象,调用其get方法完成当前操作。而Segment的get操作首先通过hash值和对象数组大小减1的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry产生不一致性,那么ConcurrentHashMap是如何保证的?

        对象数组大小的改变只有在put操作时有可能发生,由于HashEntry对象数组对应的变量是volatile类型的,因此可以保证如HashEntry对象数组大小发生改变,读操作可看到最新的对象数组大小。

        在获取到了HashEntry对象后,怎么能保证它及其next属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap采用了一个简单的方式,即HashEntry对象中的hash、key、next属性都是final的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry对象后,其基于next属性构建的链表是不会发生变化的。

        ConcurrentHashMap默认情况下采用将数据分为16个段进行存储,并且16个段分别持有各自不同的锁Segment,锁仅用于put和remove等改变集合对象的操作,基于volatile及HashEntry链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深刻掌握的体现。

    推荐博客地址:http://m.oschina.net/blog/269037

     

    七、Java的多线程

    1、多线程的两种创建方式

    java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接实现Runnable接口来重写run()方法实现线程。

    2、java中wait和sleep方法的不同?

    最大的不同是在等待时wait会释放锁,而sleep一直持有锁。wait通常被用于线程间交互,sleep通常被用于暂停执行。

    3、synchronized和volatile关键字的作用

    一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

    1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

    2)禁止进行指令重排序。

    volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;

    synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

    1.volatile仅能使用在变量级别;

    synchronized则可以使用在变量、方法、和类级别的

    2.volatile仅能实现变量的修改可见性,并不能保证原子性;

    synchronized则可以保证变量的修改可见性和原子性

    3.volatile不会造成线程的阻塞;

    synchronized可能会造成线程的阻塞。

    4.volatile标记的变量不会被编译器优化;

    synchronized标记的变量可以被编译器优化

    4、分析线程并发访问代码解释原因

    上面的代码执行完后输出的结果确定为1000吗?

    答案是不一定,或者不等于1000。这是为什么吗?

    java 的内存模型中每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

    也就是说上面主函数中开启了1000个子线程,每个线程都有一个变量副本,每个线程修改变量只是临时修改了自己的副本,当线程结束时再将修改的值写入在主内存中,这样就出现了线程安全问题。因此结果就不可能等于1000了,一般都会小于1000。

    上面的解释用一张图表示如下:

    图片来自网络,非本人所绘

    5、什么是线程池,如何使用?

    线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。

    JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法。

    然后调用他们的execute方法即可。

    6、请叙述一下您对线程池的理解?(2015-11-25)

    (如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)

    合理利用线程池能够带来三个好处。

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    7、线程池的启动策略?(2015-11-25)

    官方对线程池的执行过程描述如下:

    1.  /*

    2.          * Proceed in 3 steps:

    3.          *

    4.          * 1. If fewer than corePoolSize threads are running, try to

    5.          * start a new thread with the given command as its first

    6.          * task.  The call to addWorker atomically checks runState and

    7.          * workerCount, and so prevents false alarms that would add

    8.          * threads when it shouldn't, by returning false.

    9.          *

    10.          * 2. If a task can be successfully queued, then we still need

    11.          * to double-check whether we should have added a thread

    12.          * (because existing ones died since last checking) or that

    13.          * the pool shut down since entry into this method. So we

    14.          * recheck state and if necessary roll back the enqueuing if

    15.          * stopped, or start a new thread if there are none.

    16.          *

    17.          * 3. If we cannot queue task, then we try to add a new

    18.          * thread.  If it fails, we know we are shut down or saturated

    19.          * and so reject the task.

    20.          */

    1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

    2、当调用execute() 方法添加一个任务时,线程池会做如下判断:

         a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

    b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。

    c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;

    d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

    3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

    4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

    8、如何控制某个方法允许并发访问线程的个数?(2015-11-30)

    21. package com.yange;

    22. 

    23. import java.util.concurrent.Semaphore;

    24. /**

    25.  *

    26.  * @author wzy 2015-11-30

    27.  *

    28.  */

    29. public class SemaphoreTest {

    30. /*

    31. * permits the initial number of permits available. This value may be negative,

    32. in which case releases must occur before any acquires will be granted.

    33. fair true if this semaphore will guarantee first-in first-out granting of

    34. permits under contention, else false

    35. */

    36. static Semaphore semaphore = new Semaphore(5,true);

    37. public static void main(String[] args) {

    38. for(int i=0;i<100;i++){

    39. new Thread(new Runnable() {

    40. 

    41. @Override

    42. public void run() {

    43. test();

    44. }

    45. }).start();

    46. }

    47. 

    48. }

    49. 

    50. public static void test(){

    51. try {

    52.         //申请一个请求

    53. semaphore.acquire();

    54. } catch (InterruptedException e1) {

    55. e1.printStackTrace();

    56. }

    57. System.out.println(Thread.currentThread().getName()+"进来了");

    58. try {

    59. Thread.sleep(1000);

    60. } catch (InterruptedException e) {

    61. e.printStackTrace();

    62. }

    63. System.out.println(Thread.currentThread().getName()+"走了");

    64.    //释放一个请求

    65. semaphore.release();

    66. }

    67. }

    68. 

    可以使用Semaphore控制,第16行的构造函数创建了一个Semaphore对象,并且初始化了5个信号。这样的效果是控件test方法最多只能有5个线程并发访问,对于5个线程时就排队等待,走一个来一下。第33行,请求一个信号(消费一个信号),如果信号被用完了则等待,第45行释放一个信号,释放的信号新的线程就可以使用了。

     

    1. JavaSE高级(★★

    一、Java中的反射

    1、说说你对Java中反射的理解

    Java中的反射首先是能够获取到Java中要反射类的字节码,获取字节码有三种方法,1.Class.forName(className) 2.类名.class 3.this.getClass()。然后将字节码中的方法,变量,构造函数等映射成相应的Method、Filed、Constructor等类,这些类提供了丰富的方法可以被我们所使用。

    二、Java中的动态代理

    1、写一个ArrayList的动态代理类(笔试题)

    2、动静态代理的区别,什么场景使用?(2015-11-25)

    静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。

        静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。

        动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。

        还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

    AOP编程就是基于动态代理实现的,比如著名的Spring框架、Hibernate框架等等都是动态代理的使用例子。

    三、Java中的设计模式&回收机制

    1、你所知道的设计模式有哪些

    Java中一般认为有23种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。

    总体来说设计模式分为三大类:

    创建型模式,共五种:工厂方法模式抽象工厂模式单例模式建造者模式、原型模式。

    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    2、单例设计模式

    最好理解的一种设计模式,分为懒汉式和饿汉式。

     饿汉式:

     懒汉式:

    3、工厂设计模式

    工厂模式分为工厂方法模式和抽象工厂模式。

     工厂方法模式

    工厂方法模式分为三种:普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

    多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

    静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

     普通工厂模式

     多个工厂方法模式

    该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

     静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

     抽象工厂模式

    工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

    4、建造者模式(Builder)

    工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。

    5、适配器设计模式

     适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

     类的适配器模式

     对象的适配器模式

    基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。

     接口的适配器模式

    接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。

    6、装饰模式(Decorator)

    顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

    7、策略模式(strategy)

    策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

    8、观察者模式(Observer)

    观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。

    9、JVM垃圾回收机制和常见算法

    理论上来讲Sun公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同。

    GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象,如何去发现定位这些无用的对象?常用的搜索算法如下:

    1)引用计数器算法(废弃)

    引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象不再被使用,是“垃圾”了。

    引用计数器实现简单,效率高;但是不能解决循环引用问问题(A对象引用B对象,B对象又引用A对象,但是A,B对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在JDK1.1之后,这个算法已经不再使用了。

    2)根搜索算法(使用)

    根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots的引用链连接的时候,说明这个对象是不可用的。

    GC Roots对象包括:

    a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。

    b) 方法区域中的类静态属性引用的对象。

    c) 方法区域中常量引用的对象。

    d) 本地方法栈中JNI(Native方法)的引用的对象。

    通过上面的算法搜索到无用对象之后,就是回收过程,回收算法如下:

    1)标记—清除算法(Mark-Sweep)(DVM使用的算法

    标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。

    2)复制算法(Copying)

    复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的JVM用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)。

    3)标记—整理算法(Mark-Compact)

    标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。

    4)分代收集(Generational Collection)

    分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。

    10、谈谈JVM的内存结构和内存分配

    a) Java内存模型

    Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。

        1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。

    常数池,源代码中的命名常量、String常量和static变量保存在方法区。

        2、Java Stack是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。

            最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的  方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。

        3、Java堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。

           堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

    我们每天都在写代码,每天都在使用JVM的内存。

    b) java内存分配

         1、基础数据类型直接在栈空间分配;

         2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;

         3、引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;

         4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;

         5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;

         6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;

         7、字符串常量在 DATA 区域分配 ,this 在堆空间分配;

         8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!

    11、Java中引用类型都有哪些?(重要)

    Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

     强引用(StrongReference)

    这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

    Java的对象是位于heap中的,heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下代码:

    第一行在heap堆中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。

    第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的abc对象已经有3个引用,显然此时abc对象仍是强可及的。

    第四行之后heap中对象不再是强可及的,变成软可及的。

    第五行执行之后变成弱可及的。

     软引用(SoftReference)

    如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用时执行以下过程,以上面的softRef为例:

        1 首先将softRef的referent(abc)设置为null,不再引用heap中的new String("abc")对象。

        2 将heap中的new String("abc")对象设置为可结束的(finalizable)。

        3 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, softRef被添加到它的ReferenceQueue(如果有的话)中。

       注意:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有。

       Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且没有Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (OutOfMemoryError)。

     弱引用(WeakReference)

    如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被gc扫描到了随时都会把它干掉。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

     虚引用(PhantomReference)

    "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。

    虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程再说一下他的作用。

      1 不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable)。

      2 与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象。

     

    2. Android基础(★★★

    一、Android基本常识

    1、10个简单的linux命令

    mkdir 创建文件夹

    rmdir 删除文件夹

    rm 删除文件

    mv 移动文件

    cp 拷贝文件

    cat 查看文件

    tail 查看文件尾部

    more 分页查看文件

    cd 切换当前目录

    ls 列出文件清单

    reboot 重启

    date 显示日期

    cal  显示日历

    ps 查看系统进程相当于windows的任务管理器

    ifconfig 配置网络

    2、书写出android工程的目录结构

    src 源文件

    gen 生成的文件 R文件就在此

    android. jar 依赖的android sdk

    assets 资源文件

        bin 生成的字节码apk在此

    libs 依赖jar和so

    res 资源文件

    drawable

    drawable-hdpi

    layout

    menu

    values

    AndroidManifest.xml

    project.properties

    3、什么是ANR 如何避免它?

    Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。

    不同的组件发生ANR的时间不一样,主线程(Activity、Service)是5秒,BroadCastReceiver 是10秒。

    解决方案:

    将所有耗时操作,比如访问网络,Socket通信,查询大量SQL语句,复杂逻辑计算等都放在子线程中去,然后通过handler.sendMessage、runonUITread、AsyncTask等方式更新UI。无论如何都要确保用户界面操作的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示进度条。

    4、谈谈Android的优点和不足之处

    优点:

    1、开放性,开源,免费,可定制

    2、挣脱运营商束缚

    3、丰富的硬件选择

    4、不受任何限制的开发商

    5、无缝结合的Google应用

    缺点:

    1、安全问题、隐私问题

    2、同质化严重

    3、运营商对Android手机仍然有影响

    4、山寨化严重

    5、过分依赖开发商,缺乏标准配置

    5、一条最长的短信息约占多少byte? 

    在国内的三大运营商通常情况下中文70(包括标点),英文160个。对于国外的其他运行商具体多长需要看运营商类型了。

    android内部是通过如下代码进行判断具体一个短信多少byte的。

    ArrayList<String> android.telephony.SmsManager.divideMessage(String text)

    6、sim卡的EF文件有何作用?(不用看,废弃)

    基本文件EF(Elementary File)是SIM卡文件系统的一部分。

     

    文件

    文件标识符

    文件缩写

    中文名称

    文件作用

    MF

    3F00

    根目录

    备注:所有非ETSI GSM协议中规定的应用文件由各厂家自行定义在根目录下(如:PIN1,PIN2…)

    EFICCID

    2FE2

    ICCID

    SIM卡唯一的识别号

    包含运营商、卡商、发卡时间、省市代码等信息

    DFGSM

    7F20

    GSM目录

    备注:根据ETSIGSM09.91的规定Phase2(或以上)的SIM卡中应该有7F21并指向7F20,用以兼容Phase1的手机

    EFLP语言选择

    6F05

    LP

    语言选择文件

    包含一种或多种语言编码

    EFIMSI

    6F07

    IMSI

    国际移动用户识别符

    包含SIM卡所对应的号段,比如46000代表135-139号段、46002代表1340-1348

    EFKC语音加密密钥

    6F20

    Kc

    计算密钥

    用于SIM卡的加密、解密

    EFPLMNsel网络选择表

    6F30

    PLMNsel

    公共陆地网选择

    决定SIM卡选择哪种网络,在这里应该选择中国移动的网络

    EFHPLMN归属地网络选择表

    6F31

    HPLMN

    两次搜索PLMN的时间间隔

    两次搜索中国移动的网络的时间间隔

    EFACMmax最大计费额

    6F37

    ACMmax

    包含累积呼叫表的最大值

    全部的ACM数据存在SIM卡中,此处取最大值

    EFSST SIM卡服务表

    6F38

    SST

    SIM卡服务列表

    指出SIM卡可以提供服务的种类,哪些业务被激活哪些业务没有开通

    EFACM累加计费计数器

    6F39

    ACM

    累计呼叫列表

    当前的呼叫和以前的呼叫的单位总和

    EFGID1分组识别1

    6F3E

    GID1

    1级分组识别文件

    包含特定的SIM-ME组合的标识符,可以识别一组特定的SIM卡

    EFGID2分组识别2

    6F3F

    GID2

    2级分组识别文件

    包含特定的SIM-ME组合的标识符,可以识别一组特定的SIM卡

    EFPUCT单位价格/货币表

    6F41

    PUCT

    呼叫单位的价格和货币表

    PUCT是与计费通知有关的信息,ME用这个信息结合EFACM,以用户选择的货币来计算呼叫费用

    EFCBMI小区广播识别号

    6F45

    CBMI

    小区广播信息标识符

    规定了用户希望MS采纳的小区广播消息内容的类型

    EFSPN服务提供商

    6F46

    SPN

    服务提供商名称

    包含服务提供商的名称和ME显示的相应要求

    EFCBMID

    6F48

    CBMID

    数据下载的小区广播消息识别符

    移动台将收到的CBMID传送给SIM卡

    EFSUME

    6F54

    SUME

    建立菜单单元

    建立SIM卡中的菜单

    EFBCCH广播信道

    6F74

    BCCH

    广播控制信道

    由于BCCH的存储,在选择小区时,MS可以缩小对BCCH载波的搜索范围

    EFACC访问控制级别

    6F78

    ACC

    访问控制级别

    SIM卡有15个级别,10个普通级别,5个高级级别

    EFFPLMN禁止网络号

    6F7B

    FPLMN

    禁用的PLMN

    禁止选择除中国移动以外的其他运营商,比如中国联通、中国卫通等

    EFLOCI位置信息

    6F7E

    LOCI

     位置信息

    存储临时移动用户识别符、位置区信息等内容

    EFAD管理数据

    6FAD

    AD

    管理数据

    包括关于不同类型SIM卡操作模式的信息。例如:常规模式(PLMN用户用于GSM网络操作),型号认证模式(允许ME在无线设备的认证期间的特殊应用);小区测试模式(在小区商用之前,进行小区测试),制造商特定模式(允许ME制造商在维护阶段进行特定的性能自动测试)

    EFPHASE阶段

    6FAE

    PHASE

    阶段标识

    标识SIM卡所处的阶段信息,比如是普通SIM卡还是STK卡等

    DFTELECOM

    7F10

    电信目录

     

     

    EFADN缩位拨号

    6F3A

    AND

    电话簿

    用于将电话记录存放在SIM卡中

    EFFDN固定拨号

    6F3B

    FDN

    固定拨号

    包括固定拨号(FDN)和/或补充业务控制字串(SSC),还包括相关网络/承载能力的识别符和扩展记录的识别符,以及有关的α识别符

    EFSMS短消息

    6F3C

    SMS

    短消息

    用于将短消息记录存放在SIM卡中

    EFCCP能力配置参数

    6F3D

    CCP

    能力配置参数

    包括所需要的网络和承载能力的参数,以及当采用一个缩位拨号号码,固定拨号号码,MSISDN、最后拨号号码、服务拨号号码或禁止拨号方式等,建立呼叫时相关的ME配置

    EFMSISDN电话号码

    6F40

    MSISDN

    移动基站国际综合业务网号

    存放用户的手机号

    EFSMSP短信息参数

    6F42

    SMSP

    短消息业务参数

    包括短信中心号码等信息

    EFSMSS短信息状态

    6F43

    SMSS

    短消息状态

    这个标识是用来控制流量的

    EFLND最后拨号

    6F44

    LND

    最后拨叫号码

    存储最后拨叫号码

    EFExt1扩展文件1

    6F4A

    EXT1

    扩展文件1

    包括AND,MSISDN或LND的扩展数据

    EFExt2扩展文件2

    6F4B

    EXT2

    扩展文件2

    包含FDN的扩展数据

    · 

     

    7、如何判断是否有SD卡?

     通过如下方法:

    Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)

    如果返回true就是有sdcard,如果返回false则没有。

    8、dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念?

    dvm指dalvik的虚拟机。每一个Android应用程序都拥有一个独立的Dalvik虚拟机实例,应用程序都在它自己的进程中运行。而每一个dvm都是在Linux 中的一个进程,所以说可以近似认为是同一个概念。

    什么是android DVM:Dalvik是Google公司自己设计用于Android平台的Java虚拟机,每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

    Dalvik和Java虚拟机的区别

    1:Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。   

    2:Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。

    3:不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式Dex

    4: dex文件格式可以减少整体文件尺寸,提高I/O操作的类查找速度。 

    5: odex是为了在运行过程中进一步提高性能,对dex文件的进一步优化。   

    6:所有的Android应用的线程都对应一个Linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制

    7:有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域。

    9、Android程序与Java程序的区别?

     Android程序用android sdk开发,java程序用javasdk开发.

    Android SDK引用了大部分的Java SDK,少数部分被Android SDK抛弃,比如说界面部分,java.awt  swing  package除了java.awt.font被引用外,其他都被抛弃,在Android平台开发中不能使用。android sdk 添加工具jar httpclient , pull  opengl 

    展开全文
  • 第一个springboot程序

    2020-09-24 11:32:52
    第一个springboot程序 项目结构:与spring不尽相同 1、/main/java/…/HelloworldApplication,java:程序的主入口 2、/main/resource/application.properties:核心配置文件 3、新建项目包必须与程序的主入口...

    第一个springboot程序

    项目结构:与spring不尽相同
    在这里插入图片描述
    1、/main/java/…/HelloworldApplication,java:程序的主入口
    2、/main/resource/application.properties:核心配置文件
    3、新建项目包必须与程序的主入口(HelloworldApplication,java)同级目录。
    在这里插入图片描述

    因为在pom.xml中约定了主入口(HelloworldApplication,java)所在的包为父项目,程序只会扫描这个包里面的东西,这里体现了springboot的“约定大于配置”。
    在这里插入图片描述
    4、在包controller下新建HelloCotroller.java
    在这里插入图片描述
    其中声明一个接口,无需配置,体现了springboot自动装配原理,省去繁琐的配置,其原理见图。
    5、helloworld-0.0.1-SNAPSHOT.jar中封装了tomcat和大量.xml配置文件。只需在浏览器直接访问接口即可。
    在这里插入图片描述
    在这里插入图片描述
    无须再次去调用tomcat,再次体现了简化开发的理念

    展开全文
  • cv2.findContours()返回函数详解, findContours()

    万次阅读 多人点赞 2017-06-21 11:53:15
    对于cv2.findContours() 函数,相信很多人都在使用,利用其进行轮廓的寻找,之后利用cnt[num],对num轮廓进行操作,但是该函数返回的三参数具体表示的是什么呢? 下面就进行详细介绍,为了能够使读者更加深入...

    对于cv2.findContours() 函数,相信很多人都在使用,利用其进行轮廓的寻找,之后利用cnt[num],对第num个轮廓进行操作,但是该函数返回的三个参数具体表示的是什么呢?

    下面就进行详细介绍,为了能够使读者更加深入的理解,利用下面的例程进行具体分析。

    准备材料:图片一张(作者手动画图软件绘制的)


    Python程序如下:

    improve cv2
    improve numpy as np
    img=cv2.imread('test.jpg')
    imgray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret,thresh=cv2.thrshold(imgray,127,255,0)
    image,cnts,hierarchy=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    cv2.imshow('imageshow',image)  # 显示返回值image,其实与输入参数的thresh原图没啥区别
    cv2.waitKey()
    print(np.size(cnts))  #   得到该图中总的轮廓数量
    print(cnts[0])   #  打印出第一个轮廓的所有点的坐标, 更改此处的0,为0--(总轮廓数-1),可打印出相应轮廓所有点的坐标
    print(hierarchy) #打印出相应轮廓之间的关系
    img=cv2.drawCountours(img,[cnts[0]],-1,(0,255,0),10)  #标记处编号为0的轮廓
    cv2.imshow('drawimg',img)
    cv2.waitKey()
     
    

    输出结果为:

    5  ###说明总轮廓是为5
    [[[272 421]]  #编号为0的轮廓的一系列坐标值
     [[270 423]]
    ......
    ......
     [[274 421]]
    
    ###各轮廓间关系
    [[[ 1 -1 -1 -1] #轮廓0
    [ 4 0 2 -1] #轮廓1
     [-1 -1 3 1] #轮廓2
    [-1 -1 -1 2] #轮廓3
    [-1 1 -1 -1]]] #轮廓4


    
    
    下面具体解释下hierarchy输出的矩阵参数的意义

    其输出矩矩阵大小为NXM, 其中N为轮廓的个数,M恒等于4,也就是说每一行的4个数,能够表示出轮廓间的相互关系,那么具体是怎样表示的呢

    第一个数:表示同一级轮廓的下个轮廓的编号,如果这一级轮廓没有下一个轮廓,一般是这一级轮廓的最后一个的时候,则为-1

    第二个数:表示同一级轮廓的上个轮廓的编号,如果这一级轮廓没有上一个轮廓,一般是这一级轮廓的第一个的时候,则为-1

    第三个数:表示该轮廓包含的下一级轮廓的第一个的编号,假如没有,则为-1

    第四个数: 表示该轮廓的上一级轮廓的编号,假如没有上一级,则为-1

    废话不多说,直接上图,标记处各轮廓间层次关系及编号


    具体分析证明:

    [[[ 1 -1 -1 -1]   #轮廓0   
      [ 4  0  2 -1]   #轮廓1
     [-1 -1  3  1]   #轮廓2
      [-1 -1 -1  2]   #轮廓3
      [-1  1 -1 -1]]] #轮廓4
    轮廓0,它的同级下一个的编号为1,第一个参数为1; 因为这一级别的第一个,第二个参数-1;因为不包含子轮廓,所以第三个参数-1;因为处于第一级,其不属于任何别的级别,所以第四个参数为-1

    轮廓1,它的同级下一个的编号为4,第一个参数为4; 因为这一级别的上一个的编号为0,第二个参数0;因为包含子轮廓,且子轮廓的第一个编号(当然只包含一个,多个也是同样的道理)为2,所以第三个参数2;因为处于第一级,其不属于任何别的级别,所以第四个参数为-1

    轮廓2,它是第二级的最后一个(因为只有一个),所以第一个参数为-1; 它也是第二级的第一个,所以第二个参数为-1;因为包含子轮廓,且子轮廓的第一个编号(当然只包含一个,多个也是同样的道理)为3,所以第三个参数3; 因为处于第二级,其属于上一级的轮廓1,所以第四个参数为1

    依次类推 。。。。。轮廓3

    轮廓4  它是同一级(第一级)的最后一个,因此第一个参数为-1;因为这一级别的上一个的编号为1,第二个参数1;因为不包含子轮廓,所以第三个参数-1;因为处于第一级,其不属于任何别的级别,所以第四个参数为-1


    用最通俗的方法,详细大家一定能理解吧! 点击


    展开全文
  • 猴子吃桃问题:猴子第一天摘下若干桃子,当即吃了一半。...从前往后推导,第一天采了 x 桃子,x/2 - 1 = 剩下的桃子。 所以 ( 剩下的桃子 + 1 ) * 2 = 等于 x ,此处的 x 为前一天的桃子。 #inclu...

     猴子吃桃问题:猴子第一天摘下若干桃子,当即吃了一半。还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。此后的每一次都吃前一天所剩桃子的一半,再多吃一个。第十天早上发现,只剩一个桃子。

    方法一:

    从前往后推导,第一天采了 x 个桃子,x/2 - 1 = 剩下的桃子。

    所以 ( 剩下的桃子 + 1 ) * 2 = 等于 x ,此处的 x 为前一天的桃子。

    #include <stdio.h>
    int main()
    {
    	int i = 1, j, sum = 0 ;
    	for (j = 1 ; j <= 9 ;j++)
    	{
    		sum = sum + (i+1)*2 ;
    	}
    	sum = sum + 1 ;
    	
    	printf("猴子一共采了%d个桃子\n",sum) ;
    
    	return 0;
    	
    }
    /*
    第十天:1
    第九天:4
    第八天:10
    第七天:22
    */
    

    展开全文
  • C++输出全排列递归算法详细解释

    万次阅读 多人点赞 2015-05-06 23:42:48
    Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀ri得到的排列。 (1)当n=1时,Perm(R)=(r),其中r是集合R中唯一的元素; (2)当n>1时,Perm(R)可由(r1)+Perm(R1),(r2)+Perm(R2),…,(rn)+Perm(Rn)构成。 ...
  • #include <stdio.h>int main() { int i = 1; int sum = 0; int dl = 1; //十天天剩下1个桃 int df = 0; //前天的桃子数 for(i = 1; i ; i ++) { df = 2*(dl + 1); dl = df; }
  • '''猴子吃桃问题:猴子第一天摘下若干桃子,当即吃了一半,还不瘾,又多吃了一 第二天早上又将剩下的桃子吃掉一半,又多吃了一。 以后每天早上都吃了前一天剩下的一半零一。到第10天早上想再吃时,见只剩下...
  • 数据库(第一范式,第二范式,第三范式)

    万次阅读 多人点赞 2015-04-21 20:49:11
    数据库(第一范式,第二范式,第三范式) 范式:英文名称是 Normal Form,它是英国人 E.F.Codd(关系数据库的老祖宗)在上世纪70年代提出关系数据库模型后总结出来的,范式是关系数据库理论的基础,也是我们在设计...
  • 49. 丑数 题目描述 把只包含因子 2、3 和 5 的数称作...习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 丑数。 解题思路 public int GetUglyNumber_Solution(int N) { if (N <= 6) return N; ...
  • 简单了解第一,二,三范式什么是范式第一范式第二范式第三范式 什么是范式 范式:范式是符合某一种级别的关系模式的集合,表示一关系内部属性之间的联系何合理化程度 粗略理解:就是一张数据表的表结构所符合的...
  • 格雷码的实现

    万次阅读 多人点赞 2012-09-24 12:07:56
    问题:产生n位元的所有格雷码。 格雷码(Gray Code)是一数列集合,每数使用二进位来表示,假设使用n位元来表示每数字,任两数之间只有一...假设原始的值从0开始,格雷码产生的规律是:第一步,改变最右边的
  • Pytorch详解NLLLoss和CrossEntropyLoss

    万次阅读 多人点赞 2019-03-11 23:57:10
    在图片单标签分类时,输入m张图片,输出一个m*N的Tensor,其中N是分类数。比如输入3张图片,分三类,最后的输出是一个3*3的Tensor,举例子: 123行分别是123张图片的结果,假设123列分别是猫、狗和猪的...
  • #include int main()  {  int i,n,a,b,c;  scanf("%d",&n); if(n>=1 && n {  for ( i=0;i  {  scanf("%d%d",&a,&b);  c=a+b;  printf("d\n",c); }   }  return 0; }
  • /* * Copyright (c) 2012, 烟台大学计算机学院 * All rights reserved. * 作 者: 刘同宾 * 完成日期:2012 年 12 月 05 日 * 版 本 号:v1.0 * * 输入描述: * 问题描述:有字符串,包含n字符
  • 参考链接: ...总结: 范式的含义: 符合某种级别的关系模式的集合。表示一关系内部的各属性之间联系的合理化程度。可以理解为:数据表的表结构...符合第一范式的关系,每属性都不可以再分割。 但是如果仅仅满足
  • oracle 中某月的第一天,最后一天,前一月的第一天,最后一天,类似方法 在Oracle 数据库中取某个月份的第一天和最后一天的方法 取当前日期前一月的第一天的日期 SELECT last_day(add_months(SYSDATE, ...
  • Java实现 LeetCode 1227 飞机座位分配概率

    万次阅读 多人点赞 2020-02-23 09:27:28
    第一位乘客的票丢了,他随便选了一座位坐下。 剩下的乘客将会: 如果他们自己的座位还空着,就坐到自己的座位上, 当他们自己的座位被占用时,随机选择其他座位 第 n 位乘客坐在自己的座位上的概率是多少? 示例 1...
  • C#两数组合并

    万次阅读 2013-12-17 20:07:28
    //第一个数组 int[] i = {1,2,3}; //第二数组 int[] j = {4,5,6}; //声明你要的数组,长度是第一和第二数组长度的和 int[] x = new int[i.Length+ j.Length]; //将第一个数组的值放到你要的数组开头 i.Copy...
  • IDEA一个项目引用另一个项目

    万次阅读 2019-05-24 10:23:58
    1、导入第一个项目,如下图所示: 2、选择导入第一个项目文件夹,如下图所示: 3、导入第一个Maven项目,如下图所示: 4、导入第一个项目设置,默认设置即可,如下图所示: 5、导入第一个项目,如下图所...
  • 这里问问题,如果是以前,还没有指针呢?前辈先人又是如何来实现单链表的呢? 答:机智的前人想出了这样的方法:**数组下标 + 游标** 的方式来实现单链表, 也就是本节要讲解的静态链表!当然你也可以直接跳过本节...
1 2 3 4 5 ... 20
收藏数 10,016,065
精华内容 4,006,426
关键字:

第一个