java基础回顾

发布于:2025-02-10 ⋅ 阅读:(117) ⋅ 点赞:(0)

定义 遍历 常用方法

数据类型

[Java]基本数据类型与引用类型赋值的底层分析_java引用数据类型的赋值-CSDN博客

基本数据类型
(存栈中,没有地址这个概念!相互赋值给的就是值!如:int c=4;int b=c;b=5; //b是5,c仍是4)

byte short char[在计算时会转为int] int long float double boolean

引用数据类型
(地址存栈中,指向对象可能存在堆或者方法区的常量池中)

String:两种定义方法 1.str=new String("hello")放到堆中 2.str="hello"放到常量池中

数组

包装类:

对于整数型包装类定义方式:

valueOf或自动装箱(底层为valueOf)Integer a=3; Integer b=Integer.valueOf(3); a==b;

Integer、Short、Byte、Character、Long 都有一个常量池,常量池的范围是-128~127之间。如果此方法定义的包装类的值在这个范围内,则会直接返回内部缓存池中已经存在的对象的引用(new的方式jdk9后就淘汰了)

对于浮点型包装类:

对于浮点型Float和Double这样的包装类,没有常量池机制,不管传入的值是多少,都会new一个新的对象,放入堆区。 

常用类

集合

数组

1.定义:【基本引用 一维二维】

不要沿用c++的想法。

java的数组分基本数据类型和引用类型

基本数据类型
如int[] s=new int[10]; 这里面创建了长度为10的整型数组,并且自动为每个元素创建了对象,即能找到相关地址

引用类型
如写了个class student(){}, 在student[] s=new student[10]中只是new了数组空间,我们还要对s[0]-s[9]每一个创建对象,否则数组为null 。指向堆区位置为null。​

还需要遍历,给赋值。

for(int i=0;i<10;i++)
{
    s[i]=new student();
}

一维数组: 

int[] a=new int[5];
int[] a=new int[]{1,2}; --> 简写int[]a={1,2};

注意:大小和初始值不可以兼得 int[] a=new int[2]{1,2};错误!

二维数组:

int[][] a=new int[n][]; //默认为null

int[][] a=new int[n][n]; //默认全为0

int[][] a=new int[][]{ {1,2},{},{}3}; --> 简写int[][] a={ {1,2},{},{}3};

注意:仍然大小和初始值不可以兼得 int[][] a=new int[n][]{ {1,2}}错误!!

           在不初始化时,第一个参数必须写,指定有几个数组。

2.遍历:

内有属性length 可以修改数据元素【基本和引用均可以】

可以用增强for循环进行遍历,但是这个方法不可以修改数据元素,对于基本数据元素我们不可以对其重新赋值(会创建一个新的地址,新的元素);但是对于引用类型来说,里面的属性我们是可以改的。

管理数组的常用类Arrays 经常用的方法 Arra3344ys.sort(nums) 进行排序

字符串

1.定义  new String();  new String(char数组); str="hello"; new String(byte数组,从哪开始,长度):
new String(byte数组)

2.遍历:

方式一:

字符串的遍历方式

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

        s.charAt(i);

}

字符串没有继承迭代器,不能用增强for循环

方式二:

通过s.toCharArray()转成字符数组,再通过增强for循环

for(char c:s.toCharArray()) { }

3.常用方法 【截取1个 查找2个 替换1个 格式化字符串1个】

  • s.length()
  • s.charAt()
  • s.toCharArray()
  • s.indexOf(String neddle) 查找s字符串第一次与neddle字符串匹配的位置,不匹配返回-1
  • s.substring(beginIndex,endIndex) 左闭右开,可以省略endIndex默认截到最后
  • s.contains("") 看字符串中是否包含这个子串 
  • s.replace(str1,str2) 将s中的str1替换为str2,并返回一个新字符串
  • String.valueOf(Object e) 将对象转为字符串
  •  String.format(format, arguments)是 Java 中用于创建格式化字符串的方法。
    %s: 字符串
    %d: 整数
    %f: 浮点数 -> String result = String.format("Formatted number: %.2f", number); 四舍五入
    %x: 十六进制整数
    %t: 日期和时间 
    -->联想 System.out.printf(format, arguments)方法是 Java 中用于格式化输出字符串的一个非常方便的工具。
    格式说明符与上面一致。可以进行位数限值(%.2f),宽度控制(右对齐%10d,左对齐%-10s)
    System.out.printf("PI to four decimal places: %.4f\n", pi); // 输出: PI to four decimal places: 3.1416 四舍五入
    System.out.printf("%-10s%-10d\n", "Bob", 80);

4.ascii码和java字符相互转换

'c'-0 --> ascii码 

char(79) --> 字符

常用类

Arrays

  • Arrays.sort
  • Arrays.asList(i,j,k)
  • Arrays.copyOf(要复制那个数组,复制数组的长度) Arrays.copyOf(ch,ch.length)

Math

  • Math.min()
  • Math.max()
  • Math.round(浮点数) 四舍五入
  • Math.floor(浮点数) 小于等于
  • Math.ceil(浮点数) 大于等于
  • Math.abs(x) 返回x的绝对值
  • Math.sqrt(double x) x开方
  • Math.pow(doble a,double b) a的b次方

Date

new Date() 表示当前时间到毫秒 :Mon Feb 03 21:15:09 CST 2025
new Date(Long time) time是到1970年的毫秒数
new Date(System.currentTimeMillis()+1000 * 60 * 60 * 24):Tue Feb 04 21:38:13 CST 2025

包装类

对于包装类 == equals 方法:

  • ==比较地址
    如果右边是常量,会拆包,比较值是否一致
  • equals都进行了重写,比较值是否一致

Java基础数据类型之包装类equals和==详解_包装蕾==和equals-CSDN博客

1.Integer

  • Integer.MAX_VALUE
  • Integer.valueOf
  • Integer.parseInt()
  • Integer.intValue()
  • Integer.doubleValue()
  •  Integer.toString

2.Character 

  • Character.isLetter('2') //false 判断字符是否为字母
  • Character.toUpperCase(c)
  • Character.toLowerCase(c)

互相转换:

StringBuffer

  • StringBuffer n=new StringBuffer(" ");
  • n.append(" ")
  • n.charAt()
  • n.setCharAt(位置,要设置的字符)
  • n.deleteCharAt
  • n.equals 这个方法没有重写,比较的是引用类型的地址 可以将stringBuffer先转为String再比较
  • n.toString()转换成String类型
  • n.reverse()反转字符串

