Java基础必备

目录

一、基础

Object类的方法

hashCode() 和 equals() 之间的关系

抽象类与接口

重载、重写

toString()、String.valueOf()、(String)

关于金钱和经纬度的存储

为什么 Java 中只有值传递?

异常

字符串拼接的过程

访问修饰符

Java 创建对象的几种方式

Java基本数据类型

IO流

Java序列化与反序列化是什么

new一个对象和利用反射创建一个对象的区别

二、集合

线程安全的集合类

fail-fast与fail-safe

遍历List集合的方式

数组和 List 之间的转换

ArrayList 和 LinkedList 的区别

hashMap 1.7 和 hashMap 1.8 的区别

ArrayList 和 Vector 的区别

多线程场景下使用 ArrayList

List 和 Set 的区别

HashSet 的实现原理

HashSet与HashMap的区别

HashMap、HashTable

ConcurrentHashMap、HashTable

comparable 、 comparator

Collection 、 Collections

HashMap

LinkedHashMap

HashTable

ConcurrentHashMap

三、JDK新特性

Lambda表达式

functional interface(函数式接口)

方法引用

Stream

概述

创建

使用 

四、网络

TCP 三次握手和四次挥手

TCP与UDP 协议的区别

TCP 协议如何保证可靠传输

输入URL 到页面加载过程

状态码

Cookie与Session

URI 和 URL 的区别

OSI与TCP/IP各层的功能结构


一、基础

Object类的方法

Object类是Java中所有类的基类。位于java.lang包中,一共有13个方法。如下图:

hashCode() 和 equals() 之间的关系

以下是阿里规范要求:

equals() :用来判断两个对象是否相等。

hashCode():是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

  • 不会创建“类对应的散列表”“hashCode() 和 equals() ”没有半毛钱关系的
  • 会创建“类对应的散列表”:如果两个对象相等,那么它们的hashCode()值一定相同;如果两个对象hashCode()相等,它们并不一定相等

抽象类与接口

  1. 接口是多实现的,是对行为的规范,如鸟会飞;抽象类是单继承的,是模板的设计,如猫是动物。
  2. 接口定义的方法不能实现,默认是public abstract;抽象类可以实现部分的方法
  3. 接口的成员变量是public static final,且要有初始值;抽象类不是
  4. 接口无构造器、静态方法、代码块;抽象类是有的

重载、重写

构造器不能被继承,因此不能被重写,但可以被重载。

  • 重写:发生在父类与子类之间。

    • (2同)方法名、参数列表必须相同;

    • (2小)子类抛出的异常小于父类抛出的异常;子类的返回值类型小于父类的返回值类型;

    • (1大)子类的访问修饰权限大于父类的

  • 重载:发生在一个类中。

    • 同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。

    • 重载对返回类型和抛出的异常没有要求

toString()、String.valueOf()、(String)

  • toString():可能会抛空指针异常

    在这种使用方法中,因为java.lang.Object类里已有public方法.toString(),所以java对象都可以调用此方法。但在使用时要注意,必须保证object不是null值,否则将抛出NullPointerException异常。采用这种方法时,通常派生类会覆盖Object里的toString()方法。
  • String.valueOf():推荐使用,返回字符串“null”

    String.valueOf()方法是推荐使用的,因为它不会出现空指针异常,而且是静态的方法,直接通过String调用即可,只是有一点需要注意,如果为null,String.valueOf()返回结果是字符串“null”。而不是null。
  • (String)强转:不推荐使用

    (String)是标准的类型转换,将Object类型转为String类型,使用(String)强转时,最好使用instanceof做一个类型检查,以判断是否可以进行强转,否则容易抛出ClassCastException异常。需要注意的是编写的时候,编译器并不会提示有语法错误,所以这个方法要谨慎的使用。

关于金钱和经纬度的存储

  • 金钱:MySQL使用decimal,对应Java的BigDecimal类型;

    • 工具类:BigDecimalUtil

    • BigDecimal常用方法:加(add)、减(subtract)、乘(multiply)、除(divide)、比较大小(compareTo)、保留小数(setScale)

  • 经纬度:MySQL使用varchar,对应Java的String

