Java基础知识
目录
6.2 OutputStreamWriter与InputStreamReader
1、数据类型
Java所有关键字都是小写的,TRUE、FALSE和NULL都不是Java关键字。
Java语言支持的类型分为两类:基本类型和引用类型。基本类型包括boolean类型和数值类型。引用类型包括类、接口和数组类型,还有一种特殊的null类型。
字符串不是基本数据类型,字符串是一个类,也就是一个引用数据类型。
如果使用一个巨大的整数值(超过了int类型的表数范围)时,Java不会自动把这个整数当作long类型来处理。如果希望系统将一个整数值当成long类型来处理,应该在这个整数值后增加 l 或者 L 作为后缀。推荐使用 L
Java语言的浮点类型默认是 double类型,如果希望Java将一个浮点类型值当成 float 类型来处理,应该在这个整数值后增加 f 或者 F 作为后缀。
----练习:Java生成随机数
import java.util.Random;
public class myRandom {
public static void main(String[] args) {
Random r = new Random();
// 获取数据范围 [0,10)
int n = r.nextInt(10);
System.out.println(n);
}
}
2、修饰符总结
定义类的修饰符:public、final、abstract
定义成员变量的修饰符:public、protected、private、static、final(其中public、protected、private三个最多出现其中一个,可以与static、final组合起来修饰成员变量)
定义方法的修饰符:public、protected、private、static、final、abstract(其中public、protected、private三个最多出现其中一个;final和abstract最多出现其中一个它们可以与static组合起来修饰方法)
定义构造方法的修饰符:public、protected、private三个其中一个
final关键字:
final修饰的类不能被继承,修饰的方法不能被重写,修饰的变量不可以修改成为常量
static关键字:
它的作用是用于区分成员变量、方法、内部类、初始化块这四种成员到底属于类本身还是属于实例。
静态的加载优于对象,随着类的加载而加载。所以静态成员不能直接访问非静态成员。static修饰的方法中无法使用this关键字
初始化块
格式: 修饰符只能是 static,使用static修饰的初始化块被称为静态初始化块
[修饰符] {
...
}
普通初始化块:当创建对象时,系统总是先调用该类里定义的初始化块,然后调用相应的构造器。
当Java创建一个对象时,系统先为该对象的所有实例变量分配内存,接着程序开始对这些实例变量执行初始化,其初始化的顺序是:先执行初始化块或声明实例变量时指定的初始值(按照在代码中的顺序执行),再执行构造器里指定的初始值。
![](https://img2018.cnblogs.com/blog/1324226/201901/1324226-20190123160109029-402086263.png)
创建一个java对象时,不久会执行该类的普通初始化块和构造器,而且系统会一直上溯到java.lang.Object类,先执行java.lang.Object类的初始化块,开始执行java.lang.Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器......最后才执行该类的初始化块和构造器,返回该类的对象。
静态初始化块:
负责对类进行初始化
![](https://img2018.cnblogs.com/blog/1324226/201901/1324226-20190123184247055-394660912.png)
运行结果:静态只初始化一次,先上溯
![](https://img2018.cnblogs.com/blog/1324226/201901/1324226-20190123184336950-1796538081.png)
3、String总结
![](https://img2018.cnblogs.com/blog/1324226/201901/1324226-20190121143015045-1089480204.png)
==的作用:比较基本数据类型的值是否相同,比较引用数据类型的地址是否相同
用"+"拼接字符串内存示意图
用"+"做字符串拼接的时候,是将拼接后的字符串新生成一个,原先的会被废弃掉,造成巨大的内存浪费。
StringBuilder了解一下
4、Java集合(包括数组的知识)
需要集中存放多个数据,但数组长度不可变化,且无法保存具有映射关系的数据
常用的集合有哪些? List Set Map
4.1 List
存储的元素有索引,可以重复
4.2 Set
存储的元素无索引,不可以重复,无序状态
4.3 Map
存储键-值对形式的数据,键值不可重复
Map的遍历方法:
利用Map的:方法
Set<Map.Entry<K,V>> |
entrySet() 返回此映射中包含的映射关系的 Set 视图。 |
然后遍历这个Set集合对象即可,获取到每一个Map.Entry<K,Y>元素,然后调用其
![](https://img2018.cnblogs.com/blog/1324226/201901/1324226-20190131205108509-90601364.png)
并发修改异常 java.util.ConcurrentModificationException
产生原因:在遍历集合的同时对其进行修改
解决办法:不在遍历集合的同时对其进行修改
示例代码:遍历students这个ArrayList集合,并在符合条件时删除其中的元素
for(Student s:students){
if(s.getId().equals(id)){
students.remove(s);
flag = true;
}
}
修改后:用一个下标记录要删除的元素位置
for(Student s:students){
if(s.getId().equals(id)){
index = students.indexOf(s);
flag = true;
}
}
迭代器知识:
public interface Iterator<E> :对 collection 进行迭代的迭代器。在对集合进行迭代时,要将其转换为一个迭代器对象进行操作。
Collection中有:可以获得迭代器对象
Iterator<E> |
iterator() 返回在此 collection 的元素上进行迭代的迭代器。 |
具体到特定的类型,例如List中有:获得迭代器对象
ListIterator<E> |
listIterator() 返回此列表元素的列表迭代器(按适当顺序)。 |
ListIterator中有add方法,可以实现迭代期间修改列表,从而不引发 并发修改异常
5、File类
文件和目录路径名的抽象表示形式。File类的实例是不可变的;也就是说,一旦创建,File对象表示的抽象路径名将永不改变。
通过指定路径创建File对象时,并不会去检查路径是否存在或者文件是否存在
5.1 File类创建和删除功能
boolean createNewFile():指定路径不存在该文件时时创建文件,返回true;存在时不创建,并返回false
boolean mkdir():当指定的单级文件夹不存在时创建文件夹并返回true,否则返回false
boolean mkdirs():当指定的多级文件夹某一级文件夹不存在时,创建多级文件夹并返回true,否则返回false
boolean delete():删除文件或者删除单级文件夹 存在时删除并返回true,不存在时返回false 不能删除拥有子文件或者子文件夹的文件夹
5.2 File类的判断功能
boolean exists():判断指定路径的文件或文件夹是否存在
boolean isAbsolute():判断当前路路径是否是绝对路径
boolean isDirectory():判断当前的目录是否存在
boolean isFile():判断当前路径是否是一个文件
boolean isHidden():判断当前路径是否是隐藏文件
5.3 File类的获取功能和修改名字功能
File getAbsoluteFile():获取文件的绝对路径,返回File对象
String getAbsolutePath():获取文件的绝对路径,返回路径的字符串
String getParent():获取当前路径的父级路径,以字符串形式返回该父级路径
File getParentFile():获取当前路径的父级路径,以字File对象形式返回该父级路径
String getName():获取文件或文件夹的名称
String getPath():获取File对象中封装的路径,绝对就返回绝对,相对就返回相对
long lastModified():以毫秒值返回最后修改时间
long length():返回文件的字节数
boolean renameTo(File dest): 将当前File对象所指向的路径 修改为 指定File所指向的路径,修改的文件不能存在,也就是不能重名
5.4 File类的其它获取功能
String[] list():以字符串数组的形式返回当前路径下(必须是文件夹路径)所有的文件和文件夹的名称
File[] listFiles():以File对象的形式返回当前路径下所有的文件和文件夹的名称
static File[] listRoots():获取计算机中所有的盘符
按流向分类:
输入流: 读取数据 FileReader Reader
输出流: 写出数据 FileWriter Writer
按数据类型分类:
字节流:(一次读取一个字节,不能因为有字符流就不需要字节流,因为图片、视频之类的数据底层还是字节流形式)
字节输入流: InputStream
字节输出流: OutputStream
字符流:(为了方便一次读取多个字节,比如中文)
字符输入流: Reader
字符输出流: Writer
二进制文件(图片、视频、音频等)只能使用字节流
6.2 OutputStreamWriter与InputStreamReader
一个案例:利用字符流读取一个文件中的数据在命令行中展示
首先介绍标准的输入输出流:System.in System.out
查看System的文档
Static InputStream in
Static PrintStream out
可以看出标准的输入输出流是字节流
示例代码:
import java.io.*; public class MyIO { public static void main(String[] args) { BufferedReader br = null; OutputStream os = null; try { // 创建输入流对象 br = new BufferedReader(new FileReader("test.txt")); // 创建输出流对象 os = System.out; String line; while ((line=br.readLine()) != null) { os.write(line.getBytes()); os.write("\r\n".getBytes()); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //释放资源 try { os.close(); br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
从代码处可以看出,标准输出在输出数据时,由于我们获取的数据是字符流获取的,而输出只能是以字节的形式,需要我们将数据转换为字节数组,这样很不方便,那我们需要考虑Java是否提供了一种将字节流转换的工具,使其可以处理字符流,而不是让我们自己手动转换。
这种解决方法就是OutputStreamWriter与InputStreamReader 这两个转换流
利用这个类我们对代码进行修改
import java.io.*;
public class MyIO {
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 创建输入流对象
br = new BufferedReader(new FileReader("test.txt"));
// 创建输出流对象
bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line;
while ((line=br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
try {
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
为了更加方便操作,我们直接将OutputStreamWriter再封装一层,成为BufferedWriter,这里我们就不需要自己再进行字符串转换为字节数组的操作了。
同理也可以对标准输入流转换
PrintStream 与 PrintWriter
PrintWriter是一个包装流,字符打印流。
具有写出数据自动刷新(需要在构造方法中设置,并使用特有方法),自动换行(使用println()方法)的特有功能,具体参考API
详情见Java面向对象 的对象操作流部分
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。
在并发程序中可以同时拥有两个或者多个线程。这意味着,如果程序在单核处理器上运行,那么这两个线程将交替地换入或者换出内存。这些线程是同时“存在”的——每个线程都处于执行过程中的某个状态。如果程序能够并行执行,那么就一定是运行在多核处理器上。此时,程序中的每个线程都将分配到一个独立的处理器核上,因此可以同时运行。
我相信你已经能够得出结论——“并行”概念是“并发”概念的一个子集。也就是说,你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。
摘自:《并发的艺术》 — 〔美〕布雷谢斯public class MyThread { public static void main(String[] args) { Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.setName("one"); t2.setName("two"); t1.start(); t2.start(); } } ----------------------------------- public class Thread1 extends Thread { @Override public void run() { for(int i=0; i<100; i++) { System.out.println(super.getName() + ":" + i); } } } ----------------------------------- public class Thread2 extends Thread{ @Override public void run() { for(int i=0; i<100; i++) { System.out.println(super.getName() + ":" + i); } } }
实现多线程的方法2:实现Runable接口
public class MyRunable { public static void main(String[] args) { Runable1 r1 = new Runable1(); Thread t1 = new Thread(r1); t1.start(); Thread t2 = new Thread(r1); t2.start(); } } ------------------------------------------- public class Runable1 implements Runnable { @Override public void run() { for(int i=0; i<100; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }
为什么会有继承Thread类实现多线程和实现Runable实现多线程两种方法呢?
主要是因为Java的单一继承,如果我们继承了Thread类后还想继承其他类,显然是做不到的,但实现Runable接口便还可以继承其他类,所以推荐使用第二种方法
涉及到多线程必然会涉及到同步问题,也就是对于共享资源,同一时刻只能有一个线程进行访问。
synchronized可以用来修饰代码块和方法
非静态方法的锁对象是this,静态方法的锁对象是当前类的字节码对象
自定义枚举类
class Season{
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc =seasonDesc;
}
public static Season SPRING = new Season("Spring", "春天");
public static Season SUMMER = new Season("Summer", "夏天");
public static Season AUTUMN = new Season("Autumn", "秋天");
public static Season WINTER = new Season("Winter", "冬天");
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}利用关键字:enum
public enum Season {
SPRING("spring","春天"),
SUMMER("summer","夏天"),
AUTUMN("autumn","秋天"),
WINTER("winter","冬天");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
java.lang.Class是反射的源头,我们创建了一个类,通过编译(java.exe),生成对应的.class文件、之后我们使用java.exe加载(JVM的类加载器)此.class文件,此.class文件加载到内存以后,就是一个运行时类,存放在缓存区,这个类本身就是一个Class的实例
具体内容见:Java反射