StringBuilder 

集合

 

1.collections接口 -> list set 子接口 (继承迭代器可以用增强for循环)

  • list实现类:ArrayList  Vector  LinkedList
  • set实现类:HashSet  LinkedHashSet  TreeSet

构造方法 这个接口下的在实例化时构造方法可以不填,也可以填Collection接口的实现eg.Set<Integer> set=new HashSet(); or Set<Integer> set=new HashSet(Collection);
     Set<Integer> set=new HashSet(Arrays.asList(1,2,3));

collection: 

遍历

1.迭代器遍历
Iterator iterator = collection.iterator();
while(iterator.hasNext()) {
      Object next = iterator.next();
      System.out.println(next);
 }
注意:
1)迭代器遍历是遍历的集合当前的快照。所以用集合的增加删除方法,迭代器不知道状态改变了,所以变出现异常。
迭代器遍历时,删除可以用iterator的remove方法。
2)但是增加就不行了,iterator是不会复位的。
3)NoSuchElementException(无索引所以不报指针越界)
可以用迭代器删除,增加修改不可以【可以修改引用类型的属性】

2.增强for

底层就是迭代器,为简化迭代器出现
由于底层是迭代器,但是没拿到迭代器,所以不可以调用迭代器的remove方法,所以增强for在遍历时不可以修改删除添加元素【可以修改引用类型的属性】

3.Lambda表达式(java 8开始)

可以使用 集合.forEach方法遍历
调用forEach要传入一个实现了Consumer接口的类,这个类必须要重写accept方法
forEach方法中把遍历的每个元素交给accept方法处理
也就是说accept(Object o)其中o就是集合中的每个对象
可以用Lambda表达式简化 集合.foreach(遍历的元素 -> 进行的操作)
底层也是迭代器,也不可以修改删除添加【可以修改引用类型的属性】

实现collection接口都可以用的方法:

  • add添加一个元素 
  • remove删除一个元素 set,remove("second")
  • contains查看有没有这个元素 --> 调用equals方法来比较两者是否一致
  • size看集合大小
  • isEmpty()

2.list:

遍历

List实现了collection接口,所以以下三种都可以用

  • 迭代器遍历 --> 遍历时要删除用这个
  • 增强for循环 --> 只是遍历
  • Lambda表达式 --> 只是遍历

还有两个特有的

  • 普通for循环 -->遍历时操作索引用这个(增删改)
  • 列表迭代器 --> 遍历时删除添加修改元素用这个

普通for循环:

 for(int i=0;i<list.size();i++) {
    System.out.println(list.get(i));
}

列表迭代器:

修改:set()改变当前遍历的元素,也就是next()返回的元素

ListIterator<String> listIterator = list.listIterator();  

 // 遍历列表 while (listIterator.hasNext()) {  
 String item = listIterator.next();  
 // 在遍历到"B"时修改为"NewB"  
 if ("B".equals(item)) {  
 listIterator.set("NewB"); // 修改当前元素 }  
 }  

 System.out.println(list); // 输出: [A, NewB, C]  
 }  
}  

增加:add()在当前遍历元素的后面添加,也就是next()返回元素的后面

ListIterator<String> listIterator = list.listIterator();  

 // 遍历列表 while (listIterator.hasNext()) {  
 String item = listIterator.next();  
 // 在遍历到"B"时插入新元素"newElement"  
 if ("B".equals(item)) {  
 listIterator.add("newElement"); // 安全添加元素 }  
 }  

 System.out.println(list); // 输出: [A, B, newElement, C]  
 }  

实现list接口都可以用的方法:

  • add(index,ele) 如add(3,1);在坐标我3的位置插入1这个元素
  • get(i) 得到坐标为i的元素
  • remove(int index):移除指定index位置的元素,并返回此元素 当然也可以调用collection中的remove(object) 看哪个匹配
  • 从 Java 8 开始,List 接口也提供了一个默认方法sort(Comparator<? super E> c)

3.map接口

map定义:

map接口成员:

  • interface Entry -> getKey() getValue()
  • Set<Map.Entry<k,v>> entrySet
  • Set<K> keySet()
  • Collection<String> values()
  • object方法重写
  • 特有方法

HashMap是Map的实现类,主要成员有以下:

  • 内部类Node,实现了Map.Entry接口(非public)Node就是放键值对
  • 内部类EntrySet extends AbstractSet<Map.Entry<K,V>>
  • 内部类KeySet extends AbstractSet<K>
  • 内部类Values extends AbstractCollection<V>
  • 存放Node的数组,Node<K,V>[] table

实现了以上三个背景为蓝色的抽象方法,就是返回相应的内部类,这三个内部类都继承了AbstractXXX,这个AbstractXXX实现了set/collection接口,所以可以返回。

HashMap<K,V> map=new HashMap();

把每次扩容参数赋值为默认扩容参数 this.loadFactor = DEFAULT_LOAD_FACTOR

map.put("no1","hsp");
map.put("no2","张无忌");

当我们调用put方法时,实际上是在调用下面的方法,

putVal(hash(key), key, value, false, true)

putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

参数has:根据key算出hash值,也就是在数组table的索引

参数onlyIfAbsent:如果为 true,只有在键不存在时才插入新的值。(set中填true)

参数evict:如果为 true,可能会触发清除老旧的数据

大致流程:

  • 首先检查 table 是否为 null 或者长度为 0。如果是,则需要调用 resize() 方法扩大 HashMap 的容量
  • 计算哈希索引,为空插入Node
  • 有的话,处理冲突(通过链表和红黑树一起处理),大致可以理解为在同一索引位置形成链表,可以放重复的键

【HashSet的底层就是维护了一个HashMap,其中set中add的值就是Node的key,value就是一个默认值,然后调用putVal方法,传入时onlyIfAbsent为true,有冲突的话就不插入值了,保证值唯一】

遍历

​对应以上三个方法,下面详细怎么用

其实就是提供一个遍历索引(不同方法返回不同的索引类型),在foreach时,会遍历table,然后返回对应数据。

        Map<String,String> map = new HashMap<>();
        map.put("no1","hsp");
        map.put("no2","张无忌");
        Set<Map.Entry<String, String>> entries = map.entrySet();
        System.out.println(entries.getClass()); //class java.util.HashMap$EntrySet
        for(Map.Entry<String,String> entry:entries) {
            System.out.println(entry.getClass()); //class java.util.HashMap$Node
            System.out.println(entry.getKey()+" "+entry.getValue());
        }

