• 尚硅谷Java——宋红康笔记【day25-day29】


    day25

    Map接口

    一、Map的实现类的结构:
    |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
       |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
          |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
             原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
       |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
       |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
          |----Properties:常用来处理配置文件。key和value都是String类型

    HashMap的底层:数组+链表 (jdk7及之前)
            数组+链表+红黑树 (jdk 8)

    面试题:

    1. HashMap的底层实现原理?
    2. HashMap 和 Hashtable的异同?
    3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)

    二、Map结构的理解:

    Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)

    Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()

    一个键值对:key-value构成了一个Entry对象。

    Map中的entry:无序的、不可重复的,使用Set存储所有的entry

    三、HashMap的底层实现原理?以jdk7为例说明:

    HashMap map = new HashMap():

    在实例化以后,底层创建了长度是16的一维数组Entry[] table。

    ...可能已经执行过多次put... ---> map.put(key1,value1):

    首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

    如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1

    如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

       如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2

       如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:

          如果equals()返回false:此时key1-value1添加成功。----情况3

          如果equals()返回true:使用value1替换value2。

    补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

    在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

    jdk8 相较于jdk7在底层实现方面的不同:

    1. new HashMap():底层没有创建一个长度为16的数组
    2. jdk 8底层的数组是:Node[],而非Entry[]
    3. 首次调用put()方法时,底层创建长度为16的数组
    4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
         4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
         4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

    DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
    DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
    threshold:扩容的临界值,=容量 * 填充因子:16 * 0.75 => 12
    TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
    MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

    四、LinkedHashMap的底层实现原理(了解)

    源码中:

    static class Entry<K,V> extends HashMap.Node<K,V> {
         Entry<K,V> before, after;//能够记录添加的元素的先后顺序
         Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
         }
     }
    

    五、Map中定义的方法:

    添加、删除、修改操作:

    Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    void putAll(Map m):将m中的所有key-value对存放到当前map中
    Object remove(Object key):移除指定key的key-value对,并返回value
    void clear():清空当前map中的所有数据

    元素查询的操作:

    Object get(Object key):获取指定key对应的value
    boolean containsKey(Object key):是否包含指定的key
    boolean containsValue(Object value):是否包含指定的value
    int size():返回map中key-value对的个数
    boolean isEmpty():判断当前map是否为空
    boolean equals(Object obj):判断当前map和参数对象obj是否相等

    元视图操作的方法:

    Set keySet():返回所有key构成的Set集合
    Collection values():返回所有value构成的Collection集合
    Set entrySet():返回所有key-value对构成的Set集合

    ①遍历所有的key集

    //遍历所有的key集:keySet()
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
    

    ②遍历所有的value集

    //遍历所有的value集: values()
    Collection values = map.values();
    for(Object obj : values){
        System.out.println(obj);
    }
    

    ③遍历所有的key-value

    //遍历所有的key-value
    //方式一:entrySet()
    Set entrySet = map.entrySet();
    Iterator iterator1 = entrySet.iterator();
    while(iterator1.hasNext()){
        Object obj = iterator1.next();
        //entrySet集合中的元素都是entry
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + "------>" + entry.getValue());
    }
    System.out.println();
    

    //方式二:
    Set keySet = map.keySet();
    Iterator iterator2 = keySet.iterator();
    while(iterator2.hasNext()){
    Object key = iterator2.next();
    Object value = map.get(key);
    System.out.println(key + "======" + value);
    }

    总结:常用方法:

    添加:put(Object key,Object value)

    删除:remove(Object key)

    修改:put(Object key,Object value)

    查询:get(Object key)

    长度:size()

    遍历 :keySet() / values() / entrySet()

    TreeSet

    向TreeMap中添加key-value,要求key必须是由同一个类创建的对象

    因为要按照key进行排序:自然排序 、定制排序

    TreeSet排序例子

    import org.junit.Test;
    

    import java.util.*;

    public class TreeMapTest {

    //自然排序
    @Test
    public void test1(){
        TreeMap map = new TreeMap();
        User u1 = new User(&quot;Tom&quot;,23);
        User u2 = new User(&quot;Jerry&quot;,32);
        User u3 = new User(&quot;Jack&quot;,20);
        User u4 = new User(&quot;Rose&quot;,18);
    
        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);
    
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + &quot;----&gt;&quot; + entry.getValue());
    
        }
    }
    
    //定制排序
    @Test
    public void test2(){
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User &amp;&amp; o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }
                throw new RuntimeException(&quot;输入的类型不匹配!&quot;);
            }
        });
        User u1 = new User(&quot;Tom&quot;,23);
        User u2 = new User(&quot;Jerry&quot;,32);
        User u3 = new User(&quot;Jack&quot;,20);
        User u4 = new User(&quot;Rose&quot;,18);
    
        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);
    
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + &quot;----&gt;&quot; + entry.getValue());
    
        }
    }
    

    }

    Collections:操作Collection、Map的工具类

    reverse(List):反转 List 中元素的顺序
    shuffle(List):对 List 集合元素进行随机排序
    sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
    sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

    Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
    Object min(Collection)
    Object min(Collection,Comparator)
    int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    void copy(List dest,List src):将src中的内容复制到dest中
    boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

    其中要注意copy函数的使用:

    //报异常:IndexOutOfBoundsException("Source does not fit in dest")
    List dest = new ArrayList();
    Collections.copy(dest,list);
    

    正确的用法:

    List dest = Arrays.asList(new Object[list.size()]);
    System.out.println(dest.size());//list.size();
    Collections.copy(dest,list);
    

    System.out.println(dest);

    Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

    //返回的list1即为线程安全的List
    List list1 = Collections.synchronizedList(list);
    

    Properties

    Properties:常用来处理配置文件。key和value都是String类型

    例子

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.Properties;
    

    public class PropertiesTest {

    //Properties:常用来处理配置文件。key和value都是String类型
    public static void main(String[] args)  {
        FileInputStream fis = null;
        try {
            Properties pros = new Properties();
    
            fis = new FileInputStream(&quot;jdbc.properties&quot;);
            pros.load(fis);//加载流对应的文件
    
            String name = pros.getProperty(&quot;name&quot;);
            String password = pros.getProperty(&quot;password&quot;);
    
            System.out.println(&quot;name = &quot; + name + &quot;, password = &quot; + password);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
    }
    

    }

    day26

    泛型的使用

    1.jdk 5.0新增的特性

    2.在集合中使用泛型:

    以ArrayList为例:

    @Test
    public void test1(){
        ArrayList<Integer> list = new ArrayList<>();
    
    list.add(78);
    list.add(87);
    list.add(99);
    list.add(65);
    
    //list.add(&quot;Tom&quot;);
    
    Iterator&lt;Integer&gt; iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    

    }

    以HashSet为例

    @Test
    public void test2(){
    
    Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
    
    map.put(&quot;Tom&quot;,87);
    map.put(&quot;Jerry&quot;,87);
    map.put(&quot;Jack&quot;,67);
    
    //map.put(123, &quot;kkk&quot;);
    
    //泛型的嵌套
    Set&lt;Map.Entry&lt;String, Integer&gt;&gt; entry = map.entrySet();
    Iterator&lt;Map.Entry&lt;String, Integer&gt;&gt; iterator = entry.iterator();
    while(iterator.hasNext()){
        Map.Entry&lt;String, Integer&gt; e = iterator.next();
        String key = e.getKey();
        Integer value = e.getValue();
        System.out.println(key + &quot;-----&gt;&quot; + value);
    }
    

    }

    总结:
    ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
    ② 在实例化集合类时,可以指明具体的泛型类型
    ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
    比如:add(E e) --->实例化以后:add(Integer e)
    ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
    ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

    3.如何自定义泛型结构:泛型类、泛型接口;泛型方法

    如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型,如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。

    定义一个继承了Order的子类,它的类型为

    public class SubOrder extends Order<Integer> {
    
    public static &lt;E&gt; List&lt;E&gt; copyFromArrayToList(E[] arr){
        ArrayList&lt;E&gt; list = new ArrayList&lt;&gt;();
    
        for (E e: arr){
            list.add(e);
        }
    
        return list;
    }
    

    }

    那么,由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。

    SubOrder sub1 = new SubOrder();
    sub1.setOrderT(1122);
    

    如果一个子类在继承时未声明类型:

    public class SubOrder1<T> extends Order<T> {
    }
    

    声明时:

    SubOrder1<String> sub2 = new SubOrder1<>();
    sub2.setOrderT("order2......");
    

    泛型不同的引用不能相互赋值。

    ArrayList<String> list1 = null;
    ArrayList<Integer> list2 = new ArrayList<>();
    //泛型不同的引用不能相互赋值。
    //list1 = list2;
    

    说明:
    1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:< E1,E2,E3>
    2. 泛型类的构造器如下 public GenericClass(){} 。而下面是错误的public GenericClass<E>(){} 3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
    4. 泛型不同的引用不能相互赋值。尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中。
    5. 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于Object。经验: 泛型要使用一路都用。要不用,一路都不要用。
    6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象 。
    7. jdk1.7,泛型的简化 操作 ArrayList<Fruit> flist = new ArrayList<>();
    8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
    9. 在类接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
    10. 异常类不能是泛型的
    11. 不能使用 new E[] 。但是可以 E[] elements = (E[])new Object[capacity];
    参考:ArrayList 源码中声明: Object[] elementData 而非泛型参数类型数组。
    12. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    子类不保留父类的泛型:按需实现

      没有类型 擦除

      具体类型

    子类保留父类的泛型:泛型子类

      全部保留

      部分保留

    结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型

    泛型方法

    泛型方法的格式: [访问权限] <泛型> 返回类型 方法名 ([泛型标识 参数名称]) 抛出的异常

    泛型在继承方面的体现

    虽然类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,二者是并列关系。

    补充:类A是类B的父类,A<G> 是 B<G> 的父类

    通配符的使用

    通配符:?

    类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>

    @Test
    public void test1(){
        List<Object> list1 = null;
        List<String> list2 = null;
    
    List&lt;?&gt; list = null;
    
    list = list1;
    list = list2;
    
    System.out.println(list1);
    System.out.println(list2);
    
    
    List&lt;String&gt; list3 = new ArrayList&lt;&gt;();
    list3.add(&quot;ASA&quot;);
    list3.add(&quot;DGA&quot;);
    list3.add(&quot;Gesag&quot;);
    
    //list.add(&quot;AA&quot;);
    //list.add('?');
    
    list.add(null);
    
    //获取(读取):允许读取数据,读取的数据类型为Object。
    Object o = list.get(0);
    System.out.println(o);
    

    分析:

    因为?是两者的共同父类,所以可以将list1和2传递给list

    添加(写入):对于List<?>就不能向其内部添加数据。除了null

    读取:允许读取数据,读取的数据类型为Object。

    有限制条件的通配符的使用。

    ? extends A:
        G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类

    ? super A:
        G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类

    @Test
    public void test4(){
    
    List&lt;? extends Person&gt; list1 = null;
    List&lt;? super Person&gt; list2 = null;
    
    List&lt;Student&gt; list3 = new ArrayList&lt;Student&gt;();
    List&lt;Person&gt; list4 = new ArrayList&lt;Person&gt;();
    List&lt;Object&gt; list5 = new ArrayList&lt;Object&gt;();
    
    list1 = list3;
    list1 = list4;
    //list1 = list5;
    
    //list2 = list3;
    list2 = list4;
    list2 = list5;
    
    //读取数据:
    list1 = list3;
    Person p = list1.get(0);
    //编译不通过
    //Student s = list1.get(0);
    
    list2 = list4;
    Object obj = list2.get(0);
    ////编译不通过
    //Person obj = list2.get(0);
    
    //写入数据:
    //编译不通过
    //list1.add(new Student());
    
    //编译通过
    list2.add(new Person());
    list2.add(new Student());
    

    }

    File类的使用

    1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
    2. File类声明在java.io包下
    3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
    4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

    如何创建File类的实例

    File(String filePath)
    File(String parentPath,String childPath)
    File(File parentFile,String childPath)
    

    相对路径:相较于某个路径下,指明的路径。
    绝对路径:包含盘符在内的文件或文件目录的路径

    路径分隔符

     windows:\
     unix:/
     */
    

    public String getAbsolutePath():获取绝对路径
    public String getPath() :获取路径
    public String getName() :获取名称
    public String getParent():获取上层文件目录路径。若无,返回null
    public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
    public long lastModified() :获取最后一次的修改时间,毫秒值

    如下的两个方法适用于文件目录:
    public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
    public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组


    public boolean renameTo(File dest):把文件重命名为指定的文件路径
    比如:file1.renameTo(file2)为例:
    要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。


    public boolean isDirectory():判断是否是文件目录
    public boolean isFile() :判断是否是文件
    public boolean exists() :判断是否存在
    public boolean canRead() :判断是否可读
    public boolean canWrite() :判断是否可写
    public boolean isHidden() :判断是否隐藏


    创建硬盘中对应的文件或文件目录

    public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
    public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
    public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

    删除磁盘中的文件或文件目录

    public boolean delete():删除文件或者文件夹
    删除注意事项:Java中的删除不走回收站。

    要想删除成功,io4文件目录下不能有子目录或文件

    day27

    一、流的分类:
    1.操作数据单位:字节流、字符流
    2.数据的流向:输入流、输出流
    3.流的角色:节点流、处理流

    抽象基类 字节流 字符流
    输入流 InputStream Reader
    输出流 OutputStream Writer

    二、流的体系结构

    抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
    InputStream FileInputStream(read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
    OutputStream FileOutputStream(write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
    Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
    Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()

    说明点:
    1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
    2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
    3. 读入的文件一定要存在,否则就会报FileNotFoundException。

    学会对read()操作升级:使用read的重载方法

    示例代码:

    @Test
    public void testFileReader1(){
        FileReader fr = null;
        try{
            //1.File类的实例化
            File file = new File("hello.txt");
            //2.FileReader流的实例化
            fr = new FileReader(file);
    
        //3. 读入的操作
        //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果大奥文件末尾,返回-1
        char[] cbuf = new char[5];
        int len;
        while((len = fr.read(cbuf)) != -1){
            String str = new String(cbuf, 0 ,len);
            System.out.println(str);
        }
    
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        if(fr != null){
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    }

    读取文件的推荐方式:

    while((len = fr.read(cbuf)) != -1){
        String str = new String(cbuf, 0 ,len);
        System.out.println(str);
    }
    

    从内存中写出数据到硬盘的文件里。

    说明:
    1. 输出操作,对应的File可以不存在的。并不会报异常
    2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
    File对应的硬盘中的文件如果存在:
    如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
    如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容

    IO流基本操作:
    1.File类的实例化
    2.FileReader流的实例化
    3.读入的操作
    4.资源的关闭


    read():返回读入的一个字符。如果达到文件末尾,返回-1

    不能使用字符流来处理图片等字节数据

    测试FileInputStream和FileOutputStream的使用

    1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
    2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理

    使用字节流FileInputStream处理文本文件,可能出现乱码。

    图片复制操作示例代码

    /*
    实现对图片的复制操作
     */
    @Test
    public void testFileInputOutputStream()  {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情2.jpg");
    
        //
        fis = new FileInputStream(srcFile);
        fos = new FileOutputStream(destFile);
    
        //复制的过程
        byte[] buffer = new byte[5];
        int len;
        while((len = fis.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
    
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fos != null){
            //
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    

    }

    处理流之一:缓冲流的使用

    1.缓冲流:

    • BufferedInputStream
    • BufferedOutputStream
    • BufferedReader
    • BufferedWriter

    2.作用:提供流的读取、写入的速度

    提高读写速度的原因:内部提供了一个缓冲区

    3.处理流,就是“套接”在已有的流的基础上。

    说明:

    ① 关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.

    ② 使用String读取数据:

    //方式二:使用String
    String data;
    while((data = br.readLine()) != null){
        //方法一:
        //bw.write(data + "
    ");//data中不包含换行符
        //方法二:
        bw.write(data);//data中不包含换行符
        bw.newLine();//提供换行的操作
    }
    

    复制文件的示例代码:

    //实现文件复制的方法
    public void copyFileWithBuffered(String srcPath,String destPath){
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
    
    try {
        //1.造文件
        File srcFile = new File(srcPath);
        File destFile = new File(destPath);
        //2.造流
        //2.1 造节点流
        FileInputStream fis = new FileInputStream((srcFile));
        FileOutputStream fos = new FileOutputStream(destFile);
        //2.2 造缓冲流
        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);
    
        //3.复制的细节:读取、写入
        byte[] buffer = new byte[1024];
        int len;
        while((len = bis.read(buffer)) != -1){
            bos.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.资源关闭
        //要求:先关闭外层的流,再关闭内层的流
        if(bos != null){
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
        if(bis != null){
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
        //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
    //fos.close();
    //fis.close();
    }
    

    }

    处理流之二:转换流的使用

    1.转换流:属于字符流

    • InputStreamReader:将一个字节的输入流转换为字符的输入流
    • OutputStreamWriter:将一个字符的输出流转换为字节的输出流

    2.作用:提供字节流与字符流之间的转换

    //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集  
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    

    3.解码:字节、字节数组 --->字符数组、字符串
    编码:字符数组、字符串 ---> 字节、字节数组

    4.字符集

    • ASCII:美国标准信息交换码。用一个字节的7位可以表示。
    • ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
    • GB2312:中国的中文编码表。最多两个字节编码所有字符
    • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
    • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
    • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

    示例代码:

    /*
    此时处理异常的话,仍然应该使用try-catch-finally
    

    综合使用InputStreamReader和OutputStreamWriter
    */
    @Test
    public void test2() throws Exception {
    //1.造文件、造流
    File file1 = new File("dbcp.txt");
    File file2 = new File("dbcp_gbk.txt");

    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    
    InputStreamReader isr = new InputStreamReader(fis,&quot;utf-8&quot;);
    OutputStreamWriter osw = new OutputStreamWriter(fos,&quot;gbk&quot;);
    
    //2.读写过程
    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) != -1){
        osw.write(cbuf,0,len);
    }
    
    //3.关闭资源
    isr.close();
    osw.close();
    

    }

    day28

    Path

    1. jdk 7.0 时,引入了 Path、Paths、Files三个类。
    2. 此三个类声明在:java.nio.file包下。
    3. Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
    4. 如何实例化Path:使用Paths.

      static Path get(String first, String … more) : 用于将多个字符串串连成路径
      static Path get(URI uri): 返回指定uri对应的Path路径

    如何使用Paths实例化Path

    示例代码:

    @Test
    public void test1() {
        Path path1 = Paths.get("d:\nio\hello.txt");//new File(String filepath)
    
    Path path2 = Paths.get(&quot;d:\&quot;, &quot;nio\hello.txt&quot;);//new File(String parent,String filename);
    
    System.out.println(path1);
    System.out.println(path2);
    
    Path path3 = Paths.get(&quot;d:\&quot;, &quot;nio&quot;);
    System.out.println(path3);
    

    }

    Path中的常用方法

    String toString() : 返回调用 Path 对象的字符串表示形式
    boolean startsWith(String path) : 判断是否以 path 路径开始
    boolean endsWith(String path) : 判断是否以 path 路径结束
    boolean isAbsolute() : 判断是否是绝对路径
    Path getParent():返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
    Path getRoot() :返回调用 Path 对象的根路径
    Path getFileName() : 返回与调用 Path 对象关联的文件名
    int getNameCount(): 返回Path 根目录后面元素的数量
    Path getName(int idx): 返回指定索引位置 idx 的路径名称
    Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
    Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
    File toFile(): 将Path转化为File类的对象

    File file = path1.toFile();//Path--->File的转换
    Path newPath = file.toPath();//File--->Path的转换
    

    Files工具类的使用:操作文件或目录的工具类

    以这两条代码为例,导入文件路径:

    Path path1 = Paths.get("d:\nio", "hello.txt");
    Path path2 = Paths.get("atguigu.txt");
    

    Path copy(Path src, Path dest, CopyOption … how): 文件的复制
    说明:要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。

    Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
    

    Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
    说明:要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。

    Path path3 = Paths.get("d:\nio\nio1");
    Files.createDirectory(path3);
    

    Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
    说明:要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。

    Path path4 = Paths.get("d:\nio\hi.txt");
    Files.createFile(path4);
    

    void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错

    Files.delete(path4);
    

    void deleteIfExists(Path path): Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束

    Files.deleteIfExists(path3);
    

    Path move(Path src, Path dest, CopyOption…how): 将 src 移动到 dest 位置
    说明:要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。

    Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
    

    long size(Path path): 返回 path 指定文件的大小

    long size = Files.size(path2);
    System.out.println(size);
    

    boolean exists(Path path, LinkOption … opts): 判断文件是否存在

    System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));
    

    boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
    说明:不要求此path对应的物理文件存在。

    System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));
    

    boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

    boolean isHidden(Path path) : 判断是否是隐藏文件
    说明:要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。

    System.out.println(Files.isHidden(path1));
    

    boolean isReadable(Path path) : 判断文件是否可读

    System.out.println(Files.isReadable(path1));
    

    boolean isWritable(Path path) : 判断文件是否可写

    System.out.println(Files.isWritable(path1));
    

    boolean notExists(Path path, LinkOption … opts): 判断文件是否不存在

    System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
    
    • StandardOpenOption.READ:表示对应的Channel是可读的。
    • StandardOpenOption.WRITE:表示对应的Channel是可写的。
    • StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
    • StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常

    InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象

    InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);
    

    OutputStream newOutputStream(Path path, OpenOption…how): 获取 OutputStream 对象

    OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    

    SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。

    SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    

    DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录

    Path path2 = Paths.get("e:\teach");
    DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
    Iterator<Path> iterator = directoryStream.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
    

    对象流的使用

    1.ObjectInputStream 和 ObjectOutputStream
    2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
    3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java

    Person.java

    import java.io.Serializable;
    

    /**

    • Person需要满足如下的要求,方可序列化

    • 1.需要实现接口:Serializable

    • 2.当前类提供一个全局常量:serialVersionUID

    • 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性

    • 也必须是可序列化的。(默认情况下,基本数据类型可序列化)

    • 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

    • @author shkstart

    • @create 2019 上午 10:38
      */
      public class Person implements Serializable{

      public static final long serialVersionUID = 475463534532L;

      private String name;
      private int age;
      private int id;
      private Account acct;

      public Person(String name, int age, int id) {
      this.name = name;
      this.age = age;
      this.id = id;
      }

      public Person(String name, int age, int id, Account acct) {
      this.name = name;
      this.age = age;
      this.id = id;
      this.acct = acct;
      }

      @Override
      public String toString() {
      return "Person{" +
      "name='" + name + ''' +
      ", age=" + age +
      ", id=" + id +
      ", acct=" + acct +
      '}';
      }

      public int getId() {
      return id;
      }

      public void setId(int id) {
      this.id = id;
      }

      public String getName() {
      return name;
      }

      public void setName(String name) {
      this.name = name;
      }

      public int getAge() {
      return age;
      }

      public void setAge(int age) {
      this.age = age;
      }

      public Person(String name, int age) {

       this.name = name;
       this.age = age;
      

      }

      public Person() {

      }
      }

    class Account implements Serializable{
    public static final long serialVersionUID = 4754534532L;
    private double balance;

    @Override
    public String toString() {
        return &quot;Account{&quot; +
                &quot;balance=&quot; + balance +
                '}';
    }
    
    public double getBalance() {
        return balance;
    }
    
    public void setBalance(double balance) {
        this.balance = balance;
    }
    
    public Account(double balance) {
    
        this.balance = balance;
    }
    

    }

    4.序列化机制:
    对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

    RandomAccessFile的使用

    1. RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
    2. RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
    3. 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
    4. 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果

    示例代码:

     /*
    使用RandomAccessFile实现数据的插入效果
     */
    @Test
    public void test3() throws IOException {
    
    RandomAccessFile raf1 = new RandomAccessFile(&quot;hello.txt&quot;,&quot;rw&quot;);
    
    raf1.seek(3);//将指针调到角标为3的位置
    //保存指针3后面的所有数据到StringBuilder中
    StringBuilder builder = new StringBuilder((int) new File(&quot;hello.txt&quot;).length());
    byte[] buffer = new byte[20];
    int len;
    while((len = raf1.read(buffer)) != -1){
        builder.append(new String(buffer,0,len)) ;
    }
    //调回指针,写入“xyz”
    raf1.seek(3);
    raf1.write(&quot;xyz&quot;.getBytes());
    
    //将StringBuilder中的数据写入到文件中
    raf1.write(builder.toString().getBytes());
    
    raf1.close();
    

    }

    网络编程

    一、网络编程中有两个主要的问题:

    • 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
    • 2.找到主机后如何可靠高效地进行数据传输

    二、网络编程中的两个要素:

    • 1.对应问题一:IP和端口号
    • 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)

    三、通信要素一:IP和端口号

    1.IP:唯一的标识 Internet 上的计算机(通信实体)
    2.在Java中使用InetAddress类代表IP
    3.IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
    4.域名:  www.baidu.com  www.mi.com  www.sina.com  www.jd.com  www.vip.com
    5.本地回路地址:127.0.0.1 对应着:localhost
    6.如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost()

      两个常用方法:getHostName() / getHostAddress()

    7.端口号:正在计算机上运行的进程。

    • 要求:不同的进程有不同的端口号
    • 范围:被规定为一个 16 位的整数 0~65535。

    8.端口号与IP地址的组合得出一个网络套接字:Socket

    TCP的3个例子

    通过三个例子学习客户端与服务端的交互

    例子1: 客户端发送信息给服务端,服务端将数据显示在控制台上

    public class TCPTest1 {
    
    //客户端
    @Test
    public void client()  {
        Socket socket = null;
        OutputStream os = null;
        try {
            //1.创建Socket对象,指明服务器端的ip和端口号
            InetAddress inet = InetAddress.getByName(&quot;192.168.14.100&quot;);
            socket = new Socket(inet,8899);
            //2.获取一个输出流,用于输出数据
            os = socket.getOutputStream();
            //3.写出数据的操作
            os.write(&quot;你好,我是客户端mm&quot;.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            if(os != null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
    }
    //服务端
    @Test
    public void server()  {
    
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            //1.创建服务器端的ServerSocket,指明自己的端口号
            ss = new ServerSocket(8899);
            //2.调用accept()表示接收来自于客户端的socket
            socket = ss.accept();
            //3.获取输入流
            is = socket.getInputStream();
    
            //不建议这样写,可能会有乱码
    

    // byte[] buffer = new byte[1024];
    // int len;
    // while((len = is.read(buffer)) != -1){
    // String str = new String(buffer,0,len);
    // System.out.print(str);
    // }
    //4.读取输入流中的数据
    baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[5];
    int len;
    while((len = is.read(buffer)) != -1){
    baos.write(buffer,0,len);
    }

            System.out.println(baos.toString());
    
            System.out.println(&quot;收到了来自于:&quot; + socket.getInetAddress().getHostAddress() + &quot;的数据&quot;);
    
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(baos != null){
                //5.关闭资源
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss != null){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
    }
    

    }

    总结:
    客户端步骤分为:

    1. 创建Socket对象,指明服务器端的ip和端口号(关键字: Socket、InetAddress、getByName)
    2. 获取一个输出流,用于输出数据(关键字: OutputStream、 socket.getOutputStream)
    3. 写出数据的操作(关键字: getBytes)
    4. 关闭资源(后用先关,先关输出流,再关socket)

    服务端步骤分为:

    1. 创建服务器端的ServerSocket,指明自己的端口号(关键字: ServerSocket)
    2. 调用accept()表示接收来自于客户端的socket(关键字: accept)
    3. 获取输入流(关键字: getInputStream)
    4. 读取输入流中的数据(关键字: ByteArrayOutputStream)
    5. 关闭资源

    例子2:客户端发送文件给服务端,服务端将文件保存在本地。

    public class TCPTest2 {
    
    /*
    这里涉及到的异常,应该使用try-catch-finally处理
     */
    @Test
    public void client() throws IOException {
        //1.
        Socket socket = new Socket(InetAddress.getByName(&quot;127.0.0.1&quot;),9090);
        //2.
        OutputStream os = socket.getOutputStream();
        //3.
        FileInputStream fis = new FileInputStream(new File(&quot;beauty.jpg&quot;));
        //4.
        byte[] buffer = new byte[1024];
        int len;
        while((len = fis.read(buffer)) != -1){
            os.write(buffer,0,len);
        }
        //5.
        fis.close();
        os.close();
        socket.close();
    }
    
    /*
    这里涉及到的异常,应该使用try-catch-finally处理
     */
    @Test
    public void server() throws IOException {
        //1.
        ServerSocket ss = new ServerSocket(9090);
        //2.
        Socket socket = ss.accept();
        //3.
        InputStream is = socket.getInputStream();
        //4.
        FileOutputStream fos = new FileOutputStream(new File(&quot;beauty1.jpg&quot;));
        //5.
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        //6.
        fos.close();
        is.close();
        socket.close();
        ss.close();
    
    }
    

    }

    分析:步骤与例子1是一样的,差别在于服务端保存文件的IO方法。

    例子3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。

    public class TCPTest3 {
    
    /*
    这里涉及到的异常,应该使用try-catch-finally处理
    */
    @Test
    public void client() throws IOException {
        //1.
        Socket socket = new Socket(InetAddress.getByName(&quot;127.0.0.1&quot;),9090);
        //2.
        OutputStream os = socket.getOutputStream();
        //3.
        FileInputStream fis = new FileInputStream(new File(&quot;beauty.jpg&quot;));
        //4.
        byte[] buffer = new byte[1024];
        int len;
        while((len = fis.read(buffer)) != -1){
            os.write(buffer,0,len);
        }
        //关闭数据的输出
        socket.shutdownOutput();
    
        //5.接收来自于服务器端的数据,并显示到控制台上
        InputStream is = socket.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bufferr = new byte[20];
        int len1;
        while((len1 = is.read(buffer)) != -1){
            baos.write(buffer,0,len1);
        }
    
        System.out.println(baos.toString());
    
        //6.
        fis.close();
        os.close();
        socket.close();
        baos.close();
    }
    
    /*
    这里涉及到的异常,应该使用try-catch-finally处理
     */
    @Test
    public void server() throws IOException {
        //1.
        ServerSocket ss = new ServerSocket(9090);
        //2.
        Socket socket = ss.accept();
        //3.
        InputStream is = socket.getInputStream();
        //4.
        FileOutputStream fos = new FileOutputStream(new File(&quot;beauty2.jpg&quot;));
        //5.
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
    
        System.out.println(&quot;图片传输完成&quot;);
    
        //6.服务器端给予客户端反馈
        OutputStream os = socket.getOutputStream();
        os.write(&quot;你好,美女,照片我已收到,非常漂亮!&quot;.getBytes());
    
        //7.
        fos.close();
        is.close();
        socket.close();
        ss.close();
        os.close();
    
    }
    

    }

    URL网络编程

    1.URL:统一资源定位符,对应着互联网的某一资源地址

    2.格式:

    http://localhost:8080/examples/beauty.jpg?username=Tom

    协议 主机名 端口号   资源地址    参数列表

    • url.getProtocol( ): 获取该URL的协议名
    • url.getHost( ): 获取该URL的主机名
    • url.getPort( ): 获取该URL的端口号
    • url.getPath( ): 获取该URL的文件路径
    • url.getFile( ): 获取该URL的文件名
    • url.getQuery( ): 获取该URL的查询名

        

    public class URLTest1 {
    
    public static void main(String[] args) {
    
        HttpURLConnection urlConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            URL url = new URL(&quot;http://localhost:8080/examples/beauty.jpg&quot;);
    
            urlConnection = (HttpURLConnection) url.openConnection();
    
            urlConnection.connect();
    
            is = urlConnection.getInputStream();
            fos = new FileOutputStream(&quot;day10\beauty3.jpg&quot;);
    
            byte[] buffer = new byte[1024];
            int len;
            while((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
    
            System.out.println(&quot;下载完成&quot;);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(urlConnection != null){
                urlConnection.disconnect();
            }
        }
    }
    

    }

    UDP协议的网络编程

    public class UDPTest {
    
    //发送端
    @Test
    public void sender() throws IOException {
    
        DatagramSocket socket = new DatagramSocket();
    
    
    
        String str = &quot;我是UDP方式发送的导弹&quot;;
        byte[] data = str.getBytes();
        InetAddress inet = InetAddress.getLocalHost();
        DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
    
        socket.send(packet);
    
        socket.close();
    
    }
    //接收端
    @Test
    public void receiver() throws IOException {
    
        DatagramSocket socket = new DatagramSocket(9090);
    
        byte[] buffer = new byte[100];
        DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
    
        socket.receive(packet);
    
        System.out.println(new String(packet.getData(),0,packet.getLength()));
    
        socket.close();
    }
    

    }

    day29

    反射初步了解

    在Person类外部,不可以通过Person类的对象调用其内部私有结构。可以通过反射,创建Person类的对象,调用对象指定的属性、方法,甚至可以通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。

    疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?

    建议:直接new的方式。
    什么时候会使用:反射的方式。 反射的特征:动态性

    疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?

    不矛盾。

    关于java.lang.Class类的理解

    1. 类的加载过程:
      程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
    2. 换句话说,Class的实例就对应着一个运行时类。
    3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式 来获取此运行时类。

    获取Class的实例的方式

    (前三种方式需要掌握)

    • 方式一:调用运行时类的属性:.class
    • 方式二:通过运行时类的对象,调用getClass()
    • 方式三:调用Class的静态方法:forName(String classPath) (常用)
    • 方式四:使用类的加载器:ClassLoader (了解)

        

    public void test3() throws ClassNotFoundException {
        //方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);
    
    //方式三:调用Class的静态方法:forName(String classPath)
    Class clazz3 = Class.forName(&quot;com.atguigu.java.Person&quot;);
    //clazz3 = Class.forName(&quot;java.lang.String&quot;);
    System.out.println(clazz3);
    
    System.out.println(clazz1 == clazz2);
    System.out.println(clazz1 == clazz3);
    
    //方式四:使用类的加载器:ClassLoader  (了解)
    ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class clazz4 = classLoader.loadClass(&quot;com.atguigu.java.Person&quot;);
    System.out.println(clazz4);
    
    System.out.println(clazz1 == clazz4);
    

    }

    Class实例可以是哪些结构

    万事万物皆对象

    @Test
    public void test4(){
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;
    
    int[] a = new int[10];
    int[] b = new int[100];
    Class c10 = a.getClass();
    Class c11 = b.getClass();
    // 只要数组的元素类型与维度一样,就是同一个Class
    System.out.println(c10 == c11);
    

    }

    newInstance

    newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

    要想此方法正常的创建运行时类的对象,要求:
    1.运行时类必须提供空参的构造器
    2.空参的构造器的访问权限得够。通常,设置为public。

    在javabean中要求提供一个public的空参构造器。原因:
    1.便于通过反射,创建运行时类的对象
    2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

    了解类的加载器

    • 对于自定义类,使用系统类加载器进行加载
    • 调用系统类加载器的getParent():获取扩展类加载器
    • 调用扩展类加载器的getParent():无法获取引导类加载器
    • 引导类加载器主要负责加载java的核心类库,无法加载自定义类的。

    类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。

    @Test
    public void test1(){
        //对于自定义类,使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        //调用系统类加载器的getParent():获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);
        //调用扩展类加载器的getParent():无法获取引导类加载器
        //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);
    
    ClassLoader classLoader3 = String.class.getClassLoader();
    System.out.println(classLoader3);
    

    }

    Properties

    Properties:用来读取配置文件

    @Test
    public void test2() throws Exception {
    
    Properties pros =  new Properties();
    //此时的文件默认在当前的module下。
    //读取配置文件的方式一:
    //FileInputStream fis = new FileInputStream(&quot;jdbc.properties&quot;);
    //FileInputStream fis = new FileInputStream(&quot;src\jdbc1.properties&quot;);
    //pros.load(fis);
    
    //读取配置文件的方式二:使用ClassLoader
    //配置文件默认识别为:当前module的src下
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream(&quot;jdbc1.properties&quot;);
    pros.load(is);
    
    
    String user = pros.getProperty(&quot;user&quot;);
    String password = pros.getProperty(&quot;password&quot;);
    System.out.println(&quot;user = &quot; + user + &quot;,password = &quot; + password);
    

    }

    总结:

    1. 注意两种方式读取文件的路径不同,方式一在当前module下,方式二在当前module的src下
    2. 使用getResourceAsStream读取文件
    3. 将要读去的数据写入一个.properties文件中,创建方式New --> Resource Bundle

    获取运行时类的属性、方法、父类等

    • 获取属性结构
      getFields():获取当前运行时类及其父类中声明为public访问权限的属性
      getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
    • 获取权限修饰符 数据类型 变量名
      getModifiers():权限修饰符
      getType():数据类型
      getName():变量名
    • 获取方法结构
      getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
      getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
    • 获取注解、权限修饰符、返回值类型、方法名、形参列表、抛出的异常

    例如:

    `/*
    @Xxxx
    权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
     */`
    

    getAnnotations(): 获取方法声明的注解
    Modifier.toString(m.getModifiers()): 权限修饰符
    获取的权限修饰符比较特殊, getModifiers()返回的是int类型(如1, 2, 3, 4),这些数字分别代表public, protected等
    使用Modifier.toString将它转化为字符串形式的权限修饰符 getReturnType().getName():返回值类型
    getName()

    形参列表

        Class[] parameterTypes = m.getParameterTypes();
        if(!(parameterTypes == null && parameterTypes.length == 0)){
            for(int i = 0;i < parameterTypes.length;i++){
    
            if(i == parameterTypes.length - 1){
                System.out.print(parameterTypes[i].getName() + &quot; args_&quot; + i);
                break;
            }
    
            System.out.print(parameterTypes[i].getName() + &quot; args_&quot; + i + &quot;,&quot;);
        }
    }
    

    抛出的异常

        Class[] exceptionTypes = m.getExceptionTypes();
        if(exceptionTypes.length > 0){
            System.out.print("throws ");
            for(int i = 0;i < exceptionTypes.length;i++){
                if(i == exceptionTypes.length - 1){
                    System.out.print(exceptionTypes[i].getName());
                    break;
                }
    
            System.out.print(exceptionTypes[i].getName() + &quot;,&quot;);
        }
    }
    

  • 相关阅读:
    Mongo Windows 基本使用入门
    ASP.NET Web API 使用Swagger使用笔记
    Smtp协议与Pop3协议的简单实现
    详解boost库中的Message Queue .
    Bencode编码解析的C++实现
    pugixml使用教程
    在QT中使用Irrlicht引擎的方法与步骤
    七大查找算法
    java保留小数点两位的4种方法
    [Node] nvm 安装 node 和 npm
  • 原文地址:https://www.cnblogs.com/kylinxxx/p/13353033.html
Copyright © 2020-2023  润新知