为什么 Java 中只有值传递?

  • 实参(实际参数) :用于传递给方法的参数,必须有确定的值。

  • 形参(形式参数) :用于定义方法,接收实参,不需要有确定的值。

  • 值传递、引用传递:

    • 值传递 :方法接收的是实参值的拷贝,会创建副本。

    • 引用传递 :方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。

  • 如果传递的参数是基本类型:传递的就是基本类型的字面量值的拷贝,会创建副本。

  • 如果传递的参数是引用类型:传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。(但拷贝的地址所指向的对象和之前是同一个对象)

异常

异常的体系结构:

Java异常处理方式:

 

字符串拼接的过程

String s = new String("abc")+new String("def");

一共创建了6个对象:

在字符串常量池中"abc"、"def"。

在堆内存中new String("abc")、new String("def")。

在拼接的过程中创建了StringBuilder对象,利用StringBuilder对象的append方法进行拼接。StringBuilder对象的toString方法被重写为new String(""),所以在toString方法中还创建了一个对象。

访问修饰符

修饰符同一个类中同一包中的类不同包中的子类所有的类
public(类、接口、变量、方法)
protected(变量、方法)×
default(类、接口、变量、方法)××
private(变量、方法)×××

Java 创建对象的几种方式

  •  new创建新对象
  • 通过反射机制
  • 采用clone机制:java.lang.Cloneable 是一个标示性接口,不包含任何方法.clone ()方法在 Object 类中定义的一个Native方法
  • 通过序列化机制

Java基本数据类型

  • 自动装箱:将基本类型用它们对应的引用类型包装起来;Integer i = 10 等价于 Integer i = Integer.valueOf(10)

  • 自动拆箱:把包装类型转化为基本数据类型;int n = i 等价于 int n = i.intValue();

基本类型占用内存基本类型默认值包装类型(默认值为null)包装类型常量池
byte1字节0Byte【-128,127】
short2字节0Short【-128,127】
int4字节0Integer【-128,127】
long8字节0LLong【-128,127】
float4字节0.0fFloat
double8字节0.0dDouble
boolean1字节falseBooleanTrue或False
char2字节\u0000Character【0,127】

IO流

  • 分类:

    • 按照流的流向划分:输入流和输出流;程序(内存)作为参照物,程序从外部读取称为输入(Input),程序向外部写数据成为输出(Output)

    • 按照操作单元划分:字节流和字符流;

    • 按照流的角色划分:节点流和处理流。

  • 抽象父类:

    • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

    • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

Java序列化与反序列化是什么

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程;实现Serialable接口

new一个对象和利用反射创建一个对象的区别

1、new 无法访问私有属性,反射可以通过setAccessible()来取消隐藏访问权限

2、new 必须知道类名,反射不需要知道类名也可以创建对象

3、new方式创建的对象比反射创建的对象快。

二、集合

线程安全的集合类

  • Vector:使用synchronized修饰,因为效率较低,现在已经不太建议使用。
  • hashTable:使用synchronized修饰,不建议使用。
  • ConcurrentHashMap:
    • JDK1.7:ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段,每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。)
    • JDK1.8 :用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。

fail-fast与fail-safe

1、fail-fast: 直接在容器上进行,在遍历过程中,一旦发现容器中的数据被修改,就会立刻抛出 ConcurrentModificationException 异常从而导致遍历失败。

常见的使用 fail-fast 方式的容器有 HashMap 和 ArrayList 等。

2、fail-safe: 这种遍历基于容器的一个克隆。因此对容器中的内容修改不影响遍历,牺牲了数据的一致性

常见的使用 fail-safe 方式遍历的容器有 ConcurrentHashMap 和 CopyOnWriteArrayList。

遍历List集合的方式

1、for 循环遍历:基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。

2、迭代器遍历:Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。

3、foreach 循环:foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

数组和 List 之间的转换

  • 数组转 List:使用 Arrays. asList(array) 进行转换。
  • List 转数组:使用 List 自带的 toArray() 方法。

ArrayList 和 LinkedList 的区别

1、数据结构:ArrayList基于数组实现,内存是连续的;LinkedList基于双向链表实现,内存不连续

2、随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList需要移动指针从前往后依次查找。

3、增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

4、内存空间:ArrayList 需要预留额外的存储空间;LinkedList需要储存前后指针

hashMap 1.7 和 hashMap 1.8 的区别

ArrayList 和 Vector 的区别

这两个类都实现了 List 接口,他们都是有序集合

  • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
  • 性能:ArrayList 在性能方面要优于 Vector。
  • 扩容: Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

多线程场景下使用 ArrayList