map实现类:HashMap Hashtable->Properties
                     Hashtable和HashMap几乎一样,但是Hashtable是线程安全的

map的遍历:

遍历map的key或value

for(int key:map.keySet()) {

        int value=map.get(key);

}

for(String s:map.values()) {
            System.out.println(s);

 }

遍历map的键值对Entry

for(Map.Entry<Integer,Integer> entry:map.entrySet()) {

        entry.getKey(); entry.getVaule();

}

map接口方法:

  • put (key,value) 添加键值对
  • containsKey查看有没有这个建
  • get通过键值得到值
  • getOrDefault (key,默认值) 
  • size看map大小

4.Collections

是一个操作Set,List,Map等集合的工具。提供了一系列静态方法对集合排序,查询,修改等。

常用方法:

  • Collections.sort(List,Comparator) 修改原List,不是返回新列表
  • Collections.reverse(List) 修改原List,不是返回新列表

栈和队列

1.栈

stack栈继承了vector 

常用方法:

  • push
  • pop 将栈顶元素弹出并且返回栈顶元素
  • peek
  • size
  • empty

2.队列

queue接口 下有 deque接口

单端队列PriorityQueue 【堆实现,会排序将优先级最小的每次出队,也可以构造时传入比较器,规定排序方式】
常用方法:

  • offer
  • poll
  • peek
  • size
  • isEmpty

双端队列LinkedList,ArrayDeque 【也可以作为单端队列】
常用方法:【其中XXXFirst指的就是单端队列对应方法】

  • offerFirst
  • offerLast
  • pollFirst
  • pollLast
  • peekFirst
  • peekLast
  • size
  • isEmpty

Java三大特征:封装 继承 多态

封装

类定义 class Person{} [类可被public final修饰]

类成员

  • 属性[public protected private static final修饰]
  • 方法[public protected private static final 修饰]
  • 代码块[static]
  • 构造方法[public protected private修饰]
  • 内部类

public:当前类+当前包+其他包(项目可见性)

protected:当前类+当前包+其他包中的子类(子类可见性)【子类访问父类属性,不可以通过父类对象来访问,用子类对象可以访问】

缺省:当前类+当前包(包可见性)

private:当前类(类可见性)

继承

class B extends A{}

继承父类的所有类成员(构造方法和代码块除外),包括私有,只是不能访问

方法的重写

重写继承的方法时要求方法名,参数完全相同,返回值类型小于父类的,访问权限不能小于父类方法权限,子类不能抛出比父类更多的异常,只能抛出比父类更小的异常,或者不抛出异常

子类的私有方法虽然是继承的,但是是不可见的,所以“重写”父类的私有方法是没有问题的

【重写和重载的区别】

均是方法名相同,形参列表

重载发生在用一个类中或继承关系中,方法名相同,形参列表不同

重写发生在继承关系中,方法名相同,形参列表相同,访问权限子类大于等于父类 返回值类型和异常类型子类小于等于父类

调用父类super

默认继承java.lang.Object

其中有方法

1.toString:

  • 当我们打印一个对象的引用时,实际是默认调用这个对象的toString()方法
  • 当打印的对象所在类没有重写Object中的toString()方法时,默认调用的是Object类中toString()方法。
  • 返回对象的类型+@+哈希表
  • 当我们打印对象所 在类重写了toString(),调用的就是已经重写了的toString()方法,一般重写是将类对象的属性信息返回。包装类和String都重写了toString所以才是返回字符串形式的值

2.equals

3.hashCode

简单地说,hashCode() 方法通过哈希算法生成一个整数值。
相对的对象(通过equals方法判断)返回相同哈希值,不同对象返回不同哈希值不是必须的。重写了equals也要重写hashCode,让equals相同的对象返回的hashCode也是一样的。
为了让hashCode尽量独一无二,可以调用Object.hash(sex,age,name)会根据sex,age,name来产生hashCode,如果这三者一样,会产生一样的hashCode

多态 

编译类型 运行类型
抽象类和接口

抽象类:(名词)

权限修饰符 abstract class 类名{}
抽象类就是一个类,普通类有的它都有,多一个抽象方法 权限修饰符 abstract 返回值类型 方法名(参数列表); 只有声明没有实现
当然有构造方法,可以写,方便后面继承了它的类给其属性赋值

接口:(动词)

interface 接口名 [extends 父接口1,父接口2,...] {

        //常量定义 都属于全局静态常量,默认由public static final来修饰
        //方法定义
        抽象方法 void fly();默认前面用public abstract修饰
        普通的静态方法 public static void showInfo1(){}
        default修饰的成员变量 default void showInfo2(){}

}
没有构造方法,没有需要其赋值的属性

class 实现类 [extends 父类] implements 接口{}

接口可以多个实现,弥补了java只能单继承的缺点。

类的加载

jvm内存分布

jvm的内存主要分为三部分:方法区。其实每个部分还包括分区。

我们知道java是多线程的,其中方法区线程共享的。线程私有数据区

类的加载 [加载 链接 初始化] (也就是编译好的class文件jvm是怎么将其运行起来的)

何时被加载:

  • 第一次new创建对象实例时
  • 第一次创建子类对象实例,父类也会被加载
  • 第一次使用类的静态成员时(静态属性,静态方法)

1.加载:

  • 指将类的字节码从 .class 文件读取到方法区变成可以跑起来的数据结构,有存放类的结果信息,包括所有字段、所以方法等描述信息(就是类型,访问权限等)之外,还有个常量池(不同class中的常量池是共享的),包含了在编译期生成的字面量(量本身-也就是值)和符号引用(在解析过程变成直接引用)(现在是空的)
  •  所有放到方法区的class文件都会生成一个Class类的实例,代表这个加载的类
    【 反射的基础 ! 我们所有的类,其实都是Class类的实例,他们都拥有相同的结构-----  Field数组、Method数组。而各个类中的属性都是Field属性的一个具体属性值,
     方法都是Method属性的一个具体属性值 】

【简述:jvm将编译好的class文件放到方法区,并根据此文件生成class实例,开辟此类的常量池空间】

2.链接[验证 准备 解析]

  • 验证:class文件有没有,class对象生成没有
  • 准备:为static变量在方法区中分配空间,并默认初始值
    【放入常量池:int为0,boolean为false ,final的就为其值,如果是static对象属性,存的是其符号引用 如:static Myclass instance; 存是Myclass这个类名
       放入方法区:static类对象 初始值为null (之后再在初始化阶段创建实例后再改为其地 址)】
  • 解析:常量池符号引用变为直接引用(也就是将类名变为此类的class实例,就是getClass返回的对象)

