美文网首页
JAVASE学习笔记

JAVASE学习笔记

作者: 佐半边的翅膀 | 来源:发表于2019-03-26 21:03 被阅读0次

    1.导包

    ctrl+shift+字母o ,回车

    2.保留指定位数的小数

    (1)保留几位小数  “%.nf” ,n保留的小数位数 ,保留2位小数 "%.2f"

    (2)//借助 java.text.DecimalFormat类 ,格式化数字  保留几位小数,就留几个“0”

            DecimalFormat df = new DecimalFormat(".000");

            //借助df格式化pi    str="3.142" ,是一个字符串   

            //format()方法,将 double 按指定格式 输出成字符串

            String str = df.format(pi);

            System.out.println("str="+str);

            // 将字符串 转换成 double类型的浮点数

            double result = Double.parseDouble(str);

    3.Math.round(a)

    4.多重if

    范围打乱,顺序对结果又影响 ,要么从大往小写,要么从小往大写,对于顺序打乱,解决办法:加上逻辑限制条件。

    5.随机数

    [min, max) 整数   (int)(Math.random()*(max-min)+min);

    Random  ra  = new Random();

    [min, max)    整数:   ra.nextInt(max-min)+min

    6.冒泡排序

        int[] arr = {5,4,3,2,1};

                //声明中间变量,用于交换

                int temp=0;

                //控制轮数

                for(int k=0;k<arr.length-1;k++){

                    System.out.println("\n\n第"+(k+1)+"轮开始时:"+Arrays.toString(arr));

                    //内层控制的每一轮比较的次数,要保证比较次数在减少, 取值,4,3,2,1

                    for(int i=0;i<arr.length-1-k;i++){

                        //如果前一个比后一个大需要交换

                        if(arr[i]>arr[i+1]){

                            temp=arr[i];

                            arr[i]= arr[i+1];

                            arr[i+1]=temp;

                        }

                        System.out.println("第"+(k+1)+"轮第"+(i+1)+"次比较:"+Arrays.toString(arr));

                    }

                    System.out.println("第"+(k+1)+"轮结束后:"+Arrays.toString(arr)+"\n\n");

                }

    选择排序

        public static void main(String[] args) {

         int [] arr=new int [] {45,65,32,33,12,1};

         for(int i=0;i<arr.length-1;i++) {

            int index=i;

            for(int j=i+1;j<arr.length;j++) {

                if(arr[j]<arr[index]) {

                    int temp=arr[index];

                    arr[index]=arr[j];

                    arr[j]=temp;

                }

            }

         }

         System.out.println(Arrays.toString(arr));

        }

    快速排序

        public static void main(String[] args) {

            int[] arr = new int[] { 1, 2, 4, 5, 7, 4, 5, 3, 9, 0 };

         //System.out.println(Arrays.toString(arr));

            quickSort(arr);

            System.out.println(Arrays.toString(arr));

        }

        private static void quickSort(int[] arr) {

            if (arr.length > 0) {

                quickSort(arr, 0, arr.length - 1);

            }

        }

        private static void quickSort(int[] arr, int low, int high) {

            // 1.递归算法出口

            if (low > high) { // 放在key之前,防止下标越界

                return;

            }

            // 2. 存

            int i = low;

            int j = high;

            // key

            int key = arr[i];

            // 3.完成一趟排序

            while (i < j) {

                // 从右往左找到第一个小于key的数

                while (i < j && arr[j] > key) {

                    j--;

                }

                // 从左往右找第一个大于key的数

                while (i < j && arr[i] <= key) {

                    i++;

                }

                // 交换

                if (i < j) {

                    int temp = arr[i];

                    arr[i] = arr[j];

                    arr[j] = temp;

                }

            }

            // 当i==j时,调整key的位置

            int p = arr[i];

            arr[i] = arr[low];

            arr[low] = p;

            // 对key左边的数快排

            quickSort(arr, low, i - 1);

            // 对key右边的数快排

            quickSort(arr, i + 1, high);

        }

    http://developer.51cto.com/art/201403/430986.htm

    7.数组拷贝

           (1) int[] arr = {10,12,13,14,15};

           int[] brr = arr.clone();  //clone()

           (2)System.arraycopy(要拷贝的原始数组arr,原始数组中元素的起始下标从0开始srcfrom,目标数组brr,目标数组中起始位置destfrom,要拷贝的元素个数num) 

         * 需要满足的条件:

         * srcfrom>=0

         * destfrom>=0

         * srcfrom +num <= arr.length

         * destfrom+num<=brr.length

           (3)copyOf(int[] original, int newLength) 

          * copyOf(要拷贝的原始数组,要拷贝的元素个数num)

           (4)copyOfRange(要拷贝的原始数组,拷贝的起始索引从0开始from ,结束索引to)

          * 拷贝的索引范围:[from, to) 左闭右开 ,拷贝的元素个数 to-from

    8 .  字符串转数组

    (1)使用Java split() 方法

        split() 方法根据匹配给定的正则表达式来拆分字符串。

        注意: . 、 | 和 * 等转义字符,必须得加 \\。多个分隔符,可以用 | 作为连字符。    

        String str = "0,1,2,3,4,5";

        String[] arr = str.split(","); // 用,分割

        System.out.println(Arrays.toString(arr));

    ` int a=Integer.parseInt(arr[]) String类型转int型`

    9。数组转字符串

         String str2 = ArrayUtils.toString(arr, ","); // 数组转字符串(逗号分隔,首尾加大括号)

        String str4 = StringUtils.join(arr, ","); //StringUtils的join方法

    10.charAt(2) 取出指定位置的字符

    lastIndexOf()最后一个索引的位置

    endWith() /startsWith("aa")判断是否以aa开头,返回布尔值

    trim() 去掉两边空格

    11。 int---》String  Int型转字符串型

        String s1 = String.valueOf(num);

    String---》 int   字符串型转int型

        int num2 = Integer.parseInt(s2);

        double num3 =Double.parseDouble(s2);

    12 . 截取字符串

    //取出10  subString(from,to)  [from,to) 左闭右开

           String num1Str = s.substring(s.indexOf("从")+1, s.indexOf("数到"));

           String num1Str = s.substring(s.indexOf("从")+1, s.indexOf("数到"));

    13.//静态变量  ,可以类名.属性名 ,也可以对象名.属性名

    //非static变量/普通变量  ,只能通过对象名.属性名来访问

    //静态方法ceshi4(),只能调用静态方法ceshi1(),不能调用普通方法ceshi2();

    14.//1个类:静态成员变量,实例成员变量(普通的成员变量),静态代码块,普通代码块 ,构造

    //执行顺序: 静态成员----》静态代码块 ----》实例成员----》普通代码块----》构造

    /

    //1.父类的静态成员

    //   父类的静态代码块

    // 2.子类的静态成员

    // 子类的静态代码块

    // 3.父类的实例成员

    //   父类的普通代码块(非static代码块)

    //4.父类的构造Father()

    // 5.子类的实例成员

    //   子类的普通代码块(非static代码块)

    // 6.子类的构造Son()

    15.this() :表示调用本类的无参构造

     * this(属性名):表示调用本类的带参构造  ,this(属性1,属性2....) 可以构造的调用,需要放到第1句

    16.基本数据类型---》包装类

    //直接装箱  int类型的变量,直接赋值给Integer类型的变量 num2

            Integer num2 = num1;

        //int--->Integer

        int num1=10;

        Integer num2 = new Integer(num1);

        Integer num7 = Integer.valueOf(num1);

        //String--->Integer

            String s="10"; 

            Integer num3 = new Integer(s);    

            Integer num8 = Integer.valueOf(s);

    17.包装类----》基本数据类型

    //直接拆箱   Integer类型变量num2,直接赋值给int类型的变量num3

        int num3 = num2;

         //Integer---》int

         Integer num1 = new Integer(10);

         int num2 = num1.intValue();

        //String--->int

            String s="123";

            int num5=Integer.parseInt(s);

    **封装**(降低耦合

    将类的信息隐藏在类的内部,对外提供公有的方法,实现对该成员属性的存取操作

    封装的好处:隐藏类的实现细节,让使用者使用提供的方法来访问数据,可以方便的加入存取操作,限制不合理的操作

    **继承**

    一个类可以由其他类派生,子类继承父类特征和方法。

    只支持单继承

    子类可以继承父类Public和protected修饰的属性和方法

    在同一个包中可以继承除private以外的所有修饰的属性和方法

    子类无法继承父类的构造方法

    子类不能抛出 比父类更多的异常

    父类的静态方法不能被子类覆盖为非静态方法,同样父类的非静态方法不能被子类覆盖为静态方法

    **继承下构造方法的执行过程**

    注意:加载顺序:启动类(java虚拟机启动时,被标明为启动类的类)的static block 最先加载(父类静态成员,静态代码块----子类静态成员,静态代码块---父类实例成员,代码块-----父类构造函数------子类实例成员,代码块---子类构造函数)

    *重写*

    方法名相同 参数列表相同 返回值类型相同或是其子类 不能缩小被重写方法的访问权限

    **final关键字**

    final 修饰成员变量,则成为实例常量

    final修饰类,类不能被继承

    final修饰成员方法,则该方法不能被子类重写。

    **super关键字**

    在子类构造方法中调用且必须为第一句(调用父类的带参构造方法)super(属性1,属性2..)

    使用super关键字 直接调用父类的方法

    **多态**

    同一引用类型,使用不同的实例而执行不同的操作。

    举例说明多态:动物类父类  有“叫”的这样一个动作,  继承它的都是普通类猫 狗等  每个子类叫的方法实现都不一样  ,现在要实现各种动物的叫声  ,如何动态实现  写一个方法把父类做形参传进去

        主人类始终要修改,只要新增宠物子类,主人类就需要添加具体动物看病的方式,子类写不尽的,Host类始终需要修改,不合理 

         *  解决办法:多态来来解决

         *  1.创建Cat子类,extends Pet父类,Cat类中是Cat特有的属性

         *  2. Host类,不需要修改的,只提供一个空方法,带宠物看病

         * public void cure(Pet pet){

         *       pet.toHospital();

         * }

         *  3.父类Pet中,提供1个空方法 ,public  void toHospital(){}

         *  4.每个子类,看病的方式不同,就把各自子类看病的方式,放到各自的子类中去完成

         *   每个子类,去重写父类的toHospital()方法

         *   

         *  5.调用的时候,父类引用,指向子类对象,执行的是各自不同的子类对应的操作

    **abstract抽象**

    抽象方法(只有方法声明,没有方法实现)*abstract void fun();*

    抽象类 :包含抽象方法的类是抽象类;

    抽象类不能实例化,可以实例化子类来实现父类的方法

      * 子类必须重写所有抽象方法才能实例化,否则子类还是一个抽象类*

    抽象类有构造方法,可以被本类其他构造方法调用,如果不是private修饰,可以被其子类中的构造方法调用。

    abstract修饰类和方法,不能修饰属性和构造方法

    **接口**

    接口中不能定义变量  可以定义常量  自动用public static final修饰  全局静态常量

    接口中所有方法都是抽象方法 自动public abstract 修饰

    接口不能实例化 不能有构造方法

    接口的实现类必须要实现接口的全部方法,除非这个类是抽象类

    一个接口不能实现另一个接口,但可以继承多个其他接口

    **Date类**

        Date  date=new  Date();

        System.out.println("date="+date);

    3个子类构造

        //java.sql.Date  ,默认格式“yyyy-MM-dd"

            Date date = new Date(System.currentTimeMillis());

            // date=2019-03-25

        //java.sql.Time   ,默认格式:“HH:mm:ss”

            Time date2 = new Time(System.currentTimeMillis());

            //date2=11:53:09 

        //java.sql.Timestamp  默认格式“ yyyy-MM-dd HH:mm:ss.SSS" 精确到毫秒

            //1s = 1000ms ,毫秒的范围[000,999]

            Timestamp date3 = new Timestamp(System.currentTimeMillis());

            //date3=2019-03-25 11:54:20.097

    DateFormat:日期格式化类。抽象类无法使用

    SimpleDateFormat是其子类可以使用

    (1 格式化java.util.Date对象  ,让其按指定的格式来显式

         将Date对象 ----format()方法-----》字符串String 来显式

    (2 public static String getStrFromDate(Date date,String pattern){

        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

        Date date = new Date();    

        String result = df.format(date);

        System.out.println("date="+date+",格式化后="+DateUtil.getStrFromDate(date, "yyyy-MM-dd"));

    **Calendar类**

    Calendar:抽象类,不能直接实例化 ,可以使用其子类 GregorianCalendar

        Calendar cal = new GregorianCalendar();

    获取时间

        Calendar cal = Calendar.getInstance();

    设置时间

    Calendar cal = Calendar.getInstance();

        // 如果想设置为某个日期,可以一次设置年月日时分秒,由于月份下标从0开始赋值月份要-1

        // cal.set(year, month, date, hourOfDay, minute, second);

           cal.set(2018, 1, 15, 23, 59, 59);

    **异常**

    *  error与exceptionde 区别*

    error表示不可处理的异常  通常为内存溢出  jvm崩溃等。

    exception表示需要捕捉或者处理的异常

    *  throw和throws的区别*

    throw在程序中抛出异常,出现在方法体内,如果执行则一定抛出某种异常对象,且只能抛出一个。

    throws表示抛出异常的声明,出现在方法头,声明抛出异常的一种可能性,throws后面可以跟多个异常类。

    * try catch finally*

    try块必须   catch  finally必须出现一个

    finally一定会执行,除非System.exit(n)

    try块中有return语句,finally语句也会执行,执行return语句会记下返回值,待finally执行结束后,再向调用者返回其值。

    *常见的5种RunTimeException*

    NullPointerException,空指针异常。

    NumberFormatException,数据格式转换错误。

    ClassCastException,强制类型转换异常。

    IndexOutOfBoundsException,越界异常。

    ArithmeticException 算术异常。

    除RuntimeException及其子类其他所有的异常都是检查型异常(可查异常)

    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

    使用自定义异常的步骤:

    1.定义异常类(继承 Throwable 类,Exception类 RuntimeException)。

    2.编写构造方法,继承父类的实现。

    3.实例化自定义异常对象。

    4.使用throw 抛出。

    **String、StringBuffer与StringBuilder之间的区别**

    1.String含义为引用数据类型,是字符串常量.是不可变的对象,(显然线程安全)在每次对string类型进行改变的时候其实都等同与生成了一个新的String对象.然后指针指向新的String对象,所以经常改变内容的字符串最好不使用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了之后.JVM的垃圾回收(GC)就会开始工作,对系统的性能会产生影响

    2.StringBuffer  线程安全的可变字符序列:对StringBuffer对象本身进行操作,而不是生成新的对象.所所以在改变对象引用条件下,一般推荐使用StringBuffer.同时主要是使用append和insert方法,

    3.StringBuilder 线程不安全的可变字符序列.提供一个与StringBuffer兼容的API,但不同步.设计作为StringBuffer的一个简易替换,用在字符缓冲区被单个线程使用的时候.效率比StringBuffer更快

    区别:

    a.执行速度:StringBuilder > StringBuffer > String

    b.线程安全:StringBuffer线程安全.StringBuilder线程不安全

    c.String适用与少量字符串操作

    StringBuilder适用单线程下在字符缓冲区下进行大量操作的情况

    StringBuffer使用多线程下在字符缓冲区进行大量操作的情况

    **集合**

        Collection 接口存储一组不唯一,无序的对象。

        List接口存储一组不唯一,有序,可重复的对象。ArrayList、LinkedList和Vector(淘汰)是主要的实现类

        Set接口存储唯一,无序的对象。HashSet和TreeSet是主要的实现类。

        Map接口存储一组键—值对象,提供Key—Value的映射。其中key列就是一个集合,key不能重复,但是value可以重复。 HashMap、TreeMap和Hashtable是Map的主要实现类。

    **Vector扩容机制**

    vector 默认的扩容机制是按照容器现有容量的一倍进行增长。由于 Vector 容器分配的是一块连续的内存空间, 每次容器的增长并不是在原有连续的内容空间后进行简单的叠加, 而是重新申请一块更大的新内存, 并把现有容器中的元素逐个复制过去, 然后销毁原有内存。

    举例:vector 初始化时申请的空间大小为 6 , 存入了 6 个元素, 当向 vector 中插入第 7 个元素“ 6” 时, vector 会利用自己的扩容机制重新申请空间, 数据存放结构如图 1 所示(_First 指向使用空间的头部,_Last 指向使用空间大小(size)的尾部,_End 指向使用空间容量(capacity)的尾部)。

    ![](https://i.imgur.com/oHemGds.png)

    **ArrayList底层实现原理**

    ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是基于数组的

    ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。

    ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。

    底层使用数组实现

    该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作的代价很高。

    采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险

    remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC

    (补充)

    **LinkedList底层实现原理**

    LinkedList是List接口的双向链表非同步实现,并允许包括null在内的所有元素。

    底层的数据结构是基于双向链表的,该数据结构我们称为节点

    双向链表节点对应的类Node的实例,Node中包含成员变量:prev,next,item。其中,prev是该节点的上一个节点,next是该节点的下一个节点,item是该节点所包含的值。

    它的查找是分两半查找,先判断index是在链表的哪一半,然后再去对应区域查找,这样最多只要遍历链表的一半节点即可找到

    **HashMap底层实现原理(jdk1.8)**

    从结构上讲,hashmap是位桶(Node数组)+链表+红黑树实现的,链表是为了解决hash冲突的,当链表长度超过阈值(8)时,将链表转换为红黑树,大大减少查找时间。

    主干是Node数组,包含一个键值对,实现了Map.entry接口,它的初始容量为16, 当链表数组的容量超过初始容量的0.75时,再散列将链表数组扩大2倍,把原链表数组的搬移到新的数组中

    *如何getValue*

    get方法时获取key的Hash值,通过计算hash&(n-1)得到在链表数组中的位置,判断计算的key与参数key是否相等,不等集遍历后面的链表找到相同的key值返回对应的Value值即可。

    *如何put<K,V>*

    判断键值对数组tab[]是否为空或为null,否则以默认大小resize();

    根据键值key计算hash值得到插入的数组索引i,如果tab[i]==null,直接新建节点添加

    判断当前数组中处理hash冲突的方式为链表还是红黑树(check第一个节点类型即可),分别处理

    *HasMap的扩容机制resize();*

    构造hash表时,如果不指明初始大小,默认大小为16(即Node数组大小16),如果Node[]数组中的元素达到(填充比*Node.length)重新调整HashMap大小 变为原来2倍大小,扩容很耗时

    **Hashtable实现原理**

    不允许出现null值null键,线程同步,et/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化。      线程安全。

    **ConcurrentHashMap实现原理**

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现。

    ConcurrentHashMap采用了非常精妙的"分段锁"策略,ConcurrentHashMap的主干是个Segment数组。

         final Segment<K,V>[] segments;

    ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示

         tatic final class HashEntry<K,V> {  

         final K key;  

         final int hash;  

         volatile V value;  

         final HashEntry<K,V> next;  

        } 

    可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

    **IO操作**

    * 1.删除文件及其下面的子目录,子文件,不包括最外层的父文件夹

            public static void delAllFileExceptOuter(File dir){

                if(dir.exists() && dir.isDirectory()){

                    //获取子文件列表

                    File[] arr = dir.listFiles();

                    //遍历

                    for(File f:arr){

                        //递归调用自身方法,继续内层的删除

                        delAllFileExceptOuter(f);

                        f.delete();

                    }

                }

            }

    * 2.删除文件及其下面的子目录,子文件,包括最外层的父文件夹

               public static void delAllFileIncludeOuter(File dir){

                if(dir.exists() && dir.isDirectory()){

                    //获取子文件列表

                    File[] arr = dir.listFiles();

                    //遍历

                    for(File f:arr){

                        //递归调用自身方法,继续内层的删除

                        delAllFileIncludeOuter(f);

                        f.delete();

                    }

                    dir.delete();

                }

            }

    * 3.拷贝所有的子文件夹,不拷贝子文件

            public static void copyAllDir(File src,File dest){

                if(src.isDirectory()){

                    if(!dest.exists()){

                        dest.mkdirs();

                    }

                    //获取src的子文件列表 

                    File[] arr = src.listFiles();

                    //遍历

                    for(File f:arr){

                        //构建新的子文件对象  subSrc=new File("E:/others/", c);

                        // subDest = new File("D:/others", c);

                        File subSrc = new File(src,f.getName());

                        File subDest = new File(dest,f.getName());

                        System.out.println("###subSrc="+subSrc+",subDest="+subDest);

                        //递归调用自身方法

                        copyAllDir(subSrc, subDest);

                    }

                }

            }

    * 4.拷贝单个子文件

            public static void copySingleFile(File src,File dest){

                if(src.isDirectory()){

                    System.out.println("只能拷贝文件!");

                    return;

                }

            //

            if(dest.isDirectory()){

                System.out.println("要拷贝的文件和父目录中的文件夹重名,不能拷贝!");

                return;

            }

            //声明输入流对象,输出流对象

            FileInputStream fis=  null;

            FileOutputStream fos = null;

            try {

                //建立输入流和源文件之间的联系 

                fis = new FileInputStream(src);

                //建立输出流和目标文件之间的联系

                fos = new FileOutputStream(dest);

                //声明byte[]数组,用来存储读取的内容  ,数组的容量 n:整数值,任意定,项目中一般用1024

                byte[] buffer = new byte[3]; 

                //声明int变量,用来存储read()方法的返回值,实际上存储就是实际读取到的字节个数 

                int len=fis.read(buffer);

                while(len!=-1){

                    //读取的内容报错在byte数组中  ,需要转换成String,才能打印输出

    //            String data = new String(buffer,0,len);

    //                System.out.println("len="+len+"data="+data);

                    //读多少,写多少出去

                    fos.write(buffer, 0, len);

                    //刷新

                    fos.flush();

                    //接着读

                    len=fis.read(buffer);

                }

            } catch (FileNotFoundException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            } finally{

                //释放资源 、关闭流 

                //先打开的后关闭,先创建的后关闭

                if(null!=fos){

                    try {

                        fos.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

                if(null!=fis){

                    try {

                        fis.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

            }

        }

    * 5.拷贝所有的子文件,子目录

            public static void copyAllFiles(File src,File dest){

            if(src.getParent()==null && dest.getParent()==null){

                //证明相对路径下,同级之间的拷贝,允许通过

            }else if((src.getParent()==null && dest.getParent()!=null) && (dest.getAbsolutePath().contains(src.getAbsolutePath()))){

                System.out.println("***父目录不能拷贝到子目录中!");

                return;

            }else if(dest.getAbsolutePath().contains(src.getAbsolutePath()) &&  !src.getParent().equals(dest.getParent())){

                //src的parent()和dest的parent()不同

                System.out.println("父目录不能拷贝到子目录中!");

                return;

            }

            if(src.isDirectory()){//如果是目录,拷贝对应的目录

                if(!dest.exists()){

                    dest.mkdirs();

                }

                //获取src的子文件列表 

                File[] arr = src.listFiles();

                //遍历

                for(File f:arr){

                    //构建新的子文件对象 

                    File subDest = new File(dest,f.getName());

                    //递归调用自身方法

                    copyAllFiles(f, subDest);

                }

            }else if(src.isFile()){ //如果是文件,拷贝文件

                copySingleFile(src, dest);

            }

        }

    **IO流**

            * 面试题: 字符串和字节数组,字符串和字符数组之间的相互转换? 

             *  String  s---->byte[]  buffer 

             *  String s="asd";

             *  byte[] buffer=new byte[1024];

             *   buffer = s.getBytes();  

             *  

             *   byte[] buffer ---->String s  

             *s = new String(buffer); 

             *s = new String(buffer,int from, int len);  

             *from:buffer数组的起始索引 

             *len:要转换的字节数 (有几个字节)

             *

             *  

             *  String s ---->char[] buffer 

             *buffer = s.toCharArray();

             *

             *   char[] buffer----->String s  

             *  s = new String(buffer);  

             *  

             *  s= new String(buffer,int from,int len); 

             *  from:buffer字符数组的起始索引

             *  len:要转换的字符个数 (与几个字符)

    **IO流文件流FileInputStream/FileOutstream**

           声明输入输入流输出流对象

          FileInputStream fis=null;

          FileOutputStream fos=null;

          建立输入流,输出流与源文件的联系

          fis=new FileInputStream(" ");

          fos=new FileOutputStream(" ");

           声明byte[]字节数组,用于存储读取的内容

           byte[] buffer=new byte[1024];

           int len=0;

           while((len=fis.read(buffer))!=-1){

           String data=new String(buffer,0,len);

           syso(data);

           len=fis.read(buffer,0,buffer.length);

            fos.write(buffer,0,len);

            fos.flush();

           }

    拷贝

               //声明输入流对象,输出流对象

            FileReader fr = null;

            FileWriter fw = null;

            try {

                //建立输入流和源文件之间的联系

                fr = new FileReader("E:/others/ceshi.txt");

                //建立输出流和目标文件之间的联系

                fw = new FileWriter("D:/test/ceshi.txt");

                char[] buffer = new char[4];

                //len实际读取的字符数

                int len=0;

                while((len=fr.read(buffer))!=-1){

             //    fw.write(buffer, 0, len);

                    //拷贝的时候,一定要带上偏移量

                    String s = new String(buffer,0,len);

                    fw.write(s);

                    fw.flush();

                }

    **缓冲流BufferInputStream/BufferOuputStream/BUfferReader/bufferWriter**

         //声明输入流对象,输出流对象

            BufferedReader br = null;

            BufferedWriter bw = null;

            try {

                //建立输入流和源文件之间的联系

                br = new BufferedReader(new FileReader("E:/others/ceshi.txt"));

                //建立输出流和目标文件之间的联系

                bw = new BufferedWriter(new FileWriter("D:/test/ceshi.txt"));

                //声明String变量,用于存储读取的内容 

                String data = null;

                while((data=br.readLine())!=null){

                    bw.write(data);

                    //刷新

                    bw.flush();

                    //写一行,换一行

                    bw.newLine();

                }

            }

    复制图片

        //声明输入流,输出流对象

            BufferedInputStream bis = null;

            BufferedOutputStream bos = null;

            try {

                //建立输入流和源文件之间的联系 

                bis = new BufferedInputStream(new FileInputStream(new File("E:/others/cat.png")));

                //建立输出流和目标文之间的联系

                bos = new BufferedOutputStream(new FileOutputStream(new File("D:/test/cat.png")));

                //声明byte[]数组

                byte[] buffer = new byte[1024];

                //声明int变量

                int len=0;

                while((len=bis.read(buffer))!=-1){

                    String s = new String(buffer,0,len);

                    System.out.println("len="+len+",s="+s);

                    bos.write(buffer, 0, len);

                    bos.flush();

                }

    **乱码原因**

     * 1.两边的编码方式不一致

     * 2.保存的不完整,数据有丢失

     * InputStreamReader,提供构造,可以传入编码方式

     * InputStreamReader(InputStream in, String charsetName)

              创建使用指定字符集的 InputStreamReader。

     * ANSI ----->程序中gbk

     * 

     * UTF-8----->程序中UTF-8

     * 

     * 乱码因为:ceshi.txt ANSI  ,本地程序中UTF-8,不一致导致的。

     * 

     *    解决办法1:ceshi.txt 右击 另存为 UTF-8

     *    

     *    解决办法2:用InputStreamReader流

        //声明输入流对象

            InputStreamReader isr = null;

            try {

                //建立输入流和源文件之间的联系

                isr = new InputStreamReader(new FileInputStream("E:/others/ceshi.txt"),"gbk");

                //声明char[]数组,用于存储读取的内容

                char[] buffer = new char[4];

                //声明int变量,用来存储实际读取的字符数

                int len = 0;

                while(-1 !=(len=isr.read(buffer))){

                    String s = new String(buffer,0,len);

                    System.out.println(s);

                }

    ** FileInputStream与FileReader区别**:

    FileInputStream是字节流,FileReader是字符流,用字节流读取中文的时候,可能会出现乱码,而用字符流则不会出现乱码,而且用字符流读取的速度比字节流要快;

    **FileInputStream与BufferedInputStream区别**

    FileInputStream是字节流,BufferedInputStream是字节缓冲流,使用BufferedInputStream读资源比FileInputStream读取资源的效率高(BufferedInputStream的read方法会读取尽可能多的字节,执行read时先从缓冲区读取,当缓冲区数据读完时再把缓冲区填满。),因此,当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高,且FileInputStream对象的read方法会出现阻塞;BufferedInputStream的默认缓冲区大小是8192字节。当每次读取数据量接近或远超这个值时,两者效率就没有明显差别了。

    **ObjectOutputStream/ObjectInputStream**

    java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

    java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    **序列化**

    只有实现了Serializable(si瑞尔奈zi包)和Externalizable接口的类的对象才能被序列化。

    transient修饰属性避免序列化

    序列化是指将对象转换成字节序列的过程称为对象的序列化,反序列化则是将字节序列恢复为对象的过程

    对象的序列化通常有两种用途

    1、把对象的字节序列永久的保存到硬盘上,通常存放到一个文件中

    2、在网络上传送对象的序列化

    *序列化步骤*

    1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;

    2) 通过对象输出流的writeObject()方法写对象。

        //声明输出流对象

            ObjectOutputStream oos = null;

            try {

                //建立输出流和目标文件之间的联系  oos需要包装其他的底层流

                oos = new ObjectOutputStream(new FileOutputStream(filePath));

                //调用write()方法写出 

                List<Student> list=new ArrayList<Student>();

                 list.add(new Student("张伟",18));

                 list.add(new Student("张伟1",28));

                 list.add(new Student("张伟2",38));

                //刷新

                 oos.writeObject(list); 

                oos.flush();

            }

    *反序列化步骤*

    1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

    2) 通过对象输入流的readObject()方法读取对象。

    对象序列化和反序列范例:

        // 声明输入流对象

            ObjectInputStream ois = null;

            try {

                // 建立输入流和源文件之间的联系 ,ois需要包装其他的底层流

                ois = new ObjectInputStream(new FileInputStream(filepath));

                // 通过read()方法读取

                List<Student> list = (List) ois.readObject();

                for (Student temp : list) {

                    System.out.println("取出的学生名:" + temp.getName() + ",年龄:" + temp.getAge() + ",性别:" + temp.getSex());

                }

            }

    *s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D​:​*

    ​字​面​意​思​上​是​序​列​化​的​版​本​号​,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量

        private static final long serialVersionUID

    *显式地定义serialVersionUID有两种用途:*

    1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

    2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

    **RandomAccessFile类**

    RandomAccessFile类的主要功能是完成随机读取功能,可以读取指定位置的内容。

    之前的File类只是针对文件本身进行操作的,而如果要想对文件内容进行操作,则可以使用RandomAccessFile类,此类属于随机读取类,可以随机读取一个文件中指定位置的数据。

           * RandomAccessFile:既可以当输出流负责写出,也可以当输入流负责读取

         * 体现它随机访问的特点  seek() skipBytes()

         * 先将3个用户信息,写入到E:/others/user.txt中

         *  然后读取出来

         *  

         *  写入的时候,构造上 ,mode模式选用:rw,文件不存在,则创建

         *  

         *  读取的时候  mode:r 只读

         *  

         *  1.2个方法,一个方法负责写入

         *  一个方法负责读取

         * @author Administrator

         *

         */

        public class TestRandomAccess01 {

            public static void main(String[] args) {

                String path = "E:/others/user.txt";

                save(path);

        //        readFirst(path);

            }

        /**

         * 顺序 ,1,2,3 

         * RandomAccessFile 当 输入流用

         * @param filePath

         */

        public static void readFirst(String filePath){

            //声明输入流对象

            RandomAccessFile ra= null;

            try {

                //建立输入流和源文件之间的联系

                ra = new RandomAccessFile(new File(filePath), "r");

                byte[] buffer = new byte[8];

                for(int i=0;i<buffer.length;i++){

                    buffer[i] = ra.readByte();

                }

                //读取整数值

                int age = ra.readInt();

                System.out.println("第一个人信息:"+new String(buffer)+"---"+age);

                //读取第2个人  

                for(int i=0;i<buffer.length;i++){

                    buffer[i] = ra.readByte();

                }

                //读取整数值

                age = ra.readInt();

                System.out.println("第二个人信息:"+new String(buffer)+"---"+age);

                //读取第3个人

                for(int i=0;i<buffer.length;i++){

                    buffer[i] = ra.readByte();

                }

                //读取整数值

                age = ra.readInt();

                System.out.println("第三个人信息:"+new String(buffer)+"---"+age);

            } catch (FileNotFoundException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            } finally{

                if(ra!=null){

                    try {

                        ra.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

            }

        }

        /**

         * 存储

         * RandomAccessFile 当输出流用

         * @param filePath

         */

        public static void save(String filePath){

            RandomAccessFile ra = null;

            try {

                //建立输出流和目标文件之间的联系,同时指定模式

                ra = new RandomAccessFile(new File(filePath), "rw");

                //调用write()方法写出 

                ra.writeBytes("zhangsan");

                ra.writeInt(30);

                ra.writeBytes("lisi    ");

                ra.writeInt(31);

                ra.writeBytes("wangwu  ");

                ra.writeInt(32);

            } catch (FileNotFoundException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            } finally{

                if(ra!=null){

                    try {

                        ra.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

            }

        }

    **线程**

    进程:运行的程序,动态。是资源分配的基本单位。 一个进程可拥有多个并行的(concurrent)线程。

    线程:进程中一段代码的执行过程。是执行和调度的基本单位。是进程中执行运算单位的最小单位,是进程内部的一个执行单元。

    **实现多线程两种方式**

    *继承Thread类*

    1.定义子类继承Thread类

    2.子类重写Thread类run方法

    3.创建Thread子类对象,即创建线程对象

    4.调用线程对象的start()方法,启动线程

        public class Rabbit extends Thread {

        private int step = 1;

        // (1)线程类中定义一个标志位

        private boolean isRunning = true;

        public Rabbit() {

        }

        public Rabbit(String name) {

            super(name); // 调用父类Thread的带参构造

        }

        @Override

        public void run() {

            // (2)线程体中使用该标志位

            while (isRunning) {

                System.out.println(Thread.currentThread().getName() + "跑了"

                        + (step++) + "步");

            }

        }

        // 提供一个更改此标志位的方法

        public void stopThread() {

            isRunning=false;

        }

        }

        public static void main(String[] args) {

            //新生状态

            Rabbit ra = new Rabbit();

            ra.setName("兔子");

            //就绪状态

            ra.start();

            System.out.println("A判断兔子线程的是否处于活动状态:"+ra.isAlive());

            //延迟2ms 

            try {

                Thread.sleep(2);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            //终止线程 

            //ra.stop();  //可以用,但是不建议用,已过时的方法

            //ra.destroy();//不可以用 ,不起作用

            //(4)外部测试时候,调用更改此标志位的方法

            ra.stopThread();

            try {

                Thread.sleep(2);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("B判断兔子线程的是否处于活动状态:"+ra.isAlive());

        }

    *实现Runnable接口*

    1.定义子类实现Runnable接口

    2.重写Runnable接口run()方法

    3.通过Thread含构造器创建线程对象

    4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中

    5.调用Thread类的start()方法

    优点:1.避免单继承

    2.方便共享资源,同一份资源,多个代理访问

    **java中终止线程**

    *1.使用标志位*

    *2.中断策略*

    使用标志位这种方法有个很大的局限性,那就是通过循环来使每次的操作都需要检查一下标志位。

    java还提供了中断协作机制,能够使一个线程要求另外一个线程停止当前工作。其大致的思想为:调用线程Thread的interrupt([ˌɪntəˈrʌpt])方法,在线程内部通过捕获InterruptedException异常来决定线程是否继续还是退出。如下:

         class InterruptRunnable implements Runnable{

            private BlockingQueue queue = new ArrayBlockingQueue(10);

            @Override

            public void run() {

                int i= 0;

                for (;;) {

                    try {

                        //线程的操作

                        i++;

                        queue.put(i);

                    } catch (InterruptedException e) {

                        //捕获到了异常 该怎么做

                        System.out.println(queue);

                        e.printStackTrace();

                        return;

                    }

                }

              }

          上述代码通过BlockingQueue的put方法来抛出InterruptedException异常。

          当内部捕获到该异常时,从而决定是否继续还是直接退出了

          public void testInterruptRunnable() throws InterruptedException {

            InterruptRunnable runnable = new InterruptRunnable();

            Thread thread = new Thread(runnable);

            thread.start();

            System.err.println(thread.isAlive());

            Thread.sleep(1000);

            thread.interrupt();

            Thread.sleep(1000);

            System.err.println(thread.isAlive());

            Thread.sleep(1000);

            System.err.println(thread.isAlive());

        }

        该测试方法大致同使用标志位的测试方法,同样启动该线程后,1秒后调用线程的interrupt方法,从而触发Runnable的内部queue.put(i)操作抛出InterruptedException异常。

    **进程的几种通信方式**

    进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

    IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

    **多线程之线程间的通信方式**

    * wait/notify 等待

    * Volatile 内存共享

    **Yied放弃时间片(暂停线程)**

     * yield: 暂停当前正在执行的线程对象,并执行其他线程

     *  yield:加在哪个线程体里,就暂停谁 ,暂停不一定生效

            class YieldDemo implements Runnable{

            @Override

            public void run() {

                for(int i=0;i<=1000;i++){

                    /*if(i%20==0){

                        Thread.yield();  //暂停的线程A

                    }*/

                    System.out.println(Thread.currentThread().getName()+"----"+i);

                }

            }

            }

              public class TestYield {

              public static void main(String[] args) {

                //创建真实角色类的实例

                YieldDemo yd = new YieldDemo();

                //创建代理角色,代理持有对真实角色的引用

                Thread th = new Thread(yd,"线程A");

                //通过代理开启

                th.start();

                //main()主线程中  

                for(int i=0;i<=1000;i++){

                    if(i%20==0){

                        Thread.yield();  //暂停的main(),线程A获得执行的机会

                    }

                    System.out.println(Thread.currentThread().getName()+"----"+i);

                }

            }

            }

    **Join合并线程**

     * join:等待该线程终止。

     * 阻塞线程,合并线程

     * join()阻塞自身,让其他线程获得执行的机会,等其他线程执行完毕,自身才接着执行 。

     * 加在哪个线程体里,就阻塞谁

               class JoinDemo implements Runnable{

                @Override

                public void run() {

                    for(int i=0;i<=1000;i++){

                        System.out.println(Thread.currentThread().getName()+"----"+i);

                    }

                }

             }

             public class TestJoin {

                public static void main(String[] args) {

                    //创建真实角色

                    JoinDemo jd = new JoinDemo();

                    //创建代理,代理持有对真实角色的引用

                    Thread th = new Thread(jd,"线程a");

                    //通过代理启动

                    th.start();

                for(int i=0;i<=1000;i++){

                    if(i==50){

                        try {

                            th.join(); //阻塞main线程

                        } catch (InterruptedException e) {

                            e.printStackTrace();

                        } 

                    }

                    System.out.println(Thread.currentThread().getName()+"----"+i);

                }

            }

            }

    **Synchronized线程的同步与锁**

    * Synchronized允许加在方法前,表示方法是线程安全的。

    * 多个代理访问同一份资源,出现资源抢夺的问题,同步 :并发,多个线程访问同一份资源,确保资源安         全----线程安全

    * 方式一:同步代码块

    synchronized(引用类型、this/类.class){  ..... }

    注意:使用实现Runnable接口方式创建多线程,同步代码块中的锁可以用this,如何使继承Thread类,慎this

    * 方式二:同步方法

    访问修饰符  synchornized 返回值类型 方法名 (){...}

    **单例模式**

    单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

    关键点:

    1)一个类只有一个实例       这是最基本的

    2)它必须自行创建这个实例

    3)它必须自行向整个系统提供这个实例

    两种实现方式:

    *懒汉模式*

    (类加载时不初始化)

         public class LazySingleton {

        //懒汉式单例模式

        //比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢

        private static LazySingleton intance = null;//静态私用成员,没有初始化

        private LazySingleton()

        {

            //私有构造函数

        }

        public static synchronized LazySingleton getInstance()    //静态,同步,公开访问点

        {

            if(intance == null)

            {

                intance = new LazySingleton();

            }

            return intance;

        }

        }

    *饿汉模式*

    (在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)

         public class EagerSingleton {

        //饿汉单例模式

        //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快

        private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化

        private EagerSingleton() 

        {

            //私有构造函数

        }

        public static EagerSingleton getInstance()    //静态,不用同步(类加载时已初始化,不会有多线程的问题)

        {

            return instance;

        }

        }

    **wait()和notify()**

     * wait()方法:调用wait()方法,会挂起当前线程,并释放共享资源的锁.

    * notify()方法:调用了任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个解除阻塞,但要等到获得锁后才可真正执行。

    * notifyAll()方法:调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。

    * wait(),notify(),和notifyall()这3个方法都是Object类中的final方法,被所有的类继承且不允许重写。这3个方法只能在同步方法或同步代码块中使用,否则会抛出异常。

    **wait()方法和sleep()方法  区别**

    * wait() :线程进入等待状态,不占用任何资源,不增加时间限制,因为wait方法会释放锁,所以调用该方法时,要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。

    * sleep():线程睡眠,线程被调用时,占着cpu不工作,消耗内存资源,增加时间限制

    ,必须捕获异常

    **同步和异步的区别**

    同步是指两个线程的运行是相关的,其中一个线程要阻塞等待另外一个线程的运行。异步的意思是两个线程毫无相关,自己运行自己的。

    以通讯为例

              同步:发送一个请求,等待返回,然后再发送下一个请求 

    异步:发送一个请求,不等待返回,随时可以再发送下一个请求

              并发:同时发送多个请求

    **socket编程**

    套接字使用Tcp提供了两台计算机之间的通信机制

    区分不同应用程序进程之间的网络通信和连接

    ServerSocket用于服务器端,通过accept()监听请求,然后返回Socket。

    Socket用于客户端

    **TCP和UDP的区别**

    Tcp提供面向连接的,可靠的字节流传输,并且提供了拥塞控制和流量控制机制

    UDP提供面向无连接的,不可靠的数据报的传输,不提供拥塞控制和流量控制机制

    **Socket通信实现步骤:**

    简化出Socket通信的实现步骤:

    1.创建ServerSocket和Socket,建立连接

    2.打开链接到Socket的输入/输出流

    3.按照协议对Socket进行读/写操作

    4.关闭输入输出流、关闭Socket

    *使用多线程实现多客户端的通信:*

    多线程基本步骤:

    1.服务器端创建ServerSocket,循环调用accept()等待客户端连接。

    2.客户端创建一个socket并请求和服务器端连接。

    3.服务器端接收客户端请求,创建socket与该客户建立专线连接。

    4.建立连接的两个socket在一个单独的线程上对话。

    5.服务器端继续等待新的连接

    **NIO主要原理及使用**

    NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接、读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候,程序也可以做其他事情,以实现线程的异步操作。

    考虑一个即时消息服务器,可能有上千个客户端同时连接到服务器,但是在任何时刻只有非常少量的消息需要读取和分发(如果采用线程池或者一线程一客户端方式,则会非常浪费资源),这就需要一种方法能阻塞等待,直到有一个信道可以进行I/O操作。NIO的Selector选择器就实现了这样的功能,一个Selector实例可以同时检查一组信道的I/O状态,它就类似一个观察者,只要我们把需要探知的SocketChannel告诉Selector,我们接着做别的事情,当有事件(比如,连接打开、数据到达等)发生时,它会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的SocketChannel,然后,我们从这个Channel中读取数据,接着我们可以处理这些数据。

    Selector内部原理实际是在做一个对所注册的Channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个Channel有所注册的事情发生,比如数据来了,它就会读取Channel中的数据,并对其进行处理。

    要使用选择器,需要创建一个Selector实例,并将其注册到想要监控的信道上(通过Channel的方法实现)。最后调用选择器的select()方法,该方法会阻塞等待,直到有一个或多个信道准备好了I/O操作或等待超时,或另一个线程调用了该选择器的wakeup()方法。现在,在一个单独的线程中,通过调用select()方法,就能检查多个信道是否准备好进行I/O操作,由于非阻塞I/O的异步特性,在检查的同时,我们也可以执行其他任务。

    基于NIO的TCP连接的建立步骤

    服务端

        1、传建一个Selector实例;

        2、将其注册到各种信道,并指定每个信道上感兴趣的I/O操作;

        3、重复执行:

            1)调用一种select()方法;

            2)获取选取的键列表;

            3)对于已选键集中的每个键:

               a、获取信道,并从键中获取附件(如果为信道及其相关的key添加了附件的话);

               b、确定准备就绪的操纵并执行,如果是accept操作,将接收的信道设置为非阻塞模式,并注册到选择器;

               c、如果需要,修改键的兴趣操作集;

               d、从已选键集中移除键

    客户端

    与基于多线程的TCP客户端大致相同,只是这里是通过信道建立的连接,但在等待连接建立及读写时,我们可以异步地执行其他任务。

    **double转byte类型**

        private static byte[] data;

        public static byte[] convert(double num) throws IOException {

            data = null;

            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            DataOutputStream dos = new DataOutputStream(bos);

            dos.writeDouble(num);

            dos.flush();

            data = bos.toByteArray();

            dos.close();

            return data;

        }

    **byte转double类型**

        public static double convert(byte[] data) throws IOException {

            DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));

            double num = dis.readDouble();

            dis.close();

            return num;

        }

    **UDP通信**

    *发送引用数据类型*

    *    客户端:

    1)    创建客户端 DatagramSocket类 +指定端口

    2)    准备数据  字节数组

    3)    打包 DatagramPacket +服务器地址及端口

    4)    发送

    5)    释放资源

    *    服务器:

    1)    创建服务端DatagramSocket类+指定端口

    2)    准备接受容器  字节数组 封装DatagramPacket(封装成包)

    3)    包 接收数据

    4)    分析

    5)    释放资源

    *发送基本数据类型*

    *    客户端:

    1)    创建客户端 DatagramSocket类 +指定端口

    2)    准备数据 基本数据类型转换成字节数组(字节数组输出流ByteArrayOutputStream toByteArray 数据字节输出流DataOutputStream)

    3)    打包 DatagramPacket +服务器地址及端口(发送的地点以及端口)

    4)    发送

    5)    释放资源

    *    服务器:

    1)    创建服务端DatagramSocket类+指定端口

    2)    准备接受容器  字节数组 封装DatagramPacket(封装成包)

    3)    包 接收数据

    4)    分析数据 字节数组转换成基本数据类型(字节数组输入流ByteArrayOutputStream 数据字节输入流DataInputStream)

    5)    释放资源

    相关文章

      网友评论

          本文标题:JAVASE学习笔记

          本文链接:https://www.haomeiwen.com/subject/ksfovqtx.html