1、通过 Collections 的 synchronizedList 方法将 ArrayList 转换成线程安全的容器后再使用

2、使用线程安全的 CopyOnWriteArrayList 代替线程不安全的 ArrayList。

3、为list.add()方法加锁

4、为list.add()方法加锁

List 和 Set 的区别

1、List , Set 都是继承自Collection 接口

2、List :有序可重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

3、Set:无序不可以存储重复,只允许存入一个null元素性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

4、List 支持for循环和迭代器的遍历;set只能用迭代,因为他无序,无法用下标来取得想要的值。

HashSet 的实现原理

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

HashSet如何检查重复

set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合.

HashSet 是基于 HashMap 实现的,底层采用 HashMap 来保存元素

元素的哈希值是通过元素的 hashcode 方法 来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较 equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。

HashSet与HashMap的区别

HashMap、HashTable

1、HashMap是现场不安全的;HashTable是线程安全的

2、HashMap的K只能有一个为null,V可以有多个;HashTable的K和V不能为null

3、HashMap的初始容量是16,按2倍进行扩容;HashTable初始容量是11,按2n+1进行扩容

4、HashMap是储存映射关系;HashTable是储存对象

ConcurrentHashMap、HashTable

  • 底层数据结构:

    • ConcurrentHashMap:jdk1.7(分段数组+链表),jdk8(数组+链表/红黑树)

    • HashTable:数组+链表

  • 实现线程安全的方式:

    • ConcurrentHashMap:

      • jdk7:使用分段锁,对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分的桶

      • jdk8:采用Synchronized + CAS机制 保证了线程的安全,锁的仅是链表或红黑树的首节点,降低了锁粒度,大大提高并发度。

    • HashTable:使用synchronized修改put和get方法

comparable 、 comparator

  • 所属的包:

    • comparable:java.lang包

    • comparator:java.util 包

  • 排序的方法:

    • comparable:compareTo(Object obj)方法

    • comparator:compare(Object obj1, Object obj2)方法

Collection 、 Collections

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。

    它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类/帮助类。

    其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

HashMap

线程不安全;key只能有一个null,value可以有多个null

数据结构:

JDK 7 中,HashMap 由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。

在 JDK 8 中,HashMap 由“数组+链表+红黑树”组成。链表过长,会严重影响 HashMap 的性能,而红黑树搜索的时间复杂度是 O(logn),而链表是糟糕的 O(n)。因此,JDK 8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:

  • 当链表超过 8 且数据总量超过 64 时会转红黑树。
  • 将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。

JDK 8 中 HashMap 的结构示意图:

为什么链表改为红黑树的阈值是 8? 

因为泊松分布(是根据概率统计而选择的)

解决hash冲突的办法有哪些?HashMap用的哪种?

散列法、再哈希法、拉链法(HashMap)

为什么在解决 hash 冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?

因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。

当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。

因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。

HashMap默认加载因子是多少?为什么?

加载因子是表示Hsah表中元素的填满的程度;

加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了,查找的成本越高。

因此0.75是冲突概率与空间利用率的最佳值

HashMap 中 key 的存储索引是怎么计算的?

首先根据key的值计算出hashcode的值,然后根据hashcode计算出hash值,最后通过

hash &(length-1)计算得到存储的位置。

为什么 hash 值要与length-1相与?

hash &(length-1)等价于   hash %(length-1);但&比%的效率高

HashMap数组的长度为什么是 2 的幂次方?

2 的 N 次幂有助于减少碰撞的几率。如果 length 为2的幂次方,则 length-1 转化为二进制必定是11111……的形式,在与h的二进制与操作效率会非常的快,而且空间不浪费。

如果用户传入的容量不是 2 的 n 次方,会自动地将传入的容量转换为 2 的 n 次方。

最终数组容量=比构造方法中传递的数大的最小2的n次幂。 例如:传递10,容量为16,传递28,容量为32

HashMap 的put方法流程?

1、首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;

2、如果数组是空的,则调用 resize 进行初始化(数组长度16);

3、如果没有哈希冲突直接放在对应的数组下标里;

4、如果冲突了,且 key 已经存在,就覆盖掉 value;

5、如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;

6、如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,在链表的尾端插入键值对,若 key 存在,就覆盖掉 value。

一般用什么作为HashMap的key? 

 一般用Integer、String 这种不可变类当作 HashMap 的 key,String 最为常见。

因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的