3.初始化

  • 先static代码块 static属性(无优先级,看顺序)
  • (先在堆区分配对象空间)再非static代码块 非static属性(无优先级,看顺序)
  • 构造器

有继承关系的,先父类子类的静态部分执行完,再父类非static构造器,子类非staitc构造器,在这个期间,如果调用的方法在子类父类中都有,会遵循就近原则

  • 会先将父类的静态属性代码块执行
  • 再子类的静态属性代码块执行
  • 之后实例化父类非static代码块 非static属性构造器(与子类中super(参数)的调用一致,要没有默认super();)
  • 再实例化子类非static代码块 非static属性构造器

例子:

注意:常量池共享

public class A {  
    public static final String a = "hello"; // 在 class A 的常量池中  
}  

public class B {  
    public static final String b = "hello"; // 在 class B 的常量池中  
}  

public class Main {  
    public static void main(String[] args) {  
        System.out.println(A.a == B.b); // 输出 true,表明它们指向同一个对象  
        System.out.println(A.a.equals(B.b)); // 输出 true,表明内容相同  
    }  
}

当类已经加载过一遍了,new对象执行什么? 

1.在堆区分配空间

2.非static属性默认初始化

3.执行非静态初始化快执行非静态属性赋值(无优先级按顺序)3.执行构造函数

(保证了static是只有一份共享的!)

反射与注解

在运行时动态的操作类中的成员,如;运行时根据数据库提供的类名或方法名,或者基于字符串变量来动态实例化对象或调用方法 -->反射来提供此需求

1.获得类的class对象(3种)

1.<类名>.class 
在编译时就确定好获得的类,属于静态引用 Class<User> userClass=User.class;
用此方法获得类对象时,不会立即触发初始化,只有创建实例或访问静态成员时才会被触发
(也就是只会进行类加载的前两步:1.加载 2.链接)

2.实例对象.getClass()
已经有实例对象,Class<?> clazz=user.getClass();
这里泛型用通配符,改为User会报错,原因就是user对象实际的运行类型是什么只能在运行时获取,在编译时确定会报错。
new对象时就把所有的加载都执行了,getClass只是拿到这个class对象

3.Class.forName("完整类名") 静态方法
Class<?> aClass = Class.forName("com.yang.pojo.Car"); 这里泛型也是统配符原因同上
运行时动态的加载指定的类,把加载全部执行了

2.class对象的属性

1.带Declared
一个类中声明的所有成员,不管其访问权限如何,不包含父类成员

2.不带Declared
仅用于获取公共成员,包括从父类继承的公共成员

eg. 获得当前类的所有属性:Field[] declaredFields = aClass.getDeclaredFields();
      获得指定的属性: Field name = aClass.getDeclaredField("name");
      获的指定的方法:(后面可以加指定参数)

                                  Method function = aClass.getDeclaredMethod("function");
                                 Method function = aClass.getDeclaredMethod("function", String.class);
     

      获得指定的构造器:(后面可以加指定参数)

                                 Constructor<?>declaredConstructor=aClass.getDeclaredConstructor();

                                    aClass.getDeclaredConstructor(String.class,Integer.class);
                                 

3.得到的成员的属性

1.getName
2.getType 
   注意:有泛型的属性在获得type时,获得的是将泛型擦除的类型;想要得到具体的类型需调用方法getGenericType()

3.设置权限  setAccessible(true); fina都可以修改了 超级强

类中静态成员的访问和修改

1.静态属性
对于public的静态属性,可以直接访问和修改

        Field field = aClass.getDeclaredField("publicStaticField");

        field.set(null,12);
        System.out.println(field.get(null));
对于非public的静态属性,要先设置权限为true,才可以访问和修改

        Field field = aClass.getDeclaredField("privateStaticField");
        field.setAccessible(true);
        field.set(null,100);
        System.out.println(field.get(null));

2.静态方法

对于public的静态方法,可以直接执行

         Method function = aClass.getDeclaredMethod("function", String.class);
         function.invoke(null,"yeah");

对于非public的静态方法,要先设置权限为true,才可以执行

        Method function = aClass.getDeclaredMethod("function", String.class);
        function.setAccessible(true);
        function.invoke(null,"yeah");

类中非静态成员的访问和修改

1.创建类的实例
得到构造器->执行newInstance

2.访问和修改与静态方法一致,只不过将null换为创建的实例

注解:注解就是打了一个标记,在框架中常用,打上标记然后通过反射判断是否为空或者值是否为我想要的,对不同成员进行不同操作。

通用技巧【算法 架构 设计】 

算法:

取数字哪几位:

        //a%(10的n次) --> 取a数字的最后n位
        //a/(10的n次) --> 砍掉a数字的最后n位
        //eg. 1234 得到2 1234/100%10  得到23 1234/10%100

查找:二分法 (l+r)/2 --> 防止加法溢出 (r-l)>>1+l

滑动窗口:在数组中找一个区间最大最小时用到此技巧
                 本质就是确定好窗口的尾位置,然后调整首位置 ,保证尾位置加进来一定合法
保留思想:移除元素可以用此思想

归并排序的思想:merge

数组模拟:例题螺旋矩阵,一定要先确定好遍历边界!

前缀和:求区间和

问题由上一个问题可以得到 推理时都要想到

dp动态规划:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

积累:

1.关于兔子繁殖问题

斐波那契数列: super家养了一对刚出生的兔子, 兔子出生第4月开始每月都会生一对小兔子, 小兔子出 生后同样第4月开始也会每月生一对兔子 super想知道 如果兔子不死 n月后家里会有多少对兔子

dp[i] 为第i月有多少对兔子

dp[i]=dp[i-1]+dp[i-3] 当月兔子为上个月兔子以及本月要繁殖多少只兔子,本月要繁殖的兔子就是上上个月的兔子数。

2.数字拆分 

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。

dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

3.背包问题

01背包:dp[i][j] 从[0-i]物品中选择,放到容量为j的背包中,价值最大为多少

               dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) 选择物品i,不选物品i

               先遍历物品或者背包都可以 根据左上方更新数据

bfs 【广度优先搜索 】

1.探索方向 四个方向int[][] dir={ {-1,0},{0,-1},{1,0},{0,1}};  上下左右

2.标志已经探索过 boolean[][] visted=boolean[n][n];

3.不可以加入->地图边界 访问过 不能走

dfs(回溯) 【递归搜索树 把所有情况都列举一遍找到合法的】

