注:笔记型文章
第一章 多线程
1.1 程序、进程、线程
- 程序:是为了完成某个特定的任务,而用某种语言编写的一组指令的集合,即指的是一段静态的代码,静态对象。
- 进程:是程序的一次执行过程,或是一个正在运行的程序。是一个动态的过程:有它自身的产生、存在、和消亡的过程——生命周期。
- 程序是静态的,进程是动态的。系统在运行时会为每个进程分配不同的内存空间。
- 线程:进程可进一步细化为线程,是一个程序内部的执行的路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,这就使得线程间的通信更简便、高效。但多个线程操作共享系统资源可能会带来安全隐患。
1.2 并行、并发
- 并行:多个CPU同时执行多个任务
- 并发:单个CPU执行多个任务。
1.3 创建多线程
方法一:继承Thread类
- 创建Thread类的子类
- 重写Thread类的run(),将此线程执行的操作写到run()中
- 创建当前Thread类子类的对象
- 通过此对象调用start()。
package com.idealearn.java;
class MyClass extends Thread{
public void run(){
for(int i=0;i<=100;++i){
if(i%2==0){
System.out.println(i);
}
for(int j=0;j<=Math.random()*100;++j){
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyClass a = new MyClass();
a.start();
for(int i=0;i<=100;++i){
if(i%2==1){
System.out.println(i);
}
for(int j=0;j<=Math.random()*100;++j){
}
}
}
}
-
start()方法的作用:开始执行当前线程,JAVA虚拟机执行当前线程的run()
- 如果直接调用线程类的run(),那么不会开始当前线程。执行顺序仍是当前的单线程。
-
不能让已经start()的线程再次start()。
- 可以再新建一个线程,并start()
-
匿名子类创建线程类
public class IdeaTest {
public static void main(String[] args) {
//匿名子类
new Thread(){
public void run(){
System.out.println("哇塞!多线程!");
}
};
}
}
- Thread类常用方法:
- start():启动线程,调用run()
- run():执行对应语句
- currentThread():静态方法,返回执行当前代码的线程(类)
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前线程对CPU的执行权(转而交给其他线程)
- join():在线程a调用线程b的join(),a阻塞,直到b运行完后a继续执行。
- sleep(long minitime):线程“休眠”一段时间(单位:毫秒),并引发异常InterruptedException。(结合try-catch使用)
- isAlive():判断当前线程是否存活。
线程的调度
- CPU抢占式。
- 线程的优先级:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
- 获取/设置线程优先级
- getPriority();
- setPriority();
setPriority(Thread.MAX_PRIORITY);
方法二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的方法:run()
- 创建实现类子类的对象
- 将此对象作为参数传递到Thread类构造器中,创建Thread类对象
- 通过Thread类的对象调用start()
package com.idealearn.java;
class AThread implements Runnable{
public void run(){
for(int i=0;i<=100;++i){
if(i%2==0){
System.out.println(i+":"+Thread.currentThread().getName());
}
for(int j=0;j<=Math.random()*100;++j){}
}
}
}
public class RunnableTest {
public static void main(String[] args) {
AThread runnable_thread = new AThread();
Thread run_thread= new Thread(runnable_thread);
run_thread.setName("多线程01");
run_thread.start();
//调用runnable里的target的run()
}
}
1.4 线程的生命周期
- 新建:一个Thread类或子类对象被创建
- 就绪:start()后,还没有开始运行,等待CPU分配资源
- 运行:CPU执行中
- 阻塞:执行被阻塞,等待回到就绪
- 死亡:执行结束或者出现错误或异常强制终止
1.5 线程的同步
- 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
- 问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
- 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
1.6 解决线程同步问题
方式一:同步代码块
- 操作共享数据的代码,即为需要被同步的代码。
- 共享数据:多个线程共同操作的变量。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
synchronized(同步监视器){
//需要被同步的代码
}
- 同步的方式,解决了线程的安全问题。
- 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
class Window1 implements Runnable{
private int ticket = 100;
public void run() {
while(true){
synchronized (dog){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
- 还可以使用this充当同步监视器(此时this对象只是一个)
class Window1 implements Runnable{
private int ticket = 100;
public void run() {
while(true){
synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
- 甚至类也可以充当同步监视器(类是唯一的)
class Window1 implements Runnable{
private int ticket = 100;
public void run() {
while(true){
synchronized (Window1.class){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
方式二:同步方法。
- 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
- 使用Runnable
package com.idealearn.java;
class Window1 implements Runnable{
private int ticket = 100;
public void run() {
while(show()){;}
}
//同步的方法
private synchronized boolean show() {
//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
return true;
} else {
return false;
}
}
}
public class ScnBlock {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 继承Thread
package com.idealearn.java;
class Window1 extends Thread{
private static int ticket = 100;
public void run() {
while(show()){;}
}
private static synchronized boolean show() {
//同步监视器:当前的类
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
return true;
} else {
return false;
}
}
}
public class ScnBlock {
public static void main(String[] args) {
Window1 t1 = new Window1();
Window1 t2 = new Window1();
Window1 t3 = new Window1();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
线程安全的懒汉式
class Bank{
private Bank(){};
private static Bank instance=null;
public synchronized static Banck getInstance(){
if(instance==null){
instance=new Bank();
}
}
}
-- 效率较低
class Bank{
private Bank(){};
private static Bank instance=null;
public static Banck getInstance(){
synchronized(Bank.class){
if(instance==null){
instance=new Bank();
}
return instance;
}
}
}
-- 效率较高
class Bank{
private Bank(){};
private static Bank instance=null;
public static Banck getInstance(){
if(instance==null){
synchronized(Bank.class){
if(instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
死锁
-
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
-
解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
方式二:Lock锁
class Window1 implements Runnable{
private int ticket = 100;
private ReentrantLock alock =new ReentrantLock();
//ReentrantLock(true):公平竞争
public void run() {
while(true){
try {
//上锁
alock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}finally{
//解锁
alock.unlock();;
}
}
}
}
public class ScnBlock {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- synchronized是自动设置监视器
- lock需要手动设置。
1.7 线程的通信
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
- 注意
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
- wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
//使用两个线程打印 1-100。线程1, 线程2 交替打印
public class ThreadCommunication {
public static void main(String[] args) {
Number a = new Number();
Thread t1=new Thread(a);
Thread t2=new Thread(a);
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int num=1;
private Object obj=new Object();
public void run(){
while(true) {
synchronized (obj){
obj.notify();//唤醒wait()的线程
//notifyAll(); 唤醒所有wait()的线程
if (num <= 100) {
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
obj.wait();
//调用wait()进入阻塞状态
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
break;
}
}
}
}
}
- 面试题:sleep() 和 wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
生产者与消费者问题
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品.....");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品.....");
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
1.8 方式三:实现Callable接口
- 继承Thread
- 实现Runnable
- 实现Callable
- 使用线程池
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
1.9 方法四:使用线程池
- 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般来执行Callable- void shutdown() :关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
第二章 Java常用类
2.1 String
String基础
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
- String声明为final,不可被继承
- String实现了Serializable,表示字符串可以被序列化
- String实现了Comparable,表示可以比较大小
private final char value[];:存储字符串数据- 不可变
String a = "123456";//不同于new,采用字面量的定义方式
String s1 = "123123";
String s2 = "123123";
System.out.println(s1==s2);//true
//地址相同
//通过字面量声明字符串,"123123"存储在方法区(含有字符串的常量池)
//常量池中不会存储内容相同的字符串
s2 = "123456";
//s2指向常量池中新字符串"123456"
String s3 = s2 + "abc";
//s3指向常量池中新字符串"123456abc"
//原来的字符串"123456"仍然保留,没有改
String s4 = s2.replace('c','z');
//s43指向常量池中新字符串"123456abz"
String的创建
String str = "hello";
//本质上this.value = new char[0];
String s1 = new String();
//new + 构造器
//this.value = original.value;
String s2 = new String(String original);
//此方式在内存中创建了两个对象
//s1指向堆中新建的对象
//对象中的属性value指向存储在常量池中的字符串
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
String s1="123";
String s2="123";
String s3=new String("123");
String s4=new String("123");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s3==s4);//false
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- intern():强制要求值存在常量值中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
public void test4(){
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false
final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true
}
String常用方法
int length():返回字符串的长度: return value.lengthchar charAt(int index): 返回某索引处的字符return value[index]boolean isEmpty():判断是否是空字符串:return value.length == 0String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写String trim():返回字符串的副本,忽略前导空白和尾部空白boolean equals(Object obj):比较字符串的内容是否相同boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”int compareTo(String anotherString):比较两个字符串的大小String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。String substring(int beginIndex, int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 trueint indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索- 注:indexOf和lastIndexOf方法如果未找到都是返回-1
String与基本数据类型转换
- String to 基本数据类型: parseXXX()
- 基本数据类型 to String: String重载的 valueof(xxx)
- String to char:str.toCharArray();
- Char to String:new String(arr);调用String的构造器
- 编码:String --> byte[]:调用String的getBytes()
- 编码:字符串 -->字节 (看得懂 --->看不懂的二进制数据)
- 解码:byte[] --> String:调用String的构造器
- 解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
- 说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
System.out.println(Arrays.toString(gbks));
System.out.println("******************");
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println(str2);
String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}
常见算法题目
- 实现trim方法
public class StringFunctions {
public static void main(String[] args) {
String s1="";
String s2=" ";
String s3=" ";
String s4=" ";
String s5="123 ";
String s6=" 1234";
String s7=" 12341212 ";
String s8="a";
System.out.println(StringFunc.trim_blank(s1));
System.out.println(StringFunc.trim_blank(s2));
System.out.println(StringFunc.trim_blank(s3));
System.out.println(StringFunc.trim_blank(s4));
System.out.println(StringFunc.trim_blank(s5));
System.out.println(StringFunc.trim_blank(s6));
System.out.println(StringFunc.trim_blank(s7));
System.out.println(StringFunc.trim_blank(s8));
}
}
class StringFunc{
public static String trim_blank(String a){
// char charAt(int index)
// String substring(int beginIndex, int endIndex)
if(a.length()==0) {
return "";
}
int i=0,j=a.length()-1;
while((a.charAt(i)==' ' || a.charAt(j)==' ') && i<j){
if(a.charAt(i)==' ') {
i++;
}
if(a.charAt(j)==' '){
j--;
}
}
if(a.charAt(i)==' '){
return "";
}
else{
return a.substring(i,j+1);
}
}
}
- 获取一个字符串在另一个字符串中出现的次数。
- 下面的不能解决“abgabgabg”与“abgabg”的重复问题
public class StringFunctions {
public static void main(String[] args) {
String s="fbgfbgfbgfbgfbgfbbgfbgffbgfbvgfbg";
String a="fbg";
System.out.println(StringFunc.find_str_num(s,a));
}
}
class StringFunc{
public static int find_str_num(String s,String a){
if(s.length()==0){
return 0;
}
int num=0,index=0;
while(index<s.length()){
index=s.indexOf(a,index);
if(index==-1){
break;
}
num++;
index++;
}
return num;
}
}
- 将字符串中指定部分进行反转。
public class Main
{
public static void main(String[] args) {
String s1="";
String s2="1";
String s3="0123456789";
System.out.println(StringFunc.StringReverse(s1,0,0));
System.out.println(StringFunc.StringReverse(s2,0,0));
System.out.println(StringFunc.StringReverse(s3,0,9));
System.out.println(StringFunc.StringReverse(s3,4,7));
System.out.println(StringFunc.StringReverse(s3,4,6));
}
}
class StringFunc{
public static String StringReverse(String s,int a,int b){
if(s.length()==0){
return s;
}
char[] c=new char[s.length()];
char t;
for(int i=0;i<s.length();i++){
c[i]=s.charAt(i);
}
for(int i=a;i<=(b-a)/2+a;i++){
t=c[i];
c[i]=c[(a+b)-i];
c[(a+b)-i]=t;
}
return new String(c);
}
}
- 获取两个字符串中最大相同子串
public class Main
{
public static void main(String[] args) {
System.out.println(StringFunc.MaxSubstring("abcde3232efg","qqqqqbcdefg"));
}
}
class StringFunc{
public static String MaxSubstring(String a,String b){
if(a==""&&b==""){
return "";
}
if(a.length()<b.length()){
String c = a;
a = b;
b = c;
}
int l=b.length(),offset;
offset=0;
while(offset<b.length()){
for(int i=0;i<=offset;i++){
String temp=b.substring(i,i+l-offset);
if(a.indexOf(temp)!=-1){
return temp;
}
}
offset++;
}
return "No Found!";
}
}
2.2 StringBuffer
比较
-
String:不可变的字符序列;底层使用char[]存储
-
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
-
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
-
源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接StringBuffer delete(int start,int end):删除指定位置的内容StringBuffer replace(int start, int end, String str):把[start,end)位置替换为strStringBuffer insert(int offset, xxx):在指定位置插入xxxStringBuffer reverse():把当前字符序列逆转public int indexOf(String str)public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串public int length()public char charAt(int n )public void setCharAt(int n ,char ch)
总结: 增:append(xxx) 删:delete(int start,int end) 改:setCharAt(int n ,char ch) / replace(int start, int end, String str) 查:charAt(int n ) 插:insert(int offset, xxx) 长度:length(); 遍历:for() + charAt() / toString()
2.3 时间日期 旧
currentTimeMillis()
- 时间戳:返回1970年1月1号到现在的时间差(单位毫秒)
java.util.Date类
- 构造器
new Date()new Date(时间戳毫秒数)
- 方法
toString():显示当前的年、月、日、时、分、秒getTime():获取当前Date对象对应的毫秒数
java.sql.Date类
- 数据库中用的。
- 实例化
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
- 将java.util.Date对象转换为java.sql.Date对象
//情况一:
Date date4 = new java.sql.Date(2343243242323L);
java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
SimpleDateFormat
- 格式化(转换成字符串)
import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.ParseException;
public class Main
{
public static void main(String[] args) {
SimpleDateFormat simpleDateFormat1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date2=simpleDateFormat1.parse("2022-11-22 11:48:27");
System.out.println(date2);
}
}
- 解析(字符串转成日期)
public static void main(String[] args) throws ParseException{
SimpleDateFormat simpleDateFormat1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date2=simpleDateFormat1.parse("2020-05-29 11:48:27");
System.out.println(date2);
//Fri May 29 11:48:27 GMT 2020
//要求字符串必须符合SimpleDateFormat识别的格式(构造器参数),否则会抛异常
}
- 字符串转换java.sql.date
String s="2022-11-22";
SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd");
Date date=sim.parse(s);
java.sql.Date SqlDate=new java.sql.Date(date.getTime());
System.out.println(SqlDate);
java.util.Calendar;
-
Calender是个抽象类
-
注意,一月、星期一是从0开始
-
创建
//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用静态方法 getInstance()
Calendar c=Calendar.getInstance();
- getTime()
//获取时间
Date date=c.getTime();
System.out.println(date);
Tue Nov 22 02:24:58 GMT 2022
- setTime()
//设置时间
Date d=new Date();
c.setTime(d);
- get()
//获取常用属性
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//22
System.out.println(c.get(Calendar.DAY_OF_YEAR));
//326
- set()
//设置属性
c.set(Calendar.DAY_OF_MONTH,23);
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//23
- add()
//添加
c.add(Calendar.DAY_OF_MONTH,3);
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//26
2.4 时间日期 新
java.time
LocalDate、LocalTime、LocalDateTime
import java.time.*;
public class Main
{
public static void main(String[] args){
LocalDate d=LocalDate.now();
LocalTime t=LocalTime.now();
LocalDateTime dt=LocalDateTime.now();
System.out.println(d);
//2022-11-22
System.out.println(t);
//02:45:55.305094
System.out.println(dt);
//2022-11-22T02:45:55.305393
//设置指定时间
LocalDateTime time = LocalDateTime.of(2006,9,10,12,3,50);
System.out.println(time);
//2006-09-10T12:03:50
//getXXX()
System.out.println(time.getDayOfMonth());
//10
System.out.println(time.getDayOfYear());
//253
System.out.println(time.getMonth());
//SEPTEMBER
System.out.println(time.getMinute());
//3
//设置 withXXX()
LocalDate dd=d.withDayOfMonth(26);
System.out.println(d);
//2022-11-22
System.out.println(dd);
//2022-11-26
//添加 plusXXX()
LocalDate ddd = d.plusDays(1);
System.out.println(ddd);
//2022-11-23
}
}
Instant
- 时间线上的一个瞬时点
//now()
Instant i=Instant.now();
System.out.println(i);
//本初子午线时间
//2022-11-22T02:58:17.105656Z
//偏移时间
OffsetDateTime o = i.atOffset(ZoneOffset.ofHours(8));
System.out.println(o);
//2022-11-22T10:58:17.105656+08:00
//toEpochMilli:计算毫秒数(距离1970年)
System.out.println(i.toEpochMilli());
//1669085897105
//返回指定毫秒数(距离1970年)的对象
Instant ii=ofEpochMilli(900L)
System.out.println(ii);
//1970-01-01T00:00:00.900Z
java.time.format.DateTimeFormatter
- 格式化解析日期时间
//方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime ldt = LocalDateTime.now();
String str1 = formatter.format(ldt);
System.out.println(ldt);
System.out.println(str1);//2019-02-18T15:42:18.797
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
//方式二:
//本地化相关的格式。如:ofLocalizedDateTime()
//FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(ldt);
System.out.println(str2);//2019年2月18日 下午03时47分16秒
//本地化相关的格式。如:ofLocalizedDate()
//FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
//重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
2.5 Java比较器
Comparable接口
-
包装类、String等类实现了Comparable的借口,重写了compareTo()方法,给出了比较大小的规则
-
重写compareTo()
- 如果当前对象大于目标对象,返回正整数(1)
- 如果当前对象等于目标对象,返回0(0)
- 如果当前对象小于目标对象,返回负整数(-1)
-
自定义类的比较
import java.util.Arrays;
import java.util.Comparator;
public class Main
{
public static void main(String[] args) {
Phone[] ps=new Phone[4];
ps[0]=new Phone("小米",1900);
ps[1]=new Phone("三星",2300);
ps[2]=new Phone("金立",800);
ps[3]=new Phone("华为",1600);
Arrays.sort(ps);
for(int i=0;i<4;i++){
System.out.print(ps[i].getName()+" ");
}
//金立 华为 小米 三星
}
}
class Phone implements Comparable{
private String name;
private int price;
public Phone(String n,int p ){
name=n;
price=p;
}
public String getName(){
return name;
}
public int compareTo(Object o){
if(o instanceof Phone){
Phone g=(Phone)o;
if(this.price>g.price){
return 1;
}
else if(this.price<g.price){
return -1;
}
else{
return 0;
}
}
throw new RuntimeException("传入的数据不一致!");
}
}
- Comparator定制排序
- 需要的时候临时创建一个Comparator类
import java.util.Arrays;
import java.util.Comparator;
public class Main
{
public static void main(String[] args) {
Phone[] ps=new Phone[4];
ps[0]=new Phone("小米",1900);
ps[1]=new Phone("三星",2300);
ps[2]=new Phone("金立",800);
ps[3]=new Phone("华为",1600);
Arrays.sort(ps,new Comparator(){
public int compare(Object o1,Object o2){
if(o1 instanceof Phone && o2 instanceof Phone){
Phone p1=(Phone)o1;
Phone p2=(Phone)o2;
if(p1.getPrice()>p2.getPrice()){
return -1;
}
else if(p1.getPrice()<p2.getPrice()){
return 1;
}
else{
return 0;
}
}
throw new RuntimeException("传入的数据不一致!");
}
}
);
for(int i=0;i<4;i++){
System.out.print(ps[i].getName()+" ");
}
//三星 小米 华为 金立
}
}
class Phone{
private String name;
private int price;
public Phone(String n,int p ){
name=n;
price=p;
}
public String getName(){
return name;
}
public int getPrice(){
return price;
}
}
2.6 System类
-
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。
-
native long currentTimeMillis():
- 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
-
void exit(int status):
- 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
-
void gc()
- 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
-
String getProperty(String key)
- 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
public class Main
{
public static void main(String[] args) {
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
}
}
//java的version:11.0.7
//java的home:/usr/lib/jvm/java-11-openjdk
//os的name:Linux
//os的version:5.10.0-0.bpo.15-amd64
//user的name:repl
//user的home:/home/repl
//user的dir:/home/repl/6a1371c7-5f19-431d-aad9-0220e0149cd4
2.7 Math类
- java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
2.8 BigInteger与BigDecimal
BigInteger
- BigInteger可以表示不可变的任意精度的整数。
- BigInteger(String val):根据字符串构建BigInteger对象
- public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
- BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
- BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
- BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
- BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
- BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
- BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
- BigInteger pow(int exponent) :返回其值为 (this^exponent) 的 BigInteger。
BigDecimal
- BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
- public BigDecimal(double val)
- public BigDecimal(String val)
- public BigDecimal add(BigDecimal augend)
- public BigDecimal subtract(BigDecimal subtrahend)
- public BigDecimal multiply(BigDecimal multiplicand)
- public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
第三章 枚举类与注解
3.1 枚举类
- 一组有限个数的常量
- 如果枚举类中只有一个变量,可以作为单例模式实现。
3.2 定义枚举类
自定义枚举类
- 私有化构造器
- 声明对象的属性
- 提供枚举类的多个对象
- 自定义对象
class Season{
private String seasonName;
private String seasonInfo;
private Season (String name,String info){
seasonName=name;
seasonInfo=info;
}
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","烈日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
public String getName(){
return seasonName;
}
public String getInfo(){
return seasonInfo;
}
public String toString(){
return seasonName+""+seasonInfo;
}
}
public class Main
{
public static void main(String[] args) {
Season a =Season.WINTER;
System.out.println(a);
//冬天冰天雪地
}
}
enum关键字
- 默认继承于 java.lang.enum
- toString():默认返回枚举常量的名称
- values():显示所有枚举
- valueOf(String s):返回叫做s的枚举对象
enum实现接口
- 可以直接重写方法。也可以为每个枚举对象分别重写。
interface Info{
void show();
}
enum Season implements Info{
//多个对象逗号隔开
SPRING("春天","春暖花开"){
public void show(){
System.out.println("美丽的春天");
}
},
SUMMER("夏天","烈日炎炎"){
public void show(){
System.out.println("美丽的夏天");
}
},
AUTUMN("秋天","秋高气爽"){
public void show(){
System.out.println("美丽的秋天");
}
},
WINTER("冬天","冰天雪地"){
public void show(){
System.out.println("美丽的冬天");
}
};
private String seasonName;
private String seasonInfo;
private Season (String name,String info){
seasonName=name;
seasonInfo=info;
}
public String getName(){
return seasonName;
}
public String getInfo(){
return seasonInfo;
}
//可以直接重写show方法
//public void show(){
// System.out.println("多彩的四季");
//}
//一般不用重写枚举类
}
public class Main
{
public static void main(String[] args) {
Season a =Season.WINTER;
System.out.println(a);
//WINTER
Season[] v=a.values();
for(int i=0;i<v.length;i++){
System.out.print(v[i]+" ");
}
//SPRING SUMMER AUTUMN WINTER
System.out.println();
Season s=Season.valueOf("SPRING");
System.out.println(s);
//SPRING
a.show();
//美丽的冬天
s.show();
//美丽的春天
}
}
3.3 注解(Annotation)
常用注解
@Override 重写的方法 @Deprecated 修饰过时的结构 @SupressWarning 抑制编译器警告
自定义注解
- 注解声明为:@interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
- 如果自定义注解没有成员,表明是一个标识作用。
public @interface MyAnnotation {
String value() default "hello";
}
@MyAnnotation("Hi");
元注解
- 元注解:对现有的注解进行解释说明的注解
Retention
- Retention:指定所修饰的 Annotation 的生命周期
- SOURCE:注解仅保留在源文件中,不写入.class文件
- CLASS(默认行为):注解将写入.class文件,但运行时不会加载到内存中
- RUNTIME:注解在运行时也会存在。
- 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target
- Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(包、类、构造器等)
Documented
- Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited
- Inherited:被它修饰的 Annotation 将具有继承性。
可重复注解:
- 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
- MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
类型注解
class Generic<@MyAnnotation T>{
public void show() throws @MyAnnotation RuntimeException{
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
第四章 集合
4.1 概述
- 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
- 数组
- 初始化后长度不可变
- ArrayList、LinkedList、Vector
- 定义后只能存储指定类型的数据
- HashSet、LinkedHashSet、TreeSet
- 数据可以重复,存放时顺序存储
- HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
- 初始化后长度不可变
4.2 集合框架
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序、可重复的集合(相当于动态数组)
- Set:元素无序、不可重复的集合
- Map接口:双列集合,存储键值对(key-value)
4.3 Collection接口
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
//size():获取添加的元素的个数
System.out.println(coll.size());//4
//addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println(coll.size());//6
System.out.println(coll);
//[AA, BB, 123, Mon Nov 28 06:15:02 GMT 2022, 456, CC]
//clear():清空集合元素
coll.clear();
//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());
//true
//contains():判断是否含有当前元素
coll.add(1234);
System.out.println(coll.contains(1234));
//true
coll.add(new String("12345"));
System.out.println( coll.contains(new String("12345")) );
//true
//contains调用参数相应类的equals()来比较
//containsAll(Collection c)
//判断c中所有元素是否在当前集合中
Collection c=Arrays.asList(1234,new String("12345"));
System.out.println(coll.containsAll(c));//true
//remove(a):移除a
//同样使用equals()
coll.remove("12345");
System.out.println( coll.contains(new String("12345")) );//false
//removeAll(Collection b):
//移除b与当前集合共有的元素
Collection b=Arrays.asList(1,12,123,1234,12345);
coll.removeAll(b);
System.out.println( coll.contains(1234) );//false
//retainAll(Collection b)
//与集合b求交集
// Collection c1= (1,2,3,4,5,6,7,8,9);
// Collection c2= (2,4,6,8,10,12,14);
// c1.retainAll(c2);
// System.out.println(c1);
//equals(Collection b)
//判断两个集合是否相等
//hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//toArray():集合→数组
coll.add(1);
coll.add(2);
coll.add(3);
Object[] arr=coll.toArray();
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}//1 2 3
//数组→集合:
//Arrays.asList()
List<String> list=Arrays.asList(new String[]{"A","B","C"});
System.out.println(list);//[A, B, C]
List arr1=Arrays.asList(new int[]{123,456});
System.out.println(arr1);//[[I@ea4a92b]
List arr2=Arrays.asList(new Integer[]{123,456});
System.out.println(arr2);//[123, 456]
//iterator():返回iterator接口的实例,用于遍历元素
4.4 Iterator 迭代器
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- 提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
Iterator i=c.iterator();
System.out.println(i.next());
System.out.println(i.next());
System.out.println(i.next());
System.out.println(i.next());
//System.out.println(i.next());
// 异常
//Exception in thread "main" java.util.NoSuchElementException
i=c.iterator();
//hasNext()判断是否存在下一个元素
while(i.hasNext()){
System.out.println(i.next());
//1. 指针下移
//2. 返回指向的元素
}
i=c.iterator();
while(i.hasNext()){
Object obj=i.next();
if(obj.equals(3)){
i.remove();
//删除当前元素
break;
}
}
System.out.println(c);
//[1, 2, 4]
foreach新特性遍历集合或数组
- 集合数组都能用
//for(集合元素的类型 局部变量:集合对象)
for(Object obj:c){
System.out.println(obj);
}
4.5 List接口
- 存储有序的、可重复的数据
- ArrayList:作为List接口的主要实现类,线程不安全,执行效率高;底层使用Object[]存储
- LinkedList:底层使用双向链表——频繁地删除插入效率高
- Vector:作为List接口的古老实现类,线程安全,执行效率低;底层使用Object[]存储
List常用方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex(不包括toIndex)位置的子集合
总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:
① Iterator迭代器方式
② 增强foreach循环
③ 普通的循环
4.6 Set接口
- HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值
- LinkedHashSet:HashSet的子类,可以按照添加的数据遍历
- TreeSet:可以按照添加对象指定的属性进行排序(存储同一类型的数据)
- Set接口中没有额外定义的方法,使用的是Collection中的方法。
HashSet添加数据的过程
You can read this:https://www.cnblogs.com/missarain/articles/16025789.html
- 首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
- 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。--->情况2
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:
- equals()返回true,元素a添加失败
- equals()返回false,则元素a添加成功。--->情况2
LinkedHashSet
- 相当于在HashSet基础上添加了双向链表来记录添加时的顺序。
TreeSet
- 存储同一类型的数据。
- 数据按照指定的顺序排列。
- 自然排序
- 当前类 implements Comparable 并重写 compareTo()
- 自然排序中,比较两个对象是否相同:compareTo()是否返回0
- 定制排序
- 自然排序
// 定制排序
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
HashSet添加数据的过程
You can read this:https://www.cnblogs.com/missarain/articles/16025789.html
- 首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
- 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。--->情况2
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:
- equals()返回true,元素a添加失败
- equals()返回false,则元素a添加成功。--->情况2
LinkedHashSet
- 相当于在HashSet基础上添加了双向链表来记录添加时的顺序。
TreeSet
- 存储同一类型的数据。
- 数据按照指定的顺序排列。
- 自然排序
- 当前类 implements Comparable 并重写 compareTo()
- 自然排序中,比较两个对象是否相同:compareTo()是否返回0
- 定制排序
- 自然排序
// 定制排序
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
总结
- List 需要重写equals()方法
- HashSet、LinkedHashSet需要重写equals()、hashCode()
- TreeSet需要继承Comparable并重写compareTo(),或者写一个Comparator对象并使用TreeSet(Comparator )
4.7 MAP
- MAP:键值对,存储key-value的数据
- HashMap: 线程不安全,效率高,key或value允许存储null
- LinkedHashMap: 链表结构,可以按照添加的顺序遍历
- TreeMap: 根据key排序,底层使用红黑树
- Hashtable:年代较远,线程安全,效率低,不允许存储null
- Properties: 常用来处理配置文件
key与value
- entry:一个键值对(key-value)构成一个entry对象
- key:无序,不可重复,使用set存储(需要重写相应方法)
- value:无序,可重复,使用Collection存储
HashMap底层原理
- map.put(k,v):
- 第一步首先将k,v封装到Node对象当中(节点)。
- 第二步它的底层会调用K的hashCode()方法得出hash值。
- 第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标。
- 下标位置上如果没有任何元素,就把Node添加到这个位置上。
- 如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。 - 如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。 - 如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
- JDK8之后,如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。
Map常用方法
put(key,value)
// 添加数据
putAll(Map a)
// 将a中所有键值对添加到当前map中
remove(key)
// 移除键值对
clear()
// 清空map
isEmpty()
// 是否为空
equals(obj)
// 判断是否相等
containsKey(key)
// 是否存在key
containsValue(value)
// 是否存在value
- Map的遍历
// 分开遍历
Set set=map.keyset();
Iterator it=se.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
Collection values = map.values();
for(Object obj:values){
System.out.println(obj);
}
// 上面key与value输出的顺序对应一致
Set es=map.entrySet();
Iterator it=es.iterator();
while(it.hasNext()){
Object k = it.next();
Object v = map.get(k);
System.out.println(k+" : "+v);
}
TreeMap
- TreeMap需要设置排序。可以是自然排序,也可以是定制排序
Properties
- Properties是Hashtable的子类,用于处理属性文件
- 其key与value都是字符串类型
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Collections
- Collections是Collection的工具类
reverse(List)
// 反转List
shuffle(List)
// 对List中的元素进行随机排序
sort(List)
// List排序
max/min(Collection)
max/min(Collection,Comparator)
// 最大最小
copy(List dest,List scr)
// 复制
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);
// Collections 类中提供了多个 synchronizedXxx() 方法,
// 该方法可使将指定集合包装成线程同步的集合,从而可以解决
// 多线程并发访问集合时的线程安全问题
List list1 = Collections.synchronizedList(list);
//返回的list1即为线程安全的List
第五章 泛型
5.1 集合中的泛型
- 用泛型指名要存储的数据类型
ArrayList<Integer> a=new ArrayList<Integer>();
a.add(1);
a.add(2);
a.add(3);
System.out.println(a);
for(Integer o : a){
int num=o;
System.out.println(num);
}
Iterator<Integer> it=a.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
Map<Integer,String> m=new HashMap<Integer,String>();
m.put(1,"a");
m.put(2,"b");
m.put(3,"c");
Set<Map.Entry<Integer,String>> e = m.entrySet();
Iterator<Map.Entry<Integer,String>> it=e.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> p =it.next();
Integer key = p.getKey();
String value = p.getValue();
System.out.println(key+":"+value);
}
5.2 自定义泛型结构
public class Main
{
public static void main(String[] args) {
MyClass<String> m=new MyClass<String>(123,"aString");
System.out.println(m);
HashMap<Integer,String> map=new HashMap<Integer,String>();
map.put(789,"Map");
BClass b=new BClass(123,456,map);
System.out.println(b);
CClass c=new CClass("s123-C-class",123,new Integer(4578));
System.out.println(c);
}
}
// 自定义类中的泛型
class MyClass<T>{
int num;
T myT;
public MyClass(){
}
public MyClass(int num,T t){
this.num=num;
this.myT=t;
}
public void setNum(int a){
num=a;
}
public void setMyT(T t){
myT=t;
}
//静态方法不能使用泛型
//public static T getMyT(){
// return myT;
//}
public String toString(){
return "MyClass:"+num+","+myT;
}
}
//类继承已经指名类型的泛型类
class BClass extends MyClass<HashMap<Integer,String>>{
int Bnum;
public BClass(int bn,int num,HashMap m){
super(num,m);
Bnum=bn;
}
public String toString(){
return "BClass:"+Bnum+","+num+","+myT;
}
}
//类继承未指名类型的泛型类
class CClass<T> extends MyClass<T>{
String s;
public CClass(String cs,int num,T t){
super(num,t);
s=cs;
}
public String toString(){
return "CClass:"+s+","+num+","+myT;
}
}
5.3 泛型方法
public class Main
{
public static void main(String[] args) {
M m=new M();
System.out.println(m.toString(new Integer(1234)));
}
}
class M{
public <T> String toString(T t){
return "Func-toString:"+t;
}
// 泛型方法也可以写成静态方法
//public static <T> String toString<T>(T t){
}
5.4 泛型继承问题与通配符
List<String> ls1=new ArrayList<String>();
List<Integer> ls2=new ArrayList<Integer>();
//ls2=ls1;
// 编译不通过
List<?> ls3;
ls3=ls1;
ls3=ls2;
有条件限制的通配符的使用
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.*;
public class Main
{
public static void main(String[] args) {
List<A> la=null;
List<B_AChild> lb=null;
List<Object> lo=null;
List<? extends A> list1 = null;
list1=la;
list1=lb;
// list2=lo; //错误
// extends A 可以匹配A及其继承类
List<? super A> list2 = null;
list2=la;
// list2=lb; //错误
list2=lo;
// super A 可以匹配A及其父类
}
}
class A{}
class B_AChild extends A{}
2022年12月14日