构造方法:

  • 无参构造方法:默认加载因子为0.75,并不创建数组,而是在首次进行put时,创建数组并且长度(桶的个数)为16

  • 指定容量的构造函数:此时加载因子为0.75

  • 指定容量和加载因子的构造函数:

  • 传递Map的构造函数:

resize()实现过程:

  • 判断旧数组的容量是否大于0,从而确定数组是否初始化过

    • 否:进行初始化

      • 调用无参构造器:使用默认的容量和加载因子。

      • 调用有参构造器:使用构造器中传递的容量(比构造方法中传递的数大的最小2的n次幂)

    • 是:进行扩容,扩容的倍数为2

      • 先生成新的数组,遍历旧数组中每个桶中链表或红黑树上的元素,并重新计算下标,在放到对应的新数组中

遍历方式:

迭代器中循环删除数据安全,其他的都不安全

  • 迭代器(Iterator)方式:

    • EntrySet:遍历的效率高,因为只进行了一次循环

    • KeySet:效率低,需要循环2次

  • For Each 方式:

    • EntrySet:遍历的效率高,因为只进行了一次循环

    • KeySet:效率低,需要循环2次

  • Lambda 表达式:jdk8新增

  • Stream流 :jdk8新增

LinkedHashMap

  • 在HashMap的基础上增加了一条双向链(因此插入和访问的顺序是一致的)

  • 是线程不安全的

  • key只能有一个为null,value可以有多个为null。

HashTable

  • 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的

  • 是线程安全的(内部使用synchronized修饰,锁的是整张表,粒度较大)

  • key和value都不能为空

ConcurrentHashMap

是线程安全的集合;key和value都不可以为null;对于get方法而言,不需要进行加锁,因为对元素和指针都使用volatile进行修饰,保证了可见性。

不同点1.71.8
锁粒度基于segment基于entry节点
线程安全实现采用Segment(分段锁)对所有的桶进行分段,锁的是多个桶。采用Synchronized + CAS机制;锁的仅是链表或红黑树的首节点
储存数据结构数组+链表数组+链表+红黑树
size方法安全保证靠Segment加锁方式实现靠CAS和Volatile或synchronized方式实现

三、JDK新特性

Lambda表达式

作用:

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

语法:

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符 或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表 (其实就是接口中的抽象方法的形参列表)

右侧:指定了 Lambda 体,是抽象方法的实现逻辑(其实就是重写的抽象方法的方法体)

使用:

  • 替代匿名内部类

Runnable 接口

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程启动了");
            }
}).start();

//用lambda
new Thread(() -> System.out.println("线程启动了")).start();

 Comparator 接口

List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
    return o1 - o2;}
});

//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);

//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);
  • 集合迭代

List<String> strings = Arrays.asList("1", "2", "3");

//传统foreach
for (String s : strings) {
    System.out.println(s);
}

//Lambda foreach
strings.forEach((s) -> System.out.println(s));

//or
strings.forEach(System.out::println);
 				
//map
Map<Integer, String> map = new HashMap<>();
map.forEach((k,v)->System.out.println(v));
       
  • 方法的引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。

public class LambdaClassSuper {
    LambdaInterface sf(){
        return null;
    }
}

public class LambdaClass extends LambdaClassSuper {
    public static LambdaInterface staticF() {
        return null;
    }

    public LambdaInterface f() {
        return null;
    }

    void show() {
        //1.调用静态函数,返回类型必须是functional-interface
        LambdaInterface t = LambdaClass::staticF;

        //2.实例方法调用
        LambdaClass lambdaClass = new LambdaClass();
        LambdaInterface lambdaInterface = lambdaClass::f;

        //3.超类上的方法调用
        LambdaInterface superf = super::sf;

        //4. 构造方法调用
        LambdaInterface tt = LambdaClassSuper::new;
    }
}

functional interface(函数式接口)

有且只有一个抽象方法,但可以有多个非抽象方法的接口。

在 java 8 中专门有一个包放函数式接口java.util.function,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • 内置的四大函数式接口 :

1、函数型接口(有一个输入,有一个输出):

public class Demo1 {
    public static void main(String[] args) {
        // Function函数式接口,有一个参数输入,一个输出
//        Function<String, String> function = new Function<String, String>() {
//            @Override
//            public String apply(String str) {
//                System.out.println(str);
//                return str;
//            }
//        };

        // 只要是函数式接口,就可以用lambda表达式简化
        Function<String, String> function = (str)->{return str;};
        System.out.println(function.apply("asd"));
    }
}