回溯法解决的问题都可以抽象为树形结构
集合的大小就构成了树的宽度,递归的深度就构成了树的深度

回溯三步曲:1.确定传入参数和返回类型 2.终止条件 3.单层逻辑

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

字符串查找替换

架构: 

有两个计划表 1.项目计划表 2.项目开发任务表

项目流程:

1.立项【开会前通知大家花两天时间填写立项建议表,第二天晚上开会确定立项且安排可行性分析】

2.可行性分析【这里只分析技术上的可行性,包含1.核心功能2.数据结构3.核心功能实现逻辑 花费1天时间来完成 晚上开会定档并讨论所有功能安排需求文档】

3.项目设计阶段:需求文档 功能流程图 产品原型图
   需求文档:【花费1天时间 当晚定档讨论具体细节问题 确定每个人都了解项目具体功能(抽查保证) 再安排功能流程图】

   

   功能流程图:【花费两天时间 第二天晚上定档流程图 确定每个人知道流程是什么样的 安排产品原型图(美)和架构】

4.架构:【花费三天时间 第三天晚上确定架构 】

架构要写清晰!(用什么数据上层什么函数都写清晰!按他们不懂流程来写)

1.分层 dao service view utils pojo...(上层可以用下层的函数数据,下层不可以用上层的东西)

2.设计view层

根据需求文档和功能流程图,将view层设计好 调用service层什么方法...

2.设计service层

view用到的方法要实现,调用dao层,返回给view层数据

3.设计dao层

5.分工【编写代码 在答辩前写完】

填写项目开发任务表

设计: 

工具类的编写思路

  • 将构造方法私有化 将方法设计为静态方法
  • 有些属性的初始化可以放到静态代码块中执行

java用法【我不熟练的】

1.Lambda表达式

Lambda 表达式的语法由以下几个部分组成:

  • 参数列表,括在圆括号中(如果只有一个参数可以省略)。
  • 箭头符号 ->
  • 方法体,可以是一个单独的表达式或一个语句块。
(parameters) -> expression

使用:

1.可以用Lambda表达式来定义比较器 

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");  
Collections.sort(names, (a, b) -> a.compareTo(b)); // 按升序排序

2. 与集合一起使用

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");  
names.forEach(name -> System.out.println(name));
//name就是List的元素,就是泛型里的内容

在定义比较器时,为什么lambda表达式没有实现comparator接口就行?

简单说,就是因为comparator是一个只有一个抽象方法的接口,通过上下文推断是是要实现comparator接口,且只有一个抽象方法,所以就生成了一个实现了那个抽象方法的comparator实现类。那个抽象方法就是lambda表达式。

详细:来自AI

编译器如何处理 Lambda 表达式

在 Java 中,当你使用 Lambda 表达式创建一个方法时,编译器会根据上下文推断出这个 Lambda 表达式应该实现哪个接口。在你的例子中,总结如下:

  1. 上下文推断:当你调用 Collections.sort(people, (p1, p2) -> Integer.compare(p1.age, p2.age)); 时,Collections.sort() 方法接受一个 Comparator 类型的参数。编译器看到这个参数,便知道你要提供一个 Comparator 实例。

  2. 函数式接口:由于 Comparator 是一个函数式接口(即只有一个抽象方法),所以编译器可以将 Lambda 表达式 (p1, p2) -> Integer.compare(p1.age, p2.age) 视为 Comparator 的实现。

  3. 生成的字节码:最后,编译器会生成相应的字节码,来替代这个 Lambda 表达式,而在运行时会用匿名类的方式完成的,与传统实现的 Comparator 类的效果是一样的。

简单示例

为了更好地理解,可以想象一下,Lambda 表达式 (p1, p2) -> Integer.compare(p1.age, p2.age) 本质上是隐式地被编译成一个实现了 Comparator<Person> 接口的匿名内部类,类似于:

Collections.sort(people, new Comparator<Person>() { 
@Override public int compare(Person p1, Person p2) 
{ return Integer.compare(p1.age, p2.age);
 }
 });

尽管我们在使用 Lambda 时看起来代码更加简洁,但在编译后,Java 实际上创建了一个匿名类来完成实际的工作。

所以:

总结来说,编译器能理解你使用 Lambda 表达式是在实现一个函数式接口(如 Comparator),这使得代码更加简洁、易读,而不需要显式地创建一个实现类。

2.比较器 Comparator

Comparator 接口中有几个重要的方法,主要包括:

  1. compare(T o1, T o2):比较两个对象,并返回一个整数:

    • 如果返回负值,表示 o1 小于 o2
    • 如果返回零,表示 o1 等于 o2
    • 如果返回正值,表示 o1 大于 o2。那o1就会在o2后面,所以要倒序的话就o2-o1就好了
import java.util.*;  

class Person {  
    String name;  
    int age;  
    
    Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
    
    @Override  
    public String toString() {  
        return name + ": " + age;  
    }  
}  

public class Main {  
    public static void main(String[] args) {  
        List<Person> people = new ArrayList<>();  
        people.add(new Person("Alice", 24));  
        people.add(new Person("Bob", 30));  
        people.add(new Person("Charlie", 20));  

        // 使用 Comparator 按年龄排序  
        Comparator<Person> ageComparator = new Comparator<Person>() {  
            @Override  
            public int compare(Person p1, Person p2) {  
                return Integer.compare(p1.age, p2.age);  
            }  
        };  

        // 也可以用 Lambda 表达式定义 Comparator  
        Comparator<Person> ageComparatorLambda = (p1, p2) -> Integer.compare(p1.age, p2.age);  

        // 排序并打印  
        Collections.sort(people, ageComparatorLambda);  
        people.forEach(System.out::println);  
    }  
}

3.io流【掌握】

