我们知道IO流是用来读数据,目的是为了获取其中的信息供我们使用,但是普通的txt文件是杂乱无章的,除非我们规定,自己写。虽然可以但是约束性不高。所以在java中就出现了一些文件,来供我们使用,也就是只要我们获取到了这样的文件,就可以通过固定的技术来获取其中的内容,得到我们想要的信息。
下面就来了解两种特殊的文本文件,一种是properties文件,一种是XML文件,下面一一的作介绍。
学习这两个文件的方法从几个方面出发:
我们先学习Properties这种属性文件。首先我们要掌握属性文件的格式:
.properties
结尾admin=123456
#
表示这样是注释信息,是用来解释这一行配置是什么意思。#一些有用的信息
#Thu Mar 09 19:52:08 CST 2023
admin=root
pwd=root
白象方便面=8
卫龙辣条=12
华为手机=11996
通过上面对属性文件的简单的介绍,我们看到了这些关键字,键值对,间不能重复,值可以重复。从这里是不是能够想到我们学习的集合。那么如果要是有个类能让我们处理properties文件就好了。
喊一声java天下第一,就有了这么一个类。Properties,用来操作properties文件。先看一下类图
可以看到Properties类实现了Map接口,那么肯定就具有map这个双列集合的功能。这样子学习起来就很明白了。
Properties核心作用?
Properties类的对象,用来表示属性文件,可以用来读取属性文件中的键值对。
在了解了他的结构之后就来学习Properties中独有的方法,我们也基本只用他独有的方法。
构造器 | 说明 |
---|---|
public Properties() | 用于构建Properties集合对象(空容器) |
常用方法 | 说明 |
---|---|
public void load(InputStream is) | 通过字节输入流,读取属性文件里的键值对数据 |
public void load(Reader reader) | 通过字符输入流,读取属性文件里的键值对数据 |
public String getProperty(String key) | 根据键获取值(其实就是get方法的效果) |
public Set stringPropertyNames() | 获取全部键的集合(其实就是ketSet方法的效果) |
在学习了方法之后就来使用一下,来操作properties文件。使用步骤:
1、创建一个Properties的对象出来(键值对集合,空容器)
2、调用load(字符输入流/字节输入流)方法,开始加载属性文件中的键值对数据到properties对象中去
3、调用getProperty(键)方法,根据键取值
public static void main(String[] args) throws Exception {
// 1、创建一个Properties的对象出来(键值对集合,空容器)
Properties properties = new Properties();
System.out.println(properties);
// 2、开始加载属性文件中的键值对数据到properties对象中去
properties.load(new FileReader("properties-xml-log-app\\src\\users.properties"));
System.out.println(properties);
// 3、根据键取值
System.out.println(properties.getProperty("赵敏"));
System.out.println(properties.getProperty("张无忌"));
// 4、遍历全部的键和值。
//获取键的集合
Set<String> keys = properties.stringPropertyNames();
for (String key : keys) {
//再根据键获取值
String value = properties.getProperty(key);
System.out.println(key + "---->" + value);
}
properties.forEach((k, v) -> {
System.out.println(k + "---->" + v);
});
}
**使用Properties往属性文件中写键值对:**需要用到下面的几个方法
常用方法 | 说明 |
---|---|
public Object setProperty(String key, String value) | 保存键值对数据到Properties对象中去。 |
public void store(OutputStream os, String comments) | 把键值对数据,通过字节输出流写出到属性文件里去 |
public void store(Writer w, String comments) | 把键值对数据,通过字符输出流写出到属性文件里去 |
往属性文件中写文件的步骤如下:
1、先准备一个.properties属性文件,按照格式写几个键值对
1、创建Properties对象出来,
2、调用setProperty存储一些键值对数据
3、调用store(字符输出流/字节输出流, 注释),将Properties集合中的键和值写到文件中
注意:第二个参数是注释,必须得加;
public static void main(String[] args) throws Exception {
// 1、创建Properties对象出来,先用它存储一些键值对数据
Properties properties = new Properties();
properties.setProperty("admin", "张三");
properties.setProperty("age", "23");
properties.setProperty("class", "1023");
// 2、把properties对象中的键值对数据存入到属性文件中去
properties.store(new FileWriter("properties-xml-log-app/src/users2.properties")
, "i saved many message!");
}
运行之后的文件如下:
# i saved many message! // 这是store 第二个参数加的注释
#Thu Mar 09 20:02:08 CST 2023 // 自动加的时间
admin=张三
age=23
class=1023
本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系。
XML是可扩展的标记语言,意思是它是由一些标签组成 的,而这些标签是自己定义的。本质上一种数据格式,可以用来表示复杂的数据关系。
XML文件有如下的特点:
<标签名>
称为一个标签或者一个元素,一般是成对出现的。
XML示例如下:
<students>
<student id = "1">
<name>张三name>
<age>18age>
<sex>男sex>
student>
<student id = "2">
<name>李四name>
<age>20age>
<sex>女sex>
student>
% $ # & ! ~
]]>
students>
上面XML文件中的数据格式是最为常见的,标签有属性、文本、还有合理的嵌套。XML文件中除了写以上的数据格式之外,还有一些特殊的字符不能直接写。
<,>,&
等这些符号不能出现在标签的文本中,因为标签格式本身就有<>,会和标签格式冲突。如果标签文本中有这些特殊字符,需要用一些占位符代替。< 表示 <
> 表示 >
& 表示 &
' 表示 '
" 表示 "
那么如果需要在文件中表示就需要,像下面一样来对特殊的符号做一个替换:
<data> 3 < 2 && 5 > 4 data>
如果文本中出现了大量的特殊字符,且不想使用特殊字符替换,可以用CDATA区,格式如下:
<data1>
<![CDATA[
3 < 2 && 5 > 4
]]>
</data1>
在idea中的XML文件中输入大写的 CD 就会自动生成
那么XML在实际的开发中有什么作用呢?
在json还没有出现之前,作为一种特殊的数据结构,在网络中进行传输,但是后面就被json代替了,因为JSON效率更高,更好。
第二就是用在配置文件中,现在仍然在使用。也是我们以后常见的地方。
我们有个一个这样的文本文件,那么我们应该怎样获取中间的数据呢?可以通过前面学习过的IO流,然后一点一点的去获取。但是可以发现十分的麻烦。
记住,如果代码想对来说比较的固定,都是一个套路的话,那么我们的前辈,必然封装过,我们只需要面向对象的调用他们的代码就可以。那么XML解析肯定也有,下面就有一个介绍。
其实有很多开源的,好用的XML解析框架,最知名的是DOM4J(第三方开发的)
由于DOM4J是第三方提供的,所以需要把第三方提供的Jar包导入到自己的项目中来,才可以使用。具体步骤如下:
①下载Dom4j框架,官网下载。
②在项目中创建一个文件夹:lib
③将dom4j-2.1.3.jar文件复制到 lib 文件夹
④在jar文件上点右键,选择 Add as Library -> 点击OK
⑤在类中导包使用
DOM4J解析XML文件的思想是:文档对象模型(意思是把整个XML文档、每一个标签、每一个属性都等都当做对象来看待)。Dowument对象表示整个XML文档、root表示根标签一个XML只有一个根
、Element对象表示标签(元素)、Attribute对象表示属性、标签中的内容就是文本。
构造器方法 | 说明 |
---|---|
public SAXReader() | 构建Dom4J的解析器对象 |
public Document read(String url) | 把XML文件读成Document对象 |
public Document read(InputStream is) | 通过字节输入流读取XML文件 |
方法名 | 说明 |
---|---|
Element getRootElement() | 获得根元素对象 |
有根得到Element对象,下面是Element类中的方法:
方法名 | 说明 |
---|---|
public String getName() | 得到元素名字 |
public List elements() | 得到当前元素下所有子元素 |
public List elements(String name) | 得到当前元素下指定名字的子元素返回集合 |
public Element element(String name) | 得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个 |
public String attributeValue(String name) | 通过属性名直接得到属性值 |
public String elementText(子元素名) | 得到指定名称的子元素的文本 |
public String getText() | 得到文本 |
在dom4j中对元素的操作对象如下,记住图片的中的几个单词基本就会了解析。当然Element中还可以套element
public static void main(String[] args) throws DocumentException {
// 1. 获取到解析器对象
SAXReader reader = new SAXReader();
// 2.得到document对象
Document read = reader.read(new File("javaEE-day10\\xml\\my.xml"));
// 2. 获取到根对象,根中是一个一个的元素
Element rootElement = read.getRootElement();
// 3. 获取到根中的元素遍历
List<Element> elements = rootElement.elements();
// 每一个student
for (Element element : elements) {
// 获取id中的内容
String id = element.attribute("id").getValue();
// 得到Student中的每一个元素
for (Element element1 : element.elements()) {
// 获取其内容
String text = element1.getText();
// 获取属性的名称
String name = element1.getName();
System.out.println(id + ": " + name + " - " + text);
}
}
}
其实这个dom4j中也有写入的方法,其实很没有必要,其实我们做一个字符串然后自己通过IO写入文件即可,不用在创建一堆类,调用一堆方法。我直接写不香吗?所以下一个知识点。
如果自己亲自的去解析了这个文件,我们会发现,我们需要知道里面有几层标签,这样我们才有法遍历,要不然就是一个XML文件一个代码。那这不带累死。那么有没有一个办法去限制,XML中的内容呢?bing 狗, 当然。就是约束文件啦。
XML约束指的是限制XML文件中的标签或者属性,只能按照规定的格式写。
比如我在项目中,想约束一个XML文件中的标签只能写<书>、<书名>、<作者>、<售价>这几个标签,如果写其他标签就报错。
怎么才能达到上面的效果呢?有两种约束技术,一种是DTD约束<老技术>、一种是Schame约束<更新之后>。
如下图所示book.xml中引入了DTD约束文件,book.xml文件中的标签就受到DTD文件的约束
DTD文件解释
<!ELEMENT 书架(书+)> 表示根标签是<书架>,并且书架中有子标签<书>
<!ELEMENT 书(书名、作者、售价)> 表示书是一个标签,且书中有子标签<书名>、<作者>、<售价>
<!ELEMENT 书名(#PCDATA)> 表示<书名>是一个标签,且<书名>里面是普通文本
<!ELEMENT 作者(#PCDATA)> 表示<作者>是一个标签,且<作者>里面是普通文本
<!ELEMENT 售价(#PCDATA)> 表示<售价>是一个标签,且<售价>里面是普通文本
如下图所示,左边的book2.xml文件就受到右边schame文件(.xsd结尾的文件)的约束。我们也会发现这个文件可以是一个网址,所以就很方便。我们只需要知道我们应当,怎么做就可以了。
第二行中的xmlns 全称是xmlNameSpeace , 可以理解为和java中的import的功用一样,后面的xsi,可以认为是起的一个别名。
想搞清楚什么是日志,可以通过下面几个问题来了解的。
而日志就可以帮我们解决以上的问题。所以日志就好比生活中的日记,日记可以记录生活中的点点滴滴;而程序中的日志,通常就是一个文件,里面记录了程序运行过程中产生的各种数据。
日志技术有如下好处
有很多日志框架给开发者使用。所谓日志框架就是由一些牛人或者第三方公司已经做好的实现代码,后来者就可以直接拿过去使用。
日志框架有很多种,比如有JUL(java.util.logging)、Log4j、logback等。但是这些日志框架如果使用的API方法都不一样的话,使用者的学习成本就很高。为了降低程序员的学习压力,行内提供了一套日志接口,然后所有的日志框架都按照日志接口的API来实现就可以了。
这样程序员只要会一套日志框架,那么其他的也就可以通过用,甚至可以在多套日志框架之间来回切换。比较常用的日志框架,和日志接口的关系如下图所示
下面就来学Logback日志框架,也是业界中使用最为广泛的。
Logback日志分为下面几个模块
由于Logback是第三方提供的技术,所以首先需要将Jar包引入到项目中,具体步骤如下
在网上找到slftj-api.jar、logback-core.jar、logback-classes.jar
这三个jar包,复制一下
在当前模块下面新建一个lib文件夹,把刚刚复制的三个jar包都粘贴到此处
从资料中找到logback.xml
配置文件,将此文件复制粘贴到src目录下(必须是src目录)
也可以自己创建一个resource目录,但是要和src同级,并且右键选择下图的选项,这样idea就可以识别了,以后什么配置文件呀都在这。
然后就可以开始写代码了,在代码中创建一个日志记录日对象 <固定 >,通过LOGGER对象操作
logback.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>D:/javaEE_log/test.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>D:/javaEE_log/test-%i-%d{yyyy-MM-dd}-.log.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
1、控制日志的输出情况:如,开启日志,取消日志
-->
<root level="debug">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
public static final Logger LOGGER = LoggerFactory.getLogger("当前类名");
认识一下日志的五个级别:
日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级依次升高):
日志级别 | 说明 |
---|---|
trace | 追踪,指明程序运行轨迹 |
debug | 调试,实际应用中一般将其作为最低级别,而 trace 则很少使用 |
info | 输出重要的运行信息,数据连接、网络连接、IO操作等等,使用较多 |
warn | 警告信息,可能会发生问题,使用较多 |
error | 错误信息, 使用较多 |
通过Logger对象直接调用对应的日志级别即可
示例代码如下:
public class LogBackTest {
// 创建一个Logger日志对象
public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");
public static void main(String[] args) {
//while (true) {
try {
LOGGER.info("chu法方法开始执行~~~");
chu(10, 0);
LOGGER.info("chu法方法执行成功~~~");
} catch (Exception e) {
LOGGER.error("chu法方法执行失败了,出现了bug~~~");
}
//}
}
public static void chu(int a, int b){
LOGGER.debug("参数a:" + a);
LOGGER.debug("参数b:" + b);
int c = a / b;
LOGGER.info("结果是:" + c);
}
}
当我们运行程序时,就可以看到控制台记录的日志:
// 日志输出时间 日志级别 日志在哪个类输出的 线程名 日志记录的信息
2023-03-09 20:59:46.457 [INFO ] LogBackTest [main] : chu法方法开始执行~~~
2023-03-09 20:59:46.463 [DEBUG] LogBackTest [main] : 参数a:10
2023-03-09 20:59:46.463 [DEBUG] LogBackTest [main] : 参数b:0
2023-03-09 20:59:46.463 [ERROR] LogBackTest [main] : chu法方法执行失败了,出现了bug~~~
会发现配置文件中配置路径中的log文件也会记录:
源文件在上面
作用: 对Logback日志框架进行控制的。
日志的输出位置、输出格式的设置:
通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中,通过name可以分别
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
开启日志(ALL),取消日志(OFF):
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
root>
日志格式是由一些特殊的符号组成,可以根据需要删减不想看到的部分。比如不想看到线程名那就不要[%thread]。但是不建议更改这些格式,因为这些都是日志很基本的信息。
关于日志的级别,在上面有说明。
那么在哪里配置日志级别呢?如下图所示
Logback只输出大于或者等于核心配置文件配置的日志级别信息。小于配置级别的日志信息,不被记录。
配置的是trace,则trace、debug、info、warn、error级别的日志都被输出
配置的是debug, 则debug、info、warn、error级别的日志被输出
配置的是info,则info、warn、error级别的日志被输出
...
什么是线程?
线程就是程序内部的一条执行通道。程序中如果只有一条执行通道,那这个程序就是单线程的程序。
那么什么是多线程呢?
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
创建线程的方式有如下三种。常用第二种,但是各有各的好处。
具体步骤:
1.定义一个子类继承Thread类,并重写run方法
2.创建Thread的子类对象
3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)
public class MyThread extends Thread{
// 2、必须重写Thread类的run方法
@Override
public void run() {
// 描述线程的执行任务。
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyThread输出:" + i);
}
}
}
再定义一个测试类,在测试类中创建MyThread线程对象,并启动线程
public class ThreadTest1 {
// main方法是由一条默认的主线程负责执行。
public static void main(String[] args) {
// 3、创建MyThread线程类的对象代表一个线程
Thread t = new MyThread();
// 4、启动线程(自动执行run方法的)
t.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出:" + i);
}
}
}
打印结果如下图所示,我们会发现MyThread和main线程在相互抢夺CPU的执行权(注意:哪一个线程先执行,哪一个线程后执行,目前是无法控制的,每次输出结果都会不一样)
最后我们还需要注意一点:不能直接去调用run方法,如果直接调用run方法就不认为是一条线程启动了,而是把Thread当做一个普通对象,此时run方法中的执行的代码会成为主线程的一部分。此时执行结果是这样的。
Java为开发者提供了一个Runnable接口,该接口中只有一个run方法,意思就是通过Runnable接口的实现类对象专门来表示线程要执行的任务。具体步骤如下
1.先写一个Runnable接口的实现类,重写run方法(这里面就是线程要执行的代码)
2.再创建一个Runnable实现类的对象
3.创建一个Thread对象,把Runnable实现类的对象传递给Thread
4.调用Thread对象的start()方法启动线程(启动后会自动执行Runnable里面的run方法)
代码如下:先准备一个Runnable接口的实现类
/**
* 1、定义一个任务类,实现Runnable接口
*/
public class MyRunnable implements Runnable{
// 2、重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务。
for (int i = 1; i <= 5; i++) {
System.out.println("子线程输出 ===》" + i);
}
}
}
再写一个测试类,在测试类中创建线程对象,并执行线程
public class ThreadTest2 {
public static void main(String[] args) {
// 3、创建任务对象。
Runnable target = new MyRunnable();
// 4、把任务对象交给一个线程对象处理。
// public Thread(Runnable target)
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出 ===》" + i);
}
}
}
控制台输出结果:
主线程main输出 ===》1
主线程main输出 ===》2
主线程main输出 ===》3
子线程输出 ===》1
子线程输出 ===》2
子线程输出 ===》3
子线程输出 ===》4
子线程输出 ===》5
主线程main输出 ===》4
主线程main输出 ===》5
关于匿名内部类和Lambda,在前面的笔记中都有记录,所以就不在赘述,直接使用
public class ThreadTest2_2 {
public static void main(String[] args) {
// 1、直接创建Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程1输出:" + i);
}
}
};
new Thread(target).start();
// 简化形式1:匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程2输出:" + i);
}
}
}).start();
// 简化形式2:lambda表达式
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程3输出:" + i);
}
}).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出:" + i);
}
}
}
已经有两种了为什么还有要第三种呢? 这样,我们先分析一下前面两种都存在的一个问题。然后再引出第三种可以解决这个问题。
public void run(){
...线程执行的代码...
}
public T call(){
...线程执行的代码...
return 结果;
}
第三种创建线程的方式,步骤如下
1.先定义一个Callable接口的实现类,重写call方法
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Thread的start()方法启动线程(启动后会自动执行call方法)
等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
6.调用FutrueTask对的get()方法获取返回结果
代码如下:先准备一个Callable接口的实现类
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
再定义一个测试类,在测试类中创建线程并启动线程,还要获取返回结果
public class ThreadTest3 {
public static void main(String[] args) throws Exception {
// 3、创建一个Callable的对象
Callable<String> call = new MyCallable(100);
// 4、把Callable的对象封装成一个FutureTask对象(任务对象)
// 未来任务对象的作用?
// 1、是一个任务对象,实现了Runnable对象.
// 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
FutureTask<String> f1 = new FutureTask<>(call);
// 5、把任务对象交给一个Thread对象
new Thread(f1).start();
Callable<String> call2 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(call2);
new Thread(f2).start();
// 6、获取线程执行完毕后返回的结果。
// 注意:如果执行到这儿,假如上面的线程还没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
String rs = f1.get();
System.out.println(rs);
String rs2 = f2.get();
System.out.println(rs2);
}
}
再定义一个测试类,在测试类中创建线程并启动线程,还要获取返回结果
public class CallableTestMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3、创建一个Callable的对象
CallableTest callableTest = new CallableTest();
// 4、把Callable的对象封装成一个FutureTask对象(任务对象)
// 未来任务对象的作用?
// 1、是一个任务对象,实现了Runnable对象.
// 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
FutureTask<Integer> task = new FutureTask<>(callableTest);
// 5、把任务对象交给一个Thread对象
new Thread(task).start();
// 6、获取线程执行完毕后返回的结果。
// 注意:如果执行到这儿,假如上面的线程还没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
Integer integer = task.get();
System.out.println("最终的结果是:"+integer);
System.out.println("main方法结束");
}
}
方式一优缺点:
优点: 编码简单
缺点: 线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
方式二优缺点:
优点: 任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点: 需要多一个Runnable对象。
方式三优缺点:
优点: 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点: 编码复杂一点。
Thread提供的常见构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName (String name) | 为线程设置名称 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
public final void join()… | 让调用当前这个方法的线程先执行完! |
下面演示一下getName()
、setName(String name)
、currentThread()
、sleep(long time)
,join()
这些方法的使用效果。
public class ThreadMethodTest {
public static void main(String[] args) {
// 使用lambda表达式传入一个Runnable接口实现的匿名类, 通过构造器给线程起名叫做 “乌龟-----”
Thread wuGuiThread = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
// 通过Thread.currentThread()静态方法获取当前的Thread对象, 然后获取线程的名称。
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}, "乌龟-----");
// 开启乌龟这个线程
wuGuiThread.start();
// 通过构造器给线程起名叫做 “兔子”
Thread tuZiThread = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
// 让这个“兔子”,每次休眠1毫秒 为了测试join这个就先注释了
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// i到15的时候,让乌龟通过Join这个方法进来插队
if (i == 15){
try {
// 这里必须等乌龟的执行完了兔子的才可以执行
wuGuiThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "兔子");
// 开启兔子这个线程
tuZiThread.start();
}
}
使用join之后的测试结果:也就是说,使用了join方法,就相当于让乌龟插了一个队,必须乌龟完事了兔子才可以运行,不存在并发。
有第一个小问题:就是start之后java做了什么?
线程对象.start() ———> start0()jvm机调用 --- 再根据操作系统的算法进行调用线程
Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会后续需要用到的时候再补充。
有这样的一个product.properties文件: 使用代码把下面的product.properties文件中所有商品的价格都放大2倍,文件内容如下:(文件自己创建)
#价格修改
#Fri Mar 10 10:43:34 CST 2023
白象方便面=12
卫龙辣条=8
华为手机=20000
使用properties的方法如下:
public class Homework04 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
// 读取properties文件
properties.load(new FileReader("javaEE-day10\\product.properties"));
// 获取到KeySet集合
Set<String> strings = properties.stringPropertyNames();
// 遍历key
for (String product : strings) {
// 通过key得到value
String price = properties.getProperty(product);
// 再写回去
properties.setProperty(product, Integer.parseInt(price) * 2 + "");
}
// 写入properties文件中
properties.store(new FileWriter("javaEE-day10\\product.properties"),"价格修改");
}
既然用了流, 考虑一下,如果使用前面的stream流应该怎么处理呢? 下面的处理上面的需求比较麻烦,但是是对前面Stream流知识的一个复习;还是很有必要的.
public class Homework04OtherMethodByStream{
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileReader("javaEE-day10\\product.properties"));
// 获取entry对象 即一个一个的键值对对象
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
// 对entry中的数据进行处理 map的作用是对每个对象做一个转换.这里我们就是修改他们的值
Map<Object, Object> collect = entries.stream().map(entry -> {
// 修改每一个value并写入
Integer integer = Integer.parseInt((String) entry.getValue()) * 2;
entry.setValue(integer + "");
return entry;
// 收集一下数据流
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(collect);
// 写入properties文件中
properties.store(new FileWriter("javaEE-day10\\product.properties"),"价格修改");
}