2、断定型接口(有一个输入参数,返回只有布尔值)

 

public class Demo2 {
    public static void main(String[] args) {
//        //判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>(){
//            @Override
//            public boolean test(String o) {
//                return o.isEmpty();
//            }
//        };
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}

3、消费型接口(有一个输入参数,没有返回值)

public class Demo3 {
    public static void main(String[] args) {
//        Consumer<String> consumer = new Consumer<String>(){
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer<String> consumer = (str)->{
            System.out.println(str);
        };

        consumer.accept("asd");
    }
}

4、供给型接口(没有输入参数,只有返回参数)

public class Demo4 {
    public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "null";
//            }
//        };

        Supplier<String> supplier = ()->{return "null";};
                System.out.println(supplier.get());
    }
}

方法引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现该接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!((针对于情况1和情况2))
  • 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
  • 如下三种主要使用情况:

对象::实例方法名

//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
    Employee emp = new Employee(1001,"Tom",23,5600);
    Supplier<String> sup1 = () -> emp.getName();
    System.out.println(sup1.get());
    System.out.println("*******************");

    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
}

类::静态方法名

//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
    Function<Double,Long> func1 = d -> Math.round(d);
    System.out.println(func1.apply(12.3));
    System.out.println("*******************");

    Function<Double,Long> func2 = Math::round;
    System.out.println(func2.apply(12.6));
}

类::实例方法名

// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
    Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
    System.out.println(com1.compare("abc","abd"));
    System.out.println("*******************");

    Comparator<String> com2 = String::compareTo;
    System.out.println(com2.compare("abc","abe"));

}

// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
    Employee employee1 = new Employee(1001, "Jerry", 23, 6000);
    Function<Employee,String> func1 = e -> e.getName();
    System.out.println(func1.apply(employee1));
    System.out.println("*******************");
    Employee employee2 = new Employee(1001, "tom", 23, 6000);
    Function<Employee,String> func2 = Employee ::getName;
    System.out.println(func2.apply(employee2));

}

Stream

概述

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来对 Java 集合运算和表达的高阶抽象。

Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

创建

1、集合自带 Stream 流方法

List<String> list = new ArrayList<>();
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();

2、通过 Array 数组创建

int[] array = {1,2,3,4,5};
IntStream stream = Arrays.stream(array);

3、使用 Stream 的静态方法创建

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream = Stream.iterate(0, (x) -> x + 3).limit(3); // 输出 0,3,6

Stream<String> stream = Stream.generate(() -> "Hello").limit(3); // 输出 Hello,Hello,Hello
Stream<Double> stream = Stream.generate(Math::random).limit(3); // 输出3个随机数

使用 

public static void init() {
    personList.add(new Person("张三", 8, 3000));
    personList.add(new Person("李四", 18, 5000));
    personList.add(new Person("王五", 28, 7000));
    personList.add(new Person("孙六", 38, 9000));
}

1、按条件进行匹配(filter):

 筛选员工中已满18周岁的人,并形成新的集合

List<Person> collect = personList
                .stream()
                .filter(e -> e.getAge() > 18)
                .collect(Collectors.toList());
        System.out.println(collect);
/*
[Person(name=王五, age=28, salary=7000), Person(name=孙六, age=38, salary=9000)]
*/

2、遍历/匹配(foreach、find、match)

 筛选员工中已满18周岁的人并遍历输出

private static void foreach() {
    personList.stream()
          .filter(e -> e.getAge() > 18)
          .forEach(System.out::println);
}
/*
Person(name=王五, age=28, salary=7000)
Person(name=孙六, age=38, salary=9000)
*/

 在所有员工中匹配第一个

public static void findFirst() {
    init();
    Optional<Person> firstPerson = personList.stream()
            .findFirst();
    System.out.println(firstPerson);
}
/*
Optional[Person(name=张三, age=8, salary=3000)]
*/

 在所有员工中匹配任意一个

public static void findFirst() {
    init();
    Optional<Person> firstPerson = personList.stream()
            .filter(e -> e.getAge() > 18)
            .findAny();
    System.out.println(firstPerson);
}
/*
Optional[Person(name=张三, age=8, salary=3000)]
*/

3、聚合(max、min、count)

获取Integer集合中的最大值