文件:File file=new File("1.txt");  file.createNewFile();//会创建在user.dir(当前项目路径)下面

  • io流读取文件是在user.dir也就是当前项目路径下找到的,而不是classpath下文件。(System.getProperty("user.dir");
  • ClassLoader是从classpath下找的,其中resources会直接放到target/classes下,不用加前面的路径。

io流是用来处理文件读写的,在创建流时,传入一个文件名【也就是文件的地址了】或者文件地址,其成员path会指向这个地址,这样这个流就有了可以对这个文件读或写的能力。

【输入in 输出out的主体是内存,输入是指输入到内存,也就是读;输出是内存输出,也就是写】

io流体系图:基于四个父类派生出来的

按照处理对象不同也可以分为:

节点流(普通流):可以直接从数据源或目的地读写数据。

处理流(包装流):不直接连接到数据源或目的地,是“处理流的流”。通过对其它流进行封装,目的主要是简化操作和提高程序的性能。

节点流处于IO操作的第一线,所有操作必须通过他们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。

1.定义

四大头领:

        InputStream

        OutputStream

        Reader

        Writer

File相关的:

        FileInputStream

        FileOutputStream

        FileReader

        FileWriter

缓冲流相关的:

        BufferedInputStream

        BufferedOutputStream

        BufferedReader

        BufferedWriter

转换流相关的:

        InputStreamReader

        OutputStreamWriter

打印流相关的:

        PrintStream

        PrintWriter

对象相关的:

        ObjectInputStream

        ObjectOutputStream

数据类型的:

        DataInputStream

        DataOutputStream

字节数组相关的:

        ByteArrayInputStream

        ByteArrayOutputStream

压缩和解压缩相关的:

        GZIPInputStream

        GZIPOutputStream

线程相关的:

        PipedInputStream

        PipedOutputStream

2.方法:

通用:每个流在用之后一定要关!!!try-catch-finally(关闭之前判空处理)
           所有的输出流,都有flush方法,此方法就是把管道中的数据全部写到对应对象中。在最后一次写时进行flush就可以,之前写的时候会将数据往前推,写到对应对象中。

字节流:

字节:8bit 在文件中一个字母占1个字节,文字占3/4个字节

OutputStream(抽象类):

  • write(int b) 用ascii码写一个字符
  • write(byte[] b) 将多个ascii码存在数组中,转为String写到对象中
  • write(byte[] b,int off,int len)

   FileOutputStream:实现上述方法(节点流)
        构造方法FileOutputStream(String path,是否续写);  FileOutputStream(FIle file,是否续          写);
        是否续写不填,默认为false,第一次写的时候会先清空之前内容。 

  BufferedOutputStream:实现上述方法(包装流)

        构造方法BufferedOutputStream(OutputStream out) out要传入一个字节流,对其进行            包装。

InputSteam(抽象类):

  • int read(): 读取一个字节的数据。读取成功,则返回读取的字节对应的正整数(即Ascii码),将其转char即可;读取失败,则返回-1。 
  • int read(byte[]): 读取一定量的字节数,并存储到字节数组中。数组的每一位是把字节转成了数字(即Ascii码)将其转为String即可,读取成功,则返回读取的字节的个数;读取失败,则返回-1。(容易乱码,把汉字读一半)

    FileInputStream:实现上述方法
        构造方法FileInputStream(String path);  FileInputStream(FIle file);

    BufferedInputStream:实现上述方法

        构造方法BufferedInputStream(InputStream in) in要传入一个字节流,对其进行包装

字符流:

Writer(抽象类) :

  • write(int b) 用ascii码写一个字符
  • write(char[] b) / write(String s) 
  • write(char[] b,int off,int len) / write(String s,int off,int len) 

    FileWriter:实现上述方法
        构造方法与FileOutputStream是一致的

        FileWriter类用于向文件中读取数据,并且每次操作的数据单元为“字符”,属于向文本              文件读取字符数据的便捷类。

    BufferedWriter:实现上述方法,新增newLine方法 添加换行符

        构造方法BufferedWirter(Writer out)

 Reader(抽象类):

  • read(): 读取单个字符并返回。读取成功,则返回字符对应的正整数,读取失败,则返回-1。

  • read(char[]): 将数据读取到字符数组中。读取成功,则返回读取字符的个数,读取失败,则返回-1

    FileReader:实现上述方法

    BufferedReader:实现上述方法

        新增readLine方法 读取成功,则返回读取的一行文本内容;读取失败,则返回null。 

        构造方法BufferedReader(Reader in)    

对象流 ... 【待补充】

4.输入

Scanner scanner=new Scanner(System.in);//定义一个scanner System.in为键盘输入

scanner.next();//键盘输入的字符串

scanner.nextInt();键盘输入的整数

5.关键字

final 是常量 final int VAL=3;

6.线程【掌握】

【概念 定义 生命周期(Thread.sleep()  t.join()  线程安全... ) 线程通信】

概念:

打开一个java程序,开启一个jvm进程。

jvm中,堆和方法区是线程共享的,栈是每个线程都有一个单独的栈区。

并发和并行 并发是一个cpu在不断切换干什么 微观上不是同时进行 宏观上是同时进行的
并行 是多个cpu同时在做不同线程 微观和宏观上都是同时进行的
你写的多线程是并发还是并行 都有可能 看cpu情况

线程的调度策略
分时调度模型(所有线程轮流使用cpu的执行权 并且平均的分配每个线程占用的cpu时间)
抢占式调度模型(让优先级高的线程以较大概率得到cpu的执行权,如果优先级一致,就随机选一个线程)

定义线程:

  • 继承Thread 重写run方法 创建线程对象 线程对象.start()开启线程
  • 实现Runnable接口
    重写run方法
    创建接口实现对象 MyThread myThread = new MyThread();
    创建线程对象Thread thread = new Thread(myThread);
    线程对象.start()开启线程

线程对象.start开启线程-> 这个方法就是开辟一个新栈然后将run方法压入栈底。
直接调用run就是跑run方法而已。

用户线程和守护线程
用户线程就是当前我们创建的都是,守护线程是当用户线程全部结束后,自动结束,如jvm垃圾回收线程。
将用户线程设置为守护线程 myThread.setDaemon(true) 

线程生命周期:

1.Thread.sleep

Thread.sleep(ms)

注意:这是静态方法,如果用实例来调用,最后执行也会改为Thread.sleep是让当前线程(这个代码出现在哪个线程中,当前线程就是这个线程)睡眠,而不是实例对象的线程。

唤醒线程(实例方法):唤醒的线程.interrupt 本质是抛出异常 打断睡眠
注意这个异常不可以抛出,抛出就到jvm了,main让你抛,这里就不让抛了。

public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
        thread.interrupt(); //唤醒thread线程 抛出一个InterruptedException 

    }
}
class MyThread implements Runnable{
    public void run() {
        System.out.println(Thread.currentThread().getName()+"start");
        try {
            Thread.sleep(100*365*24);
        } catch (InterruptedException e) { //catch此异常,继续往下执行
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"wake up");

    }
}

终止线程:打标志 不可以用stop早已弃用

2.join方法

join方法完成线程合并 是一个实例方法 t.join
main中写 t.join,main方法会让t线程全部执行完后才执行main方法

也可以加时间,t.join(ms) 
t.join(10) 代表t线程合并到当前线程10ms,阻塞当前线程10ms,但不一定是这个时间,如果在指定的阻塞时间中,t线程结束了,当前线程阻塞也会解除。 

public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
        try{
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i); //2.把main线程的0-9打印
        }

    }
}
class MyThread implements Runnable{
    public void run() {
       for (int i = 0; i < 10; i++) {
           System.out.println(Thread.currentThread().getName()+" "+i); //1.把线程thread的0-9打印
       }
    }
}

3.在线程准备状态可以抢夺cpu使用权 抢夺时,cpu看线程优先级,或者线程注定将自己的时间片让出去。

1.优先级 
默认情况下优先级是5,最低是1,最高是10
setPriority //更改线程的优先级;
getPriority //获取线程的优先级;

public class Demo {
    public static void main(String[] args) {
        System.out.println(Thread.MIN_PRIORITY); //1
        System.out.println(Thread.MAX_PRIORITY); //10
        System.out.println(Thread.NORM_PRIORITY); //5
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        int priority = Thread.currentThread().getPriority();
        System.out.println(priority); //10
        
    }
}

2.yield 
静态方法Thread.yield(),当前线程让位,当然也有可能一让位还是这个线程抢到了

public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}
class MyThread implements Runnable{
    public void run() {
       for(int i = 1; i <= 100; i++) {
           if(Thread.currentThread().getName().equals("t1")&&i%10==0) {
               System.out.println(Thread.currentThread().getName()+"让位"+i);
               Thread.yield();
           }
           System.out.println(Thread.currentThread().getName() + ":" + i);
       }

    }
}

4.线程安全
【考虑线程安全:1.多线程环境 2.线程有共享数据 3.共享数据修改】

并发执行的多个线程在某个时间内只允许一个线程在执行并访问共享数据
线程排队 实现同步机制