public static void max() {
    init();
    Comparator<? super Person> comparator = Comparator.comparingInt(Person::getAge);
    Optional<Person> max = personList.stream()
            .max(comparator);
    System.out.println(max);
}
/*
Optional[Person(name=孙六, age=38, salary=9000)]
*/

获取Integer集合中的最小值

public static void min() {
    init();
    Comparator<? super Person> comparator = Comparator.comparingInt(Person::getAge);
    Optional<Person> min = personList.stream()
            .min(comparator);
    System.out.println(min);
}
/*
Optional[Person(name=张三, age=8, salary=3000)]
*/

获取年龄大于10的总数 

public static void count() {
    init();
    long count = personList.stream()
            .filter(e -> e.getAge() > 10)
            .count();
    System.out.println(count);
}
/*
3
*/

4、映射(map、flatMap)

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

为每个员工增加1元的工资

public static void map() {
    init();
    personList.stream()
            .map(e -> {
                e.setSalary(e.getSalary() + 1);
                return e;
            }).forEach(System.out::println);
}
/*
Person(name=张三, age=8, salary=3001)
Person(name=李四, age=18, salary=5001)
Person(name=王五, age=28, salary=7001)
Person(name=孙六, age=38, salary=9001)
*/

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

将两个字符数组合并成一个新的字符数组

private static void test11() {
    String[] arr = {"z, h, a, n, g", "s, a, n"};
    List<String> list = Arrays.asList(arr);
    List<String> collect = list.stream().flatMap(x -> {
        String[] array = x.split(",");
        Stream<String> stream = Arrays.stream(array);
        return stream;
    }).collect(Collectors.toList());
    System.out.println(collect);
}

5、规约(reduce)

是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

public static void reduce() {
    init();
    Optional<Integer> reduce = personList.stream()
            .map(person -> person.getSalary())
            .reduce(Integer::max);
    System.out.println("最高工资:" + reduce);

    Optional<Integer> reduce1 = personList.stream()
            .map(Person::getSalary)
            .reduce(Integer::sum);
    System.out.println("工资之和:" + reduce1);
}

6、收集(toList、toSet、toMap)

取出大于18岁的员工转为map

public static void toMap2() {
    init();
    Map<String, Person> personMap = personList.stream()
            .filter(e -> e.getAge() > 18)
            .collect(Collectors.toMap(Person::getName, y -> y));
    System.out.println(personMap);
}
/*
{王五=Person(name=王五, age=28, salary=7000), 孙六=Person(name=孙六, age=38, salary=9000)}
*/


public static void toMap3() {
    init();
    Map<String, Person> personMap = personList.stream()
            .filter(e -> e.getAge() > 18)
            .collect(Collectors.toMap(Person::getName, y -> y,(p1,p2)->p1));
    System.out.println(personMap);
}

注意:当转化成Map的时候,如果Key一直,使用toMap2的方法的操作会报错,应该使用toMap3的方法,当重复时进行合并

取出大于18岁的员工转为List或Set

public static void toList() {
    init();
    List<Person> personList1 = personList.stream()
            .filter(e -> e.getAge() > 18)
            .collect(Collectors.toList());
    System.out.println(personList1);
}
/*
[Person(name=王五, age=28, salary=7000), Person(name=孙六, age=38, salary=9000)]
*/

7、collect相关静态方法

计数: count

平均值: averagingInt、 averagingLong、 averagingDouble

最值: maxBy、 minBy

求和: summingInt、 summingLong、 summingDouble

统计以上所有: summarizingInt、 summarizingLong、 summarizingDouble

分区:(partitioningBy)将stream按条件分为两个 Map,比如员工按薪资是否高于8000分为两部分。

分组:(groupingBy)将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

/**
 * 统计员工人数、平均工资、工资总额、最高工资
 */
private static void test01(){
    //统计员工人数
    Long count = personList.stream().collect(Collectors.counting());
    //求平均工资
    Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
    //求最高工资
    Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
    //求工资之和
    Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
    //一次性统计所有信息
    DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
    System.out.println("统计员工人数:"+count);
    System.out.println("求平均工资:"+average);
    System.out.println("求最高工资:"+max);
    System.out.println("求工资之和:"+sum);
    System.out.println("一次性统计所有信息:"+collect);
}

8、排序(sorted)