语法格式
synchronized(必须是这几个线程共享的对象【堆或方法区】) {//同步代码块}

当t1执行同步代码块时,会拿到相对的对象锁,当t2相要执行这段同步代码块时,没有相应对象锁,排队等待t1执行完同步代码释放对象锁。
当t1执行同步代码块拿到锁,t2只是执行这个类的普通方法(没加synchronized)不用拿锁哈,直接运行就好。

可以直接加到方法上 同步代码块就是整个方法
如果直接加到实例方法上就是锁这个类实例this(保护实例变量的安全)
如果加到static方法上就是锁这个类(保护静态变量的安全)

1 锁不能为空,即用作锁的对象不能为空,这种错误很容易暴露,一般都能避免;

2 锁应该是final的,此处并非要求用作锁的对象的引用一定要声明为final,而是指一个对象要用作锁的话,其引用不应该存在被修改指向的可能,否则引用指向变了,对象锁也就变了,锁可能会失效。
针对第2点,我们可以将对象锁的引用声明为final以避开问题。除此之外,需要小心的便是,如果使用基本数据类型的封装类型,如Integer、Long等对象做锁时,一定要非常小心,对此类引用的赋值操作,在一些情况下(常量池的因素)其实是一次引用重指向的操作,会引起锁失效

public class Demo {
    public static void main(String[] args) {
        GetMoney getMoney = new GetMoney();
        Thread t1 = new Thread(getMoney);
        Thread t2 = new Thread(getMoney);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class GetMoney implements Runnable{
    private static int total=1000;
    private static Object lock=new Object();
    private int money=total;
    @Override
    public void run() {
        synchronized(lock){
           if(total>=money) {
               total=total-money;
               System.out.println(Thread.currentThread().getName()+"取走"+money+",现在还有"+total);
           } else {
               System.out.println(Thread.currentThread().getName()+"没取走"+total);
           }

        }
    }
    
}

线程通信

方法:wait(),notify(),notifyAll() 为Object方法,通过共享对象调用
使用前提:必须在同步代码块中使用,必须这个共享代码中锁的共享对象调用

obj.wait() ->处在obj对象上活跃的线程进入等待状态,且释放了对象锁,直到调用共享对象的notify方法进行了唤醒,唤醒后拿到时间片会接着上次调用wait()方法的地方继续执行。
obj.notify()是唤醒优先级最高的线程,如果优先级一致随机唤醒一个。
obj.notifyAll()是唤醒所有在共享对象上等待的对象。

注意:Thread.sleep()方法是一直占用对象锁,而obj.wait()方法会释放对象锁。
           wait也可以有参数,进入超时等待状态,被唤醒了或者时间到了会自动进入阻塞状态。


例子:t1,t2线程交替输出

public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class MyThread implements Runnable {
    private int i=1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                //记得唤醒t1线程
                //t2线程执行过程中把t1唤醒了,但是由于t2仍然占用对象锁,所以即使t1醒了,也不会往下执行。
                this.notify();
                if(i>100) {break;}
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " "+i++);
                try {
                    //让其中一个线程等待,这个线程可能是t1,也可能是t2
                    //假设t1线程等待
                    //t1线程进入无限等待状态,并且等待的时候,不占对象锁
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

7.网络编程【掌握】

常用网络类:(java.net包下)

InetAddress类:用来封装计算机的ip地址和dns(域名)
常用静态方法:   

public static InetAddress getByName(String host)

传入目标主机的名字或IP地址得到对应的InetAddress对象,其中封装了IP地址和主机名(或域名)

public static InetAddress getLocalHost()

得到本机的InetAddress对象,其中封装了IP地址和主机名

常用实例方法:

public String getHostAddress()

获得IP地址

public String getHostName()

获得主机名

URL类:URL由4部分组成:协议、存放资源的主机域名、资源文件名和端口号。如果未指定该端口号,则使用协议默认的端口(可以实现简单爬虫)

URL标准格式为:<协议>://<域名或IP>:<端口>/<路径>。其中,路径后面可以加参数以"?"分割,参数用&分隔

URL url = new URL("http://www.jd.com:8080/java/index.html?name=admin&pwd=123456#tip");
// 获取协议,输出:http
System.out.println("协议:" + url.getProtocol());

Socket编程: 

socket就是位于应用层和传输层之间的传输工具,首先除应用层外其他网络层会协作找到找连接的pc端,然后建立tcp或者udp连接
如果是tcp连接,会先三次握手,之后客户端开始发数据给服务端,应用层通过socket来传输数据给对应端。

socket实现TCP编程:

实现服务端:用ServerSocket实现

实现客户端:用Socket实现

应用-实现单向通信:

服务端

public class Test01 {
    public static void main(String[] args)  {
        ServerSocket serverSocket = null;
        Socket accept = null;
        try {
            // 实例化ServerSocket对象(服务端),并明确服务器的端口号
            serverSocket = new ServerSocket(8888);
            System.out.println("服务端已启动,等待客户端连接..");
            // 使用ServerSocket监听客户端的请求
            accept = serverSocket.accept();
            // 通过输入流来接收客户端发送的数据
            InputStreamReader reader = new InputStreamReader(accept.getInputStream());
            char[] chars = new char[1024];
            int len = -1;
            while ((len = reader.read(chars)) != -1) {
                System.out.println("接收到客户端信息:" + new String(chars, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (accept != null) {
                try {
                    accept.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端

public class Test02 {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            // 实例化Socket对象(客户端),并明确连接服务器的IP和端口号
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
            socket = new Socket(inetAddress, 8888);
            // 获得该Socket的输出流,用于发送数据
            Writer writer = new OutputStreamWriter(socket.getOutputStream());
            writer.write("为中华之崛起而读书!");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

应用-实现双向通信: 

服务端:

public class Test01 {
    public static void main(String[] args) {
        Socket socket = null;
        ServerSocket serverSocket = null;
        try {
            // 1.创建ServerSocket对象(客户端),并明确端口号
            serverSocket = new ServerSocket(8889);
            System.out.println("服务端已启动,等待客户端连接..");
            // 2.使用ServerSocket监听客户端的请求
            socket = serverSocket.accept();
            // 3.使用输入流接收客户端发送的图片,然后通过输出流保存图片
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len = -1;
            FileOutputStream fos = new FileOutputStream("./socket/images/yaya.jpeg");
            while ((len = inputStream.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
            // 4.给客户端反馈信息
            Writer osw = new OutputStreamWriter(socket.getOutputStream());
            BufferedWriter bw = new BufferedWriter(osw);
            bw.write("图片已经收到,谢谢");
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5.关闭资源
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端:(注意一定要把输出流线关闭,这样服务器才知道图片发完,不会接着read了,结束读循环,发送信息)

public class Test02 {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            // 1.实例化Socket对象(客户端),并设置连接服务器的IP和端口号
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 8889);
            // 2.通过输入流读取图片,然后再通过输出流来发送图片
            OutputStream outputStream = socket.getOutputStream();
            FileInputStream fis = new FileInputStream("./socket/images/tly.jpeg");
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = fis.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
            // 注意:此处必须关闭Socket的输出流,来告诉服务器图片发送完毕
            socket.shutdownOutput();
            // 3.接收服务器的反馈
            InputStreamReader isr = new InputStreamReader(socket.getInputStream());
            BufferedReader reader = new BufferedReader(isr);
            String lineStr = null;
            while ((lineStr = reader.readLine()) != null) {
                System.out.println("服务器端反馈:" + lineStr);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭资源
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

(1)Socket是最底层的通信机制

(2)HTTP是Socket之上的封装层,例如 HttpClient 等。

(3)Servlet是Java 对 HTTP的封装层,目的是为了更好的处理HTTP请求(包括参数)和HTTP响应,毕竟HTTP就分为请求和响应两大部分。

(4)Tomcat是Servlet容器。Servlet必须运行在容器之上。

servlet虽然可以处理数据和请求,但是数据单线程是,还是要用其他socket技术来实现实时的两者通信。

用servlet当然也可以实现聊天室(尝试做一下,多个用户登录,进入消息发送页面点击提交将ajax请求发送给servlet,将消息存到共享资源中,但是不刷新页面的话收到的就是旧消息,可以用websocket实现同步,当然用前端的轮询方法也可以事项poll()  还有其他技术如sse,在springboot中集成了这个技术)

Java实现简单聊天室【含源码】_java聊天室-CSDN博客

8.Properties文件的常用读取方法【掌握】

1.使用InputStream和Properties.load()方法(最常用)
   也可以用Reader(io流是从user.dir下读)

Properties props = new Properties();  
try (InputStream input = new FileInputStream("config.properties")) {  
    props.load(input);  
} catch (IOException ex) {  
    ex.printStackTrace();  
}  

2.使用ClassLoader读取资源
 如果资源在类路径下,可以用加载器来读取(加载器读是从target下读)

Properties props = new Properties();  
try (InputStream input = YourClass.class.getClassLoader().getResourceAsStream("config.properties")) {    
        props.load(input);  
} catch (IOException ex) {  
    ex.printStackTrace();  
}

3.使用ResourceBundle 
  ResourceBundle是一个更高级的方式,适用于国际化支持并能加载属性文件(从target下找)

ResourceBundle bundle = ResourceBundle.getBundle("config");  
String value = bundle.getString("key");

java中运行脚本语言【了解】

ScriptEngineManager 是javax.script的一部分,用于在 Java 程序中执行脚本代码。它支持多种脚本语言,比如 JavaScript、Groovy、Ruby 等。

  1. 创建脚本引擎:可以使用 ScriptEngineManager 来创建特定脚本语言的 ScriptEngine 实例。

  2. 管理脚本引擎:它能够管理系统中可用的所有脚本引擎,包括列出可用的引擎,并提供创建引擎的功能。

import javax.script.ScriptEngine;  
import javax.script.ScriptEngineManager;  
import javax.script.ScriptException;  

public class ScriptEngineExample {  
    public static void main(String[] args) {  
        // 创建 ScriptEngineManager 实例  
        ScriptEngineManager manager = new ScriptEngineManager();  
        
        // 获取 JavaScript 引擎  
        ScriptEngine engine = manager.getEngineByName("JavaScript");  
        
        // 执行简单的 JavaScript 代码  
        try {  
            // 计算表达式  
            Object result = engine.eval("10 + 20");  
            System.out.println("计算结果: " + result); // 输出: 计算结果: 30  

            // 定义变量并执行  
            engine.eval("var greeting = 'Hello, world!';");  
            String greeting = (String) engine.get("greeting");  
            System.out.println(greeting); // 输出: Hello, world!  
        } catch (ScriptException e) {  
            e.printStackTrace();  
        }  
    }  
}

java中运行java语言【需编译】【了解】

ResourceBundle类

简单来说就是解决本地和国际问题的读取properties资源的类,如果后面不指定资源语言,就会分析本地操作系统是什么语言,选择此语言版本的。【从target下找】

// 通过以下代码获取属性文件中的配置信息
ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");

java.util.ResourceBundle使用详解 - fancyebai - 博客园


网站公告

今日签到

点亮在社区的每一天
去签到