private static void test04(){
    // 按工资升序排序(自然排序)
    List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
            .collect(Collectors.toList());
    // 按工资倒序排序
    List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
            .map(Person::getName).collect(Collectors.toList());
    // 先按工资再按年龄升序排序
    List<String> newList3 = personList.stream()
            .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
            .collect(Collectors.toList());
    // 先按工资再按年龄自定义排序(降序)
    List<String> newList4 = personList.stream().sorted((p1, p2) -> {
        if (p1.getSalary() == p2.getSalary()) {
            return p2.getAge() - p1.getAge();
        } else {
            return p2.getSalary() - p1.getSalary();
        }
    }).map(Person::getName).collect(Collectors.toList());
 
    System.out.println("按工资升序排序:" + newList);
    System.out.println("按工资降序排序:" + newList2);
    System.out.println("先按工资再按年龄升序排序:" + newList3);
    System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}

9、去重(distinct)、分页(limit)、跳过(skip)

private static void test05(){
    String[] arr1 = { "a", "b", "c", "d" };
    String[] arr2 = { "d", "e", "f", "g" };
    Stream<String> stream1 = Stream.of(arr1);
    Stream<String> stream2 = Stream.of(arr2);
    // concat:合并两个流 distinct:去重
    List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
    // limit:限制从流中获得前n个数据
    List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
    // skip:跳过前n个数据
    List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

    System.out.println("流合并:" + newList);
    System.out.println("limit:" + collect);
    System.out.println("skip:" + collect2);
}

四、网络

TCP 三次握手和四次挥手

三次握手:

  • 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
  • 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
  • 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
  • SYN表示建立连接,
  • FIN表示关闭连接,
  • ACK表示响应,
  • PSH表示有 DATA数据传输,
  • RST表示连接重置

三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常

第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常

第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

所以三次握手就能确认双方收发功能都正常,缺一不可

第 2 次握手传回了 ACK,为什么还要传回 SYN?

接收端传回发送端所发送的 ACK 是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传 SYN 则是为了建立并确认从服务端到客户端的通信。”

四次挥手:

  • 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
  • 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
  • 服务器-关闭与客户端的连接,发送一个 FIN 给客户端
  • 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

TCP与UDP 协议的区别

TCP 协议如何保证可靠传输

首先将应用数据包分割成TCP适合发送的数据块,然后对这些数据块进行编号,排序,再把有序数据传送给应用层。然后开始校验和,主要目的是检测数据在传输过程中的任何变化,如果有差错,将丢弃掉这个报文段。然后TCP的接收端会丢弃重复的数据。完事儿之后做一次流量控制,保证只能接收缓冲区能接受的数据,如果来不及处理,则提示对方降低发送的速率,防止丢失。

输入URL 到页面加载过程

  1. 地址栏输入URL
  2. DNS 域名解析IP
  3. 请求和响应数据
    1. 建立TCP连接(3次握手)
    2. 发送HTTP请求
    3. 服务器处理请求
    4. 返回HTTP响应结果
    5. 关闭TCP连接(4次挥手)
  4. 浏览器加载,解析和渲染

状态码

Cookie与Session

Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

Cookie:一般用来保存用户信息,数据保存在客户端(浏览器端)

 ① 我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你把登录的一些基本信息给填了;

② 一般的网站都会有保持登录,也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);

③ 登录一次网站后访问网站其他页面不需要重新登录。

Session:通过服务端记录用户的状态,数据保存在服务器端。

典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

URI 和 URL 的区别

  • URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
  • URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。

URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。

OSI与TCP/IP各层的功能结构

在学习计算机网络的时候,我们一般采用折中的方法。将OSI的七层协议抽成五层。将最上层的应用层和表示层以及会话层抽成一个应用层。

所以自上而下一般都是:应用层、运输层、网络层、数据链路层和物理层。我简单的介绍一下这些层的作用。

应用层:主要任务是通过应用进程间的交互来完成特定的网络应用。应用层定义的是应用进程间的通信和交互的规则,对于不同的网络应用需要不同的应用层协议。比如域名系统DNS,HTTP协议,电子邮件的SMTP协议。

运输层:主要作用是负责向两台主机进程之间的通信提供通用的数据传输服务,应用进程利用该服务传送应用层报文。

网络层:在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换节点,确保数据及时传送。

数据链路层:通常简称为链路层。两台主机之间数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层协议。在节点之间传输数据时,链路层将网络层交下来的IP数据报组成帧,然后在节点上进行传输。

物